Migrate world bindings to PUC Lua
This commit is contained in:
parent
9687c25bb0
commit
311c6a4e47
52
ADDITIONS.md
52
ADDITIONS.md
@ -127,6 +127,15 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* Added `noise:init(seed: long)`, re-initializes noise generator with new seed, but same parameters (object returned by `sb.makePerlinSource`)
|
* Added `noise:init(seed: long)`, re-initializes noise generator with new seed, but same parameters (object returned by `sb.makePerlinSource`)
|
||||||
* Added `math.clamp(value, min, max)`
|
* Added `math.clamp(value, min, max)`
|
||||||
* Added `math.lerp(t, a, b)`
|
* Added `math.lerp(t, a, b)`
|
||||||
|
* Added `findhandle(name: String): Any?` and `gethandle(name: String): Any`, for getting engine-"private" handles, usually tables, which have a string-defined name attached to them
|
||||||
|
* `findhandle` will return nothing if handle does not exist, while `gethandle` will throw an exception if handle does not exist. Those who come from Garry's Mod you should notice this functionality is very similar to [FindMetaTable](https://wiki.facepunch.com/gmod/Global.FindMetaTable), and you won't be wrong
|
||||||
|
* Handles (Lua values stored on Lua stack in separate technical thread) are used mostly for storing metamethods for Java objects exposed to Lua state, such as `RandomGenerator`, or `CompletableFuture`.
|
||||||
|
To those tech-savvy Lua wizards who wonder why handles are used and not `LUA_REGISTRYINDEX` - using thread stack is considerably faster than using `LUA_REGISTRYINDEX`, the only downside is that stack size is limited by compile-time constant, so it can't grow indefinitely
|
||||||
|
* Currently, next handles are available for Lua code to get:
|
||||||
|
* RandomGenerator
|
||||||
|
* LuaFuture
|
||||||
|
* PerlinNoise
|
||||||
|
* PathFinder
|
||||||
|
|
||||||
## Random
|
## Random
|
||||||
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
* Added `random:randn(deviation: double, mean: double): double`, returns normally distributed double, where `deviation` stands for [Standard deviation](https://en.wikipedia.org/wiki/Standard_deviation), and `mean` specifies middle point
|
||||||
@ -176,6 +185,17 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* Added `status.minimumLiquidStatusEffectPercentage(): Double`
|
* Added `status.minimumLiquidStatusEffectPercentage(): Double`
|
||||||
* Added `status.setMinimumLiquidStatusEffectPercentage(value: Double)`
|
* Added `status.setMinimumLiquidStatusEffectPercentage(value: Double)`
|
||||||
|
|
||||||
|
## Path Finder
|
||||||
|
|
||||||
|
In new engine, pathfinder returned by `world.platformerPathStart()`, if unable find path to goal inside `finder:explore()` due to budget constraints, launches
|
||||||
|
off-thread explorer which continues to search path where `explore()` left off, allowing to make world responsive while multiple NPCs are path finding.
|
||||||
|
|
||||||
|
* `finder:explore(maxIterations: Int? = 800, useOffThread: Boolean = true): List<PathNode>?` now accepts second (optional) argument telling whenever it should continue to work off-thread automatically
|
||||||
|
* Defaults to true, since it shouldn't cause any major issues. However, if you want to continue manually exploring sometime in future, you can specify second argument as `false`
|
||||||
|
* Added `finder:runAsync()`, which launches off-thread exploration task immediately. Does nothing if already exploring off-thread
|
||||||
|
* Added `finder:isExploringOffThread(): Boolean`, used to determine if pathfinder is actively working off-thread (always returns false if work is finished regardless of actual result)
|
||||||
|
* Added `finder:usedOffThread(): Boolean`, used to determine if pathfinder ever worked off-thread
|
||||||
|
|
||||||
## world
|
## world
|
||||||
|
|
||||||
#### Additions
|
#### Additions
|
||||||
@ -183,12 +203,20 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* Added `world.liquidNamesAlongLine(start: Vector2d, end: Vector2d): List<LiquidState>`, will return Liquid' name instead of its ID
|
* 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.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.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.destroyLiquidPromise(at: Vector2i): RpcPromise<Boolean, LiquidState?>`, returns promise with two values (vararg, not table): whenever operation was successful, and previously stored liquid state, with liquid name, and not liquid id
|
||||||
* Added `world.gravityVector(at: Vector2d): Vector2d`. **Attention:** directional gravity is WIP.
|
* Added `world.gravityVector(at: Vector2d): Vector2d`. **Attention:** directional gravity is WIP.
|
||||||
* Added `world.itemDropLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
* Added `world.itemDropLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
* Added `world.playerLineQuery(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.objectLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
* Added `world.loungeableLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
* Added `world.loungeableLineQuery(p0: Vector2d, p1: Vector2d, options: Table?): List<EntityID>`
|
||||||
|
* Implemented `Poly` entity queries, which were mentioned in engine's code but never implemented as actual Lua bindings (you could still use these in original engine IF you used non-line bindings AND specified poly as `poly = { ... }` in `options` table):
|
||||||
|
* Added `world.entityPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.itemDropPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.npcPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.monsterPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.playerPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.objectPolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
|
* Added `world.loungeablePolyQuery(poly: Poly, options: Table?): List<EntityID>`
|
||||||
* Added `world.loadUniqueEntityAsync(id: String): RpcPromise<EntityID>`
|
* Added `world.loadUniqueEntityAsync(id: String): RpcPromise<EntityID>`
|
||||||
* Added `world.findUniqueEntityAsync(id: String): RpcPromise<Vector2d?>`
|
* Added `world.findUniqueEntityAsync(id: String): RpcPromise<Vector2d?>`
|
||||||
* `world.findUniqueEntity` is legacy function, and to retain legacy behavior it will **block** world thread upon `result()` call if entity has not been found yet
|
* `world.findUniqueEntity` is legacy function, and to retain legacy behavior it will **block** world thread upon `result()` call if entity has not been found yet
|
||||||
@ -217,13 +245,30 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* If all tiles were protected, it will return `"protected"`.
|
* If all tiles were protected, it will return `"protected"`.
|
||||||
* If none tiles were damaged, it will return `"none"`.
|
* If none tiles were damaged, it will return `"none"`.
|
||||||
* Added `world.damageTileAreaPromise(radius: Double, position: Vector2i, layer: String, damageSource: Vector2d, damageType: String, damageAmount: Double, harvestLevel: Int = 999, sourceEntity: EntityID = 0): RpcPromise<String>`, with same notes as `world.damageTilesPromise()` apply
|
* Added `world.damageTileAreaPromise(radius: Double, position: Vector2i, layer: String, damageSource: Vector2d, damageType: String, damageAmount: Double, harvestLevel: Int = 999, sourceEntity: EntityID = 0): RpcPromise<String>`, with same notes as `world.damageTilesPromise()` apply
|
||||||
* Added `world.placeMaterialPromise(pos: Vector2i, layer: String, material: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<Vector2i>`, returning promise of unapplied tile modifications
|
* Added `world.placeMaterialPromise(pos: Vector2i, layer: String, material: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<List<Vector2i>>`, returning promise of unapplied tile modifications
|
||||||
* However, correct clientside result will be returned _only_ when using native protocol
|
* However, correct clientside result will be returned _only_ when using native protocol
|
||||||
* Added `world.placeModPromise(pos: Vector2i, layer: String, modifier: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<Vector2i>`, returning promise of unapplied tile modifications
|
* Added `world.placeModPromise(pos: Vector2i, layer: String, modifier: String, hueShift: Number?, allowOverlap: Boolean): RpcPromise<List<Vector2i>>`, returning promise of unapplied tile modifications
|
||||||
* However, correct clientside result will be returned _only_ when using native protocol
|
* However, correct clientside result will be returned _only_ when using native protocol
|
||||||
|
* Added `world.weatherStatusEffects(pos: Vector2i): List<EphemeralStatusEffect>`
|
||||||
|
* Added async (`RpcPromise<>`) variant for next functions (to call new variant, add `Async` at end of function's name, `containerTakeNumItemsAt` -> `containerTakeNumItemsAtAsync`), which allows to somewhat properly interact with remote containers (e.g. modify serverside container on client):
|
||||||
|
* Where possible, this new functionality makes use of existing functions (available on both new and old engines), but if remote is original engine, some functions might misbehave due to emulation of desired behavior.
|
||||||
|
Generally, you should avoid interacting with remote containers through Lua scripts, but since protocol allows it (and scripts previously could interact with them, albeit interaction was crippled), these improved functions were provided for your convenience.
|
||||||
|
* `world.containerConsume`
|
||||||
|
* `world.containerConsumeAt`
|
||||||
|
* `world.containerTakeAll`
|
||||||
|
* `world.containerTakeAt`
|
||||||
|
* `world.containerTakeNumItemsAt`
|
||||||
|
* `world.containerAddItems`
|
||||||
|
* `world.containerStackItems`
|
||||||
|
* `world.containerPutItemsAt`
|
||||||
|
* `world.containerSwapItems`
|
||||||
|
* `world.containerSwapItemsNoCombine`
|
||||||
|
* `world.containerItemApply`
|
||||||
|
|
||||||
#### Changes
|
#### Changes
|
||||||
|
|
||||||
|
* `world.getObjectParameter(id: EntityID, path: String, default: Json): Json` now returns third argument as-is (which is insanely faster), without copying or any other transformations of any kind
|
||||||
|
* This MIGHT break code which expect this function to always copy third argument, or transform it into json
|
||||||
* `world.entityHandItem(id: EntityID, hand: String): String` now accepts `"secondary"` as `hand` argument (in addition to `"primary"`/`"alt"`)
|
* `world.entityHandItem(id: EntityID, hand: String): String` now accepts `"secondary"` as `hand` argument (in addition to `"primary"`/`"alt"`)
|
||||||
* `world.containerConsume(id: EntityID, item: ItemDescriptor, exact: Boolean?): Boolean?` now accepts `exact` which forces exact match on item descriptor (default `false`)
|
* `world.containerConsume(id: EntityID, item: ItemDescriptor, exact: Boolean?): Boolean?` now accepts `exact` which forces exact match on item descriptor (default `false`)
|
||||||
* `world.flyingType(): String` has been made shared (previously was server world only)
|
* `world.flyingType(): String` has been made shared (previously was server world only)
|
||||||
@ -258,6 +303,7 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
|
|||||||
* `world.loadUniqueEntity()` **is busted in new engine** and will cause major issues if used (because engine is incapable for synchronous loading of world chunks, everything is async)
|
* `world.loadUniqueEntity()` **is busted in new engine** and will cause major issues if used (because engine is incapable for synchronous loading of world chunks, everything is async)
|
||||||
* If your mod is using it **PLEASE** switch to `world.loadUniqueEntityAsync(id: String): RpcPromise<EntityID>`
|
* If your mod is using it **PLEASE** switch to `world.loadUniqueEntityAsync(id: String): RpcPromise<EntityID>`
|
||||||
* `world.spawnLiquid()` is deprecated, use `world.spawnLiquidPromise()` instead
|
* `world.spawnLiquid()` is deprecated, use `world.spawnLiquidPromise()` instead
|
||||||
|
* `world.destroyLiquid()` is deprecated for two reasons - it doesn't report whenever operation successful, and returns liquid id instead of liquid name, use `world.destroyLiquidPromise()` instead
|
||||||
* `world.damageTiles()` is deprecated, use `world.damageTilesPromise()` instead
|
* `world.damageTiles()` is deprecated, use `world.damageTilesPromise()` instead
|
||||||
* `world.damageTileArea()` is deprecated, use `world.damageTileAreaPromise()` instead
|
* `world.damageTileArea()` is deprecated, use `world.damageTileAreaPromise()` instead
|
||||||
* `world.placeMaterial()` is deprecated, use `world.placeMaterialPromise()` instead
|
* `world.placeMaterial()` is deprecated, use `world.placeMaterialPromise()` instead
|
||||||
|
@ -159,7 +159,7 @@ public interface LuaJNR {
|
|||||||
|
|
||||||
// проверка стека
|
// проверка стека
|
||||||
@IgnoreError
|
@IgnoreError
|
||||||
public int lua_checkstack(@NotNull Pointer luaState, int value);
|
public boolean lua_checkstack(@NotNull Pointer luaState, int value);
|
||||||
@IgnoreError
|
@IgnoreError
|
||||||
public int lua_absindex(@NotNull Pointer luaState, int value);
|
public int lua_absindex(@NotNull Pointer luaState, int value);
|
||||||
@IgnoreError
|
@IgnoreError
|
||||||
|
@ -276,6 +276,10 @@ class Registry<T : Any>(val name: String, val storeJson: Boolean = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ref(index: Either<String, Int>): Ref<T> {
|
||||||
|
return index.map({ ref(it) }, { ref(it) })
|
||||||
|
}
|
||||||
|
|
||||||
operator fun contains(index: String) = lock.read { index in keysInternal }
|
operator fun contains(index: String) = lock.read { index in keysInternal }
|
||||||
operator fun contains(index: Int) = lock.read { index in idsInternal }
|
operator fun contains(index: Int) = lock.read { index in idsInternal }
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
|||||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantPieceEntity
|
import ru.dbotthepony.kstarbound.world.entities.tile.PlantPieceEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
enum class EntityType(override val jsonName: String, val storeName: String, val canBeCreatedByClient: Boolean, val canBeSpawnedByClient: Boolean, val ephemeralIfSpawnedByClient: Boolean = true) : IStringSerializable {
|
enum class EntityType(override val jsonName: String, val storeName: String, val canBeCreatedByClient: Boolean, val canBeSpawnedByClient: Boolean, val ephemeralIfSpawnedByClient: Boolean = true) : IStringSerializable {
|
||||||
PLANT("plant", "PlantEntity", false, false) {
|
PLANT("plant", "PlantEntity", false, false) {
|
||||||
@ -158,4 +160,8 @@ enum class EntityType(override val jsonName: String, val storeName: String, val
|
|||||||
|
|
||||||
abstract suspend fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity
|
abstract suspend fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity
|
||||||
abstract fun fromStorage(data: JsonObject): AbstractEntity
|
abstract fun fromStorage(data: JsonObject): AbstractEntity
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ALL: Set<EntityType> = Collections.unmodifiableSet(EnumSet.allOf(EntityType::class.java))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.math.quintic2
|
import ru.dbotthepony.kstarbound.math.quintic2
|
||||||
|
import ru.dbotthepony.kstarbound.util.floorToInt
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
||||||
import ru.dbotthepony.kstarbound.world.Universe
|
import ru.dbotthepony.kstarbound.world.Universe
|
||||||
@ -32,6 +33,7 @@ import ru.dbotthepony.kstarbound.world.physics.Poly
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
class WorldTemplate(val geometry: WorldGeometry) {
|
class WorldTemplate(val geometry: WorldGeometry) {
|
||||||
var seed: Long = 0L
|
var seed: Long = 0L
|
||||||
@ -326,16 +328,23 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cellInfo(x: Int, y: Int): CellInfo {
|
fun cellInfo(x: Int, y: Int): CellInfo {
|
||||||
worldLayout ?: return CellInfo(x, y)
|
val pos = geometry.wrap(x, y)
|
||||||
val vec = Vector2i(x, y)
|
|
||||||
return cellCache[vec.hashCode() and 255].get(vec)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cellInfo(pos: Vector2i): CellInfo {
|
|
||||||
worldLayout ?: return CellInfo(pos.x, pos.y)
|
worldLayout ?: return CellInfo(pos.x, pos.y)
|
||||||
return cellCache[pos.hashCode() and 255].get(pos)
|
return cellCache[pos.hashCode() and 255].get(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
fun cellInfo(pos: Vector2i): CellInfo {
|
||||||
|
val pos = geometry.wrap(pos)
|
||||||
|
worldLayout ?: return CellInfo(pos.x, pos.y)
|
||||||
|
return cellCache[pos.hashCode() and 255].get(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cellInfo(pos: Vector2d): CellInfo {
|
||||||
|
val (x, y) = geometry.wrap(pos)
|
||||||
|
return cellInfo(x.floorToInt(), y.floorToInt())
|
||||||
|
}
|
||||||
|
|
||||||
private fun cellInfo0(x: Int, y: Int): CellInfo {
|
private fun cellInfo0(x: Int, y: Int): CellInfo {
|
||||||
val info = CellInfo(x, y)
|
val info = CellInfo(x, y)
|
||||||
val layout = worldLayout ?: return info
|
val layout = worldLayout ?: return info
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
|
data class CommonHandleRegistry(
|
||||||
|
val future: LuaHandle,
|
||||||
|
val pathFinder: LuaHandle,
|
||||||
|
)
|
@ -15,6 +15,7 @@ import org.classdump.luna.runtime.AbstractFunction3
|
|||||||
import org.classdump.luna.runtime.ExecutionContext
|
import org.classdump.luna.runtime.ExecutionContext
|
||||||
import ru.dbotthepony.kommons.gson.set
|
import ru.dbotthepony.kommons.gson.set
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
import ru.dbotthepony.kommons.util.IStruct2d
|
import ru.dbotthepony.kommons.util.IStruct2d
|
||||||
import ru.dbotthepony.kommons.util.IStruct2f
|
import ru.dbotthepony.kommons.util.IStruct2f
|
||||||
@ -556,6 +557,63 @@ fun LuaThread.ArgStack.nextOptionalVector2d(position: Int = this.position++): Ve
|
|||||||
return lua.getVector2d(position)
|
return lua.getVector2d(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? {
|
||||||
|
val abs = this.absStackIndex(stackIndex)
|
||||||
|
|
||||||
|
if (!this.isTable(abs))
|
||||||
|
return null
|
||||||
|
|
||||||
|
push(3)
|
||||||
|
val type = loadTableValue(abs)
|
||||||
|
pop()
|
||||||
|
|
||||||
|
if (type == LuaType.NUMBER) {
|
||||||
|
return Either.right(getAABB(stackIndex) ?: return null)
|
||||||
|
} else {
|
||||||
|
return Either.left(getVector2i(stackIndex) ?: return null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.ArgStack.nextVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB> {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: Vector2d expected, got nil")
|
||||||
|
|
||||||
|
return lua.getVector2iOrAABB(position)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: Vector2d or AABB expected, got ${lua.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.ArgStack.nextOptionalVector2iOrAABB(position: Int = this.position++): Either<Vector2i, AABB>? {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return lua.getVector2iOrAABB(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.getRegistryID(stackIndex: Int = -1): Either<String, Int>? {
|
||||||
|
val abs = absStackIndex(stackIndex)
|
||||||
|
|
||||||
|
when (typeAt(abs)) {
|
||||||
|
LuaType.NUMBER -> return Either.right(getLong(stackIndex)!!.toInt())
|
||||||
|
LuaType.STRING -> return Either.left(getString(stackIndex)!!)
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.ArgStack.nextRegistryID(position: Int = this.position++): Either<String, Int> {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: Vector2d expected, got nil")
|
||||||
|
|
||||||
|
return lua.getRegistryID(position)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: Vector2d or AABB expected, got ${lua.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.ArgStack.nextOptionalRegistryID(position: Int = this.position++): Either<String, Int>? {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return lua.getRegistryID(position)
|
||||||
|
}
|
||||||
|
|
||||||
fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
|
fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
|
||||||
val abs = this.absStackIndex(stackIndex)
|
val abs = this.absStackIndex(stackIndex)
|
||||||
|
|
||||||
@ -750,7 +808,8 @@ fun LuaThread.ArgStack.nextOptionalAABBi(position: Int = this.position++): AABBi
|
|||||||
return lua.getAABBi(position)
|
return lua.getAABBi(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct4i) {
|
fun LuaThread.push(value: IStruct4i?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 4)
|
pushTable(arraySize = 4)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z, w) = value
|
val (x, y, z, w) = value
|
||||||
@ -772,7 +831,8 @@ fun LuaThread.push(value: IStruct4i) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct3i) {
|
fun LuaThread.push(value: IStruct3i?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 3)
|
pushTable(arraySize = 3)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z) = value
|
val (x, y, z) = value
|
||||||
@ -790,7 +850,8 @@ fun LuaThread.push(value: IStruct3i) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct2i) {
|
fun LuaThread.push(value: IStruct2i?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 2)
|
pushTable(arraySize = 2)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y) = value
|
val (x, y) = value
|
||||||
@ -804,7 +865,8 @@ fun LuaThread.push(value: IStruct2i) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct4f) {
|
fun LuaThread.push(value: IStruct4f?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 4)
|
pushTable(arraySize = 4)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z, w) = value
|
val (x, y, z, w) = value
|
||||||
@ -826,7 +888,8 @@ fun LuaThread.push(value: IStruct4f) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct3f) {
|
fun LuaThread.push(value: IStruct3f?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 3)
|
pushTable(arraySize = 3)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z) = value
|
val (x, y, z) = value
|
||||||
@ -844,7 +907,8 @@ fun LuaThread.push(value: IStruct3f) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct2f) {
|
fun LuaThread.push(value: IStruct2f?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 2)
|
pushTable(arraySize = 2)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y) = value
|
val (x, y) = value
|
||||||
@ -858,7 +922,8 @@ fun LuaThread.push(value: IStruct2f) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct4d) {
|
fun LuaThread.push(value: IStruct4d?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 4)
|
pushTable(arraySize = 4)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z, w) = value
|
val (x, y, z, w) = value
|
||||||
@ -880,7 +945,8 @@ fun LuaThread.push(value: IStruct4d) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct3d) {
|
fun LuaThread.push(value: IStruct3d?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 3)
|
pushTable(arraySize = 3)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y, z) = value
|
val (x, y, z) = value
|
||||||
@ -898,7 +964,8 @@ fun LuaThread.push(value: IStruct3d) {
|
|||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LuaThread.push(value: IStruct2d) {
|
fun LuaThread.push(value: IStruct2d?) {
|
||||||
|
value ?: return push()
|
||||||
pushTable(arraySize = 2)
|
pushTable(arraySize = 2)
|
||||||
val table = stackTop
|
val table = stackTop
|
||||||
val (x, y) = value
|
val (x, y) = value
|
||||||
@ -911,3 +978,87 @@ fun LuaThread.push(value: IStruct2d) {
|
|||||||
push(y)
|
push(y)
|
||||||
setTableValue(table)
|
setTableValue(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct2i?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct3i?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct4i?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct2d?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct3d?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct4d?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct2f?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct3f?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: String, value: IStruct4f?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct2i?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct3i?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct4i?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct2d?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct3d?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct4d?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct2f?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct3f?) { push(key); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Long, value: IStruct4f?) { push(key); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct2i?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct3i?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct4i?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct2d?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct3d?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct4d?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct2f?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct3f?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
fun LuaThread.setTableValue(key: Int, value: IStruct4f?) { push(key.toLong()); push(value); setTableValue() }
|
||||||
|
|
||||||
|
fun LuaThread.push(value: AABB?) {
|
||||||
|
value ?: return push()
|
||||||
|
pushTable(arraySize = 4)
|
||||||
|
val table = stackTop
|
||||||
|
val (x, y) = value.mins
|
||||||
|
val (z, w) = value.maxs
|
||||||
|
|
||||||
|
push(1)
|
||||||
|
push(x)
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(2)
|
||||||
|
push(y)
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(3)
|
||||||
|
push(z)
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(4)
|
||||||
|
push(w)
|
||||||
|
setTableValue(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LuaThread.push(value: AABBi?) {
|
||||||
|
value ?: return push()
|
||||||
|
pushTable(arraySize = 4)
|
||||||
|
val table = stackTop
|
||||||
|
val (x, y) = value.mins
|
||||||
|
val (z, w) = value.maxs
|
||||||
|
|
||||||
|
push(1)
|
||||||
|
push(x.toLong())
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(2)
|
||||||
|
push(y.toLong())
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(3)
|
||||||
|
push(z.toLong())
|
||||||
|
setTableValue(table)
|
||||||
|
|
||||||
|
push(4)
|
||||||
|
push(w.toLong())
|
||||||
|
setTableValue(table)
|
||||||
|
}
|
||||||
|
@ -2,9 +2,10 @@ package ru.dbotthepony.kstarbound.lua
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
import java.lang.ref.Cleaner.Cleanable
|
||||||
|
|
||||||
class LuaHandle(private val parent: LuaHandleThread, val handle: Int) : Closeable {
|
class LuaHandle(private val parent: LuaSharedState, val handle: Int, val key: Any?) : Closeable {
|
||||||
private val cleanables = ArrayList<Runnable>()
|
private val cleanable: Cleanable
|
||||||
|
|
||||||
var isValid = true
|
var isValid = true
|
||||||
private set
|
private set
|
||||||
@ -12,28 +13,24 @@ class LuaHandle(private val parent: LuaHandleThread, val handle: Int) : Closeabl
|
|||||||
init {
|
init {
|
||||||
val parent = parent
|
val parent = parent
|
||||||
val handle = handle
|
val handle = handle
|
||||||
|
val key = key
|
||||||
|
|
||||||
cleanables.add(Starbound.CLEANER.register(this) {
|
cleanable = Starbound.CLEANER.register(this) {
|
||||||
parent.freeHandle(handle)
|
parent.freeHandle(handle, key)
|
||||||
}::clean)
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(into: LuaThread) {
|
fun push(into: LuaThread) {
|
||||||
check(isValid) { "Tried to use NULL handle!" }
|
check(isValid) { "Tried to use NULL handle!" }
|
||||||
parent.thread.push()
|
parent.handlesThread.push()
|
||||||
parent.thread.copy(handle, -1)
|
parent.handlesThread.copy(handle, -1)
|
||||||
parent.thread.moveStackValuesOnto(into)
|
parent.handlesThread.moveStackValuesOnto(into)
|
||||||
}
|
|
||||||
|
|
||||||
fun onClose(cleanable: Runnable) {
|
|
||||||
check(isValid) { "No longer valid" }
|
|
||||||
cleanables.add(cleanable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (!isValid) return
|
if (!isValid) return
|
||||||
cleanables.forEach { it.run() }
|
cleanable.clean()
|
||||||
isValid = false
|
isValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,52 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua
|
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
|
|
||||||
class LuaHandleThread(mainThread: LuaThread) {
|
|
||||||
private val pendingFree = ConcurrentLinkedQueue<Int>()
|
|
||||||
private val freeHandles = IntAVLTreeSet()
|
|
||||||
private var nextHandle = 0
|
|
||||||
// faster code path
|
|
||||||
private var handlesInUse = 0
|
|
||||||
|
|
||||||
val thread = mainThread.newThread(true)
|
|
||||||
|
|
||||||
init {
|
|
||||||
mainThread.storeRef(LuaThread.LUA_REGISTRYINDEX)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun freeHandle(handle: Int) {
|
|
||||||
pendingFree.add(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cleanup() {
|
|
||||||
if (handlesInUse == 0) return
|
|
||||||
var handle = pendingFree.poll()
|
|
||||||
|
|
||||||
while (handle != null) {
|
|
||||||
handlesInUse--
|
|
||||||
freeHandles.add(handle)
|
|
||||||
thread.push()
|
|
||||||
thread.copy(-1, handle)
|
|
||||||
thread.pop()
|
|
||||||
|
|
||||||
handle = pendingFree.poll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allocateHandle(): LuaHandle {
|
|
||||||
handlesInUse++
|
|
||||||
|
|
||||||
if (freeHandles.isEmpty()) {
|
|
||||||
return LuaHandle(this, ++nextHandle)
|
|
||||||
} else {
|
|
||||||
val handle = freeHandles.firstInt()
|
|
||||||
freeHandles.remove(handle)
|
|
||||||
|
|
||||||
thread.copy(-1, handle)
|
|
||||||
thread.pop()
|
|
||||||
return LuaHandle(this, handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,89 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.lua
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||||
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||||
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
|
||||||
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class LuaSharedState(val handlesThread: LuaThread) {
|
||||||
|
private val pendingFree = ConcurrentLinkedQueue<Int>()
|
||||||
|
private val freeHandles = IntAVLTreeSet()
|
||||||
|
private var nextHandle = 0
|
||||||
|
// faster code path
|
||||||
|
private var handlesInUse = 0
|
||||||
|
|
||||||
|
private val namedHandles = HashMap<Any, LuaHandle>()
|
||||||
|
var random: RandomGenerator = random()
|
||||||
|
|
||||||
|
var commonHandles by Delegates.notNull<CommonHandleRegistry>()
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun initializeHandles(mainThread: LuaThread) {
|
||||||
|
val future = LuaFuture.initializeHandle(mainThread)
|
||||||
|
val pathFinder = LuaPathFinder.initializeHandle(mainThread)
|
||||||
|
|
||||||
|
commonHandles = CommonHandleRegistry(
|
||||||
|
future = future,
|
||||||
|
pathFinder = pathFinder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun freeHandle(handle: Int, key: Any?) {
|
||||||
|
pendingFree.add(handle)
|
||||||
|
|
||||||
|
if (key != null) {
|
||||||
|
namedHandles.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
if (handlesInUse == 0) return
|
||||||
|
var handle = pendingFree.poll()
|
||||||
|
|
||||||
|
while (handle != null) {
|
||||||
|
handlesInUse--
|
||||||
|
freeHandles.add(handle)
|
||||||
|
handlesThread.push()
|
||||||
|
handlesThread.copy(-1, handle)
|
||||||
|
handlesThread.pop()
|
||||||
|
|
||||||
|
handle = pendingFree.poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allocateHandle(name: Any?): LuaHandle {
|
||||||
|
require(name == null || name !in namedHandles) { "Named handle '$name' already exists" }
|
||||||
|
handlesInUse++
|
||||||
|
|
||||||
|
if (freeHandles.isEmpty()) {
|
||||||
|
if (nextHandle % 10 == 0) {
|
||||||
|
handlesThread.ensureExtraCapacity(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LuaHandle(this, ++nextHandle, name).also {
|
||||||
|
if (name != null) namedHandles[name] = it
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val handle = freeHandles.firstInt()
|
||||||
|
freeHandles.remove(handle)
|
||||||
|
|
||||||
|
handlesThread.copy(-1, handle)
|
||||||
|
handlesThread.pop()
|
||||||
|
|
||||||
|
return LuaHandle(this, handle, name).also {
|
||||||
|
if (name != null) namedHandles[name] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNamedHandle(key: Any): LuaHandle {
|
||||||
|
return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findNamedHandle(key: Any): LuaHandle? {
|
||||||
|
return namedHandles[key]
|
||||||
|
}
|
||||||
|
}
|
@ -17,24 +17,28 @@ import org.apache.logging.log4j.LogManager
|
|||||||
import org.lwjgl.system.MemoryStack
|
import org.lwjgl.system.MemoryStack
|
||||||
import org.lwjgl.system.MemoryUtil
|
import org.lwjgl.system.MemoryUtil
|
||||||
import ru.dbotthepony.kommons.gson.set
|
import ru.dbotthepony.kommons.gson.set
|
||||||
import ru.dbotthepony.kommons.util.Delegate
|
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
|
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
|
||||||
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
|
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a native Lua thread + stack, and transiently, a native Lua state
|
||||||
|
*
|
||||||
|
* These are not garbage collected, and *must* be [close]d manually when no longer in use, more specifically,
|
||||||
|
* right after [LuaThread] creation, it creates several cyclic references through GC root,
|
||||||
|
* so it never gets reclaimed by GC unless [close] is called
|
||||||
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class LuaThread private constructor(
|
class LuaThread private constructor(
|
||||||
private val pointer: Pointer,
|
private val pointer: Pointer,
|
||||||
@ -52,7 +56,7 @@ class LuaThread private constructor(
|
|||||||
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
|
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.cleanable = Starbound.CLEANER.register(this) {
|
this.cleanable = Cleaner.Cleanable {
|
||||||
LuaJNR.INSTANCE.lua_close(pointer)
|
LuaJNR.INSTANCE.lua_close(pointer)
|
||||||
panic.dispose()
|
panic.dispose()
|
||||||
}
|
}
|
||||||
@ -60,8 +64,19 @@ class LuaThread private constructor(
|
|||||||
panic.setAutoRelease(false)
|
panic.setAutoRelease(false)
|
||||||
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
|
LuaJNR.INSTANCE.lua_atpanic(pointer, panic.address)
|
||||||
|
|
||||||
randomHolder = Delegate.Box(random())
|
val handles = LuaJNR.INSTANCE.lua_newthread(pointer)
|
||||||
handleThread = LuaHandleThread(this)
|
storeRef(LUA_REGISTRYINDEX)
|
||||||
|
val handlesThread = LuaThread(handles, stringInterner)
|
||||||
|
sharedState = LuaSharedState(handlesThread)
|
||||||
|
sharedState.handlesThread.sharedState = sharedState
|
||||||
|
|
||||||
|
push("__nils")
|
||||||
|
createHandle("__nils")
|
||||||
|
pop()
|
||||||
|
|
||||||
|
push("__typehint")
|
||||||
|
createHandle("__typehint")
|
||||||
|
pop()
|
||||||
|
|
||||||
LuaJNR.INSTANCE.luaopen_base(this.pointer)
|
LuaJNR.INSTANCE.luaopen_base(this.pointer)
|
||||||
this.storeGlobal("_G")
|
this.storeGlobal("_G")
|
||||||
@ -76,10 +91,12 @@ class LuaThread private constructor(
|
|||||||
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
|
LuaJNR.INSTANCE.luaopen_utf8(this.pointer)
|
||||||
this.storeGlobal("utf8")
|
this.storeGlobal("utf8")
|
||||||
|
|
||||||
|
sharedState.initializeHandles(this)
|
||||||
|
|
||||||
provideUtilityBindings(this)
|
provideUtilityBindings(this)
|
||||||
provideRootBindings(this)
|
provideRootBindings(this)
|
||||||
|
|
||||||
load(globalScript, "@starbound.jar!/scripts/global.lua")
|
load(globalScript, "@/internal/global.lua")
|
||||||
call()
|
call()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +104,20 @@ class LuaThread private constructor(
|
|||||||
fun invoke(args: ArgStack): Int
|
fun invoke(args: ArgStack): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun interface Binding<T> {
|
||||||
|
fun invoke(self: T, arguments: ArgStack): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface Binding1<T, A0> {
|
||||||
|
fun invoke(self: T, extra: A0, arguments: ArgStack): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface Binding2<T, A0, A1> {
|
||||||
|
fun invoke(self: T, extra: A0, extra1: A1, arguments: ArgStack): Int
|
||||||
|
}
|
||||||
|
|
||||||
private var cleanable: Cleaner.Cleanable? = null
|
private var cleanable: Cleaner.Cleanable? = null
|
||||||
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
|
private var sharedState by Delegates.notNull<LuaSharedState>()
|
||||||
private var handleThread by Delegates.notNull<LuaHandleThread>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for generating random numbers using math.random
|
* Responsible for generating random numbers using math.random
|
||||||
@ -98,25 +126,16 @@ class LuaThread private constructor(
|
|||||||
* math.randomseed sets this property to brand-new generator with required seed
|
* math.randomseed sets this property to brand-new generator with required seed
|
||||||
*/
|
*/
|
||||||
var random: RandomGenerator
|
var random: RandomGenerator
|
||||||
get() = randomHolder.get()
|
get() = sharedState.random
|
||||||
set(value) = randomHolder.accept(value)
|
set(value) { sharedState.random = value }
|
||||||
|
|
||||||
private fun initializeFrom(other: LuaThread, skipHandle: Boolean) {
|
val commonHandles get() = sharedState.commonHandles
|
||||||
randomHolder = other.randomHolder
|
|
||||||
|
|
||||||
if (!skipHandle)
|
fun newThread(): LuaThread {
|
||||||
handleThread = other.handleThread
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cleanup() {
|
|
||||||
handleThread.cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newThread(skipHandle: Boolean = false): LuaThread {
|
|
||||||
val pointer = LuaJNR.INSTANCE.lua_newthread(pointer)
|
val pointer = LuaJNR.INSTANCE.lua_newthread(pointer)
|
||||||
|
|
||||||
return LuaThread(pointer, stringInterner).also {
|
return LuaThread(pointer, stringInterner).also {
|
||||||
it.initializeFrom(this, skipHandle)
|
it.sharedState = sharedState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +202,7 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
fun call(numArgs: Int = 0, numResults: Int = 0): Int {
|
||||||
cleanup()
|
sharedState.cleanup()
|
||||||
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
val status = LuaJNR.INSTANCE.lua_pcallk(this.pointer, numArgs, numResults, 0, 0L, 0L)
|
||||||
|
|
||||||
if (status == LUA_ERRRUN) {
|
if (status == LUA_ERRRUN) {
|
||||||
@ -407,6 +426,20 @@ class LuaThread private constructor(
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getInt(stackIndex: Int = -1): Int? {
|
||||||
|
if (!this.isNumber(stackIndex))
|
||||||
|
return null
|
||||||
|
|
||||||
|
val stack = MemoryStack.stackPush()
|
||||||
|
val status = stack.mallocInt(1)
|
||||||
|
val value = LuaJNR.INSTANCE.lua_tointegerx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
|
||||||
|
val b = status[0] > 0
|
||||||
|
stack.close()
|
||||||
|
|
||||||
|
if (!b) return null
|
||||||
|
return value.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getLongRaw(stackIndex: Int = -1): Long {
|
private fun getLongRaw(stackIndex: Int = -1): Long {
|
||||||
val stack = MemoryStack.stackPush()
|
val stack = MemoryStack.stackPush()
|
||||||
val status = stack.mallocInt(1)
|
val status = stack.mallocInt(1)
|
||||||
@ -623,11 +656,11 @@ class LuaThread private constructor(
|
|||||||
return LuaJNI.lua_tojobject(pointer.address(), stackIndex)
|
return LuaJNI.lua_tojobject(pointer.address(), stackIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) {
|
fun iterateTable(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit): Boolean {
|
||||||
val abs = this.absStackIndex(stackIndex)
|
val abs = this.absStackIndex(stackIndex)
|
||||||
|
|
||||||
if (!this.isTable(abs))
|
if (!this.isTable(abs))
|
||||||
return
|
return false
|
||||||
|
|
||||||
this.push()
|
this.push()
|
||||||
val top = this.stackTop
|
val top = this.stackTop
|
||||||
@ -641,6 +674,8 @@ class LuaThread private constructor(
|
|||||||
} finally {
|
} finally {
|
||||||
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
LuaJNR.INSTANCE.lua_settop(this.pointer, top - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> readTableKeys(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T>? {
|
fun <T> readTableKeys(stackIndex: Int = -1, keyVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T>? {
|
||||||
@ -795,6 +830,12 @@ class LuaThread private constructor(
|
|||||||
val lua get() = this@LuaThread
|
val lua get() = this@LuaThread
|
||||||
var position = 1
|
var position = 1
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (top >= 10) {
|
||||||
|
this@LuaThread.ensureExtraCapacity(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun peek(position: Int = this.position): LuaType {
|
fun peek(position: Int = this.position): LuaType {
|
||||||
if (position !in 1 .. top)
|
if (position !in 1 .. top)
|
||||||
return LuaType.NONE
|
return LuaType.NONE
|
||||||
@ -802,10 +843,63 @@ class LuaThread private constructor(
|
|||||||
return this@LuaThread.typeAt(position)
|
return this@LuaThread.typeAt(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun peekAndSkipNothing(): LuaType {
|
||||||
|
val peek = peek()
|
||||||
|
if (peek.isNothing) position++
|
||||||
|
return peek
|
||||||
|
}
|
||||||
|
|
||||||
fun hasNext(): Boolean {
|
fun hasNext(): Boolean {
|
||||||
return position <= top
|
return position <= top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun skip(amount: Int = 1) {
|
||||||
|
position += amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyRemaining(): JsonArray {
|
||||||
|
val result = JsonArray()
|
||||||
|
|
||||||
|
while (position <= top) {
|
||||||
|
result.add(nextJson())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun iterateTable(position: Int = this.position++, keyVisitor: LuaThread.(stackIndex: Int) -> Unit, valueVisitor: LuaThread.(stackIndex: Int) -> Unit) {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: table expected, got nil")
|
||||||
|
|
||||||
|
require(this@LuaThread.iterateTable(position, keyVisitor, valueVisitor)) {
|
||||||
|
"bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> readTableKeys(position: Int = this.position++, keyVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T> {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: table expected, got nil")
|
||||||
|
|
||||||
|
return this@LuaThread.readTableKeys(position, keyVisitor)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> readTableValues(position: Int = this.position++, valueVisitor: LuaThread.(stackIndex: Int) -> T): MutableList<T> {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: table expected, got nil")
|
||||||
|
|
||||||
|
return this@LuaThread.readTableValues(position, valueVisitor)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K, V> readTable(position: Int = this.position++, keyVisitor: LuaThread.(stackIndex: Int) -> K, valueVisitor: LuaThread.(stackIndex: Int) -> V): MutableList<Pair<K, V>> {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: table expected, got nil")
|
||||||
|
|
||||||
|
return this@LuaThread.readTable(position, keyVisitor, valueVisitor)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified T> nextObject(position: Int = this.position++): T {
|
inline fun <reified T> nextObject(position: Int = this.position++): T {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
throw IllegalArgumentException("bad argument #$position: Java object expected, got nil")
|
throw IllegalArgumentException("bad argument #$position: Java object expected, got nil")
|
||||||
@ -845,6 +939,16 @@ class LuaThread private constructor(
|
|||||||
?: throw IllegalArgumentException("bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}")
|
?: throw IllegalArgumentException("bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun nextInt(position: Int = this.position++): Int {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
|
||||||
|
|
||||||
|
return this@LuaThread.getInt(position)
|
||||||
|
?: throw IllegalArgumentException("bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextOptionalInt(position: Int = this.position++) = nextOptionalLong(position)?.toInt()
|
||||||
|
|
||||||
fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement {
|
fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
||||||
@ -853,6 +957,14 @@ class LuaThread private constructor(
|
|||||||
return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}")
|
return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun nextJsonArray(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonArray {
|
||||||
|
if (position !in 1 ..this.top)
|
||||||
|
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
||||||
|
|
||||||
|
val value = this@LuaThread.getJson(position, limit = limit)
|
||||||
|
return value as? JsonArray ?: throw IllegalArgumentException("bad argument #$position: JsonArray expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
|
}
|
||||||
|
|
||||||
fun nextOptionalJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
fun nextOptionalJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
return null
|
return null
|
||||||
@ -868,13 +980,6 @@ class LuaThread private constructor(
|
|||||||
return value ?: throw IllegalArgumentException("Lua code error: bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
return value ?: throw IllegalArgumentException("Lua code error: bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextAny(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? {
|
|
||||||
if (position !in 1 ..this.top)
|
|
||||||
throw IllegalArgumentException("bad argument #$position: json expected, got nil")
|
|
||||||
|
|
||||||
return this@LuaThread.getJson(position, limit = limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun nextDouble(position: Int = this.position++): Double {
|
fun nextDouble(position: Int = this.position++): Double {
|
||||||
if (position !in 1 ..this.top)
|
if (position !in 1 ..this.top)
|
||||||
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
|
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
|
||||||
@ -909,7 +1014,7 @@ class LuaThread private constructor(
|
|||||||
else if (type == LuaType.BOOLEAN)
|
else if (type == LuaType.BOOLEAN)
|
||||||
return this@LuaThread.getBoolean(position)
|
return this@LuaThread.getBoolean(position)
|
||||||
else
|
else
|
||||||
throw IllegalArgumentException("Lua code error: bad argument #$position: boolean expected, got $type")
|
throw IllegalArgumentException("bad argument #$position: boolean expected, got $type")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextBoolean(position: Int = this.position++): Boolean {
|
fun nextBoolean(position: Int = this.position++): Boolean {
|
||||||
@ -921,78 +1026,118 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(function: Fn, performanceCritical: Boolean) {
|
private fun closure(p: Long, function: Fn, performanceCritical: Boolean): Int {
|
||||||
LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
|
sharedState.cleanup()
|
||||||
cleanup()
|
val realLuaState: LuaThread
|
||||||
val realLuaState: LuaThread
|
|
||||||
|
|
||||||
if (pointer.address() != it) {
|
if (pointer.address() != p) {
|
||||||
realLuaState = LuaThread(LuaJNR.RUNTIME.memoryManager.newPointer(it), stringInterner = stringInterner)
|
realLuaState = LuaThread(LuaJNR.RUNTIME.memoryManager.newPointer(p), stringInterner = stringInterner)
|
||||||
realLuaState.initializeFrom(this, false)
|
realLuaState.sharedState = sharedState
|
||||||
} else {
|
} else {
|
||||||
realLuaState = this
|
realLuaState = this
|
||||||
}
|
}
|
||||||
|
|
||||||
val args = realLuaState.ArgStack(realLuaState.stackTop)
|
val args = realLuaState.ArgStack(realLuaState.stackTop)
|
||||||
val rememberStack: ArrayList<String>?
|
val rememberStack: ArrayList<String>?
|
||||||
|
|
||||||
if (performanceCritical) {
|
if (performanceCritical) {
|
||||||
rememberStack = null
|
rememberStack = null
|
||||||
} else {
|
} else {
|
||||||
rememberStack = ArrayList(Exception().stackTraceToString().split('\n'))
|
rememberStack = ArrayList(Exception().stackTraceToString().split('\n'))
|
||||||
|
|
||||||
rememberStack.removeAt(0) // java.lang. ...
|
rememberStack.removeAt(0) // java.lang. ...
|
||||||
// rememberStack.removeAt(0) // at ... push( ... )
|
// rememberStack.removeAt(0) // at ... push( ... )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val value = function.invoke(args)
|
||||||
|
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
|
||||||
|
return value
|
||||||
|
} catch (err: Throwable) {
|
||||||
try {
|
try {
|
||||||
val value = function.invoke(args)
|
if (performanceCritical) {
|
||||||
check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" }
|
realLuaState.push(err.stackTraceToString())
|
||||||
return@lazy value
|
return -1
|
||||||
} catch (err: Throwable) {
|
} else {
|
||||||
try {
|
rememberStack!!
|
||||||
if (performanceCritical) {
|
val newStack = err.stackTraceToString().split('\n').toMutableList()
|
||||||
realLuaState.push(err.stackTraceToString())
|
|
||||||
return@lazy -1
|
|
||||||
} else {
|
|
||||||
rememberStack!!
|
|
||||||
val newStack = err.stackTraceToString().split('\n').toMutableList()
|
|
||||||
|
|
||||||
val rememberIterator = rememberStack.listIterator(rememberStack.size)
|
val rememberIterator = rememberStack.listIterator(rememberStack.size)
|
||||||
val iterator = newStack.listIterator(newStack.size)
|
val iterator = newStack.listIterator(newStack.size)
|
||||||
var hit = false
|
var hit = false
|
||||||
|
|
||||||
while (rememberIterator.hasPrevious() && iterator.hasPrevious()) {
|
while (rememberIterator.hasPrevious() && iterator.hasPrevious()) {
|
||||||
val a = rememberIterator.previous()
|
val a = rememberIterator.previous()
|
||||||
val b = iterator.previous()
|
val b = iterator.previous()
|
||||||
|
|
||||||
if (a == b) {
|
if (a == b) {
|
||||||
hit = true
|
hit = true
|
||||||
iterator.remove()
|
iterator.remove()
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hit) {
|
|
||||||
newStack[newStack.size - 1] = "\t<...>"
|
|
||||||
}
|
|
||||||
|
|
||||||
realLuaState.push(newStack.joinToString("\n"))
|
|
||||||
return@lazy -1
|
|
||||||
}
|
}
|
||||||
} catch(err2: Throwable) {
|
|
||||||
realLuaState.push("JVM suffered an exception while handling earlier exception: ${err2.stackTraceToString()}; earlier: ${err.stackTraceToString()}")
|
if (hit) {
|
||||||
return@lazy -1
|
newStack[newStack.size - 1] = "\t<...>"
|
||||||
|
}
|
||||||
|
|
||||||
|
realLuaState.push(newStack.joinToString("\n"))
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
} catch(err2: Throwable) {
|
||||||
|
realLuaState.push("JVM suffered an exception while handling earlier exception: ${err2.stackTraceToString()}; earlier: ${err.stackTraceToString()}")
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun push(function: Fn, performanceCritical: Boolean) {
|
||||||
|
LuaJNI.lua_pushcclosure(pointer.address()) {
|
||||||
|
closure(it, function, performanceCritical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES)
|
||||||
|
|
||||||
fun interface Binding<T> {
|
fun <T> push(self: T, function: Binding<T>) {
|
||||||
fun invoke(self: T, arguments: ArgStack): Int
|
push {
|
||||||
|
function.invoke(self, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, A0> push(self: T, extra: A0, function: Binding1<T, A0>) {
|
||||||
|
push {
|
||||||
|
function.invoke(self, extra, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, A0, A1> push(self: T, extra: A0, extra1: A1, function: Binding2<T, A0, A1>) {
|
||||||
|
push {
|
||||||
|
function.invoke(self, extra, extra1, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> pushBinding(self: T, name: String, function: Binding<T>) {
|
||||||
|
push(name)
|
||||||
|
push { function.invoke(self, it) }
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, A0> pushBinding(self: T, extra: A0, name: String, function: Binding1<T, A0>) {
|
||||||
|
push(name)
|
||||||
|
push { function.invoke(self, extra, it) }
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, A0, A1> pushBinding(self: T, extra: A0, extra1: A1, name: String, function: Binding2<T, A0, A1>) {
|
||||||
|
push(name)
|
||||||
|
push { function.invoke(self, extra, extra1, it) }
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ensureExtraCapacity(maxSize: Int): Boolean {
|
||||||
|
return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> pushBinding(fn: Binding<T>) {
|
inline fun <reified T : Any> pushBinding(fn: Binding<T>) {
|
||||||
@ -1061,8 +1206,12 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(value: String) {
|
fun push(value: String?) {
|
||||||
pushStringIntoThread(this, value)
|
if (value == null) {
|
||||||
|
push()
|
||||||
|
} else {
|
||||||
|
pushStringIntoThread(this, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(value: LuaHandle) {
|
fun push(value: LuaHandle) {
|
||||||
@ -1074,32 +1223,16 @@ class LuaThread private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a handle for top value in stack, allowing it to be referenced anywhere in engine's code
|
* Allocates a handle for top value in stack (without popping it), allowing it to be referenced anywhere in engine's code
|
||||||
* without directly storing it anywhere in Lua's code
|
* without directly storing it anywhere in Lua's code
|
||||||
*/
|
|
||||||
fun createHandle(): LuaHandle {
|
|
||||||
push()
|
|
||||||
copy(-2, -1)
|
|
||||||
moveStackValuesOnto(handleThread.thread)
|
|
||||||
return handleThread.allocateHandle()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val namedHandles = HashMap<Any, LuaHandle>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as [createHandle], but makes it permanent (unless manually closed through [LuaHandle.close])
|
|
||||||
*
|
*
|
||||||
* Makes handle available though [pushHandle] method
|
* Optionally specified [name] will make handle available permanently (unless manually closed),
|
||||||
*
|
* and allows handle to be retrieved in future using [pushNamedHandle] and [getNamedHandle] methods
|
||||||
* This is useful for not creating cyclic references going through GC root
|
|
||||||
*/
|
*/
|
||||||
fun createHandle(key: Any): LuaHandle {
|
fun createHandle(name: Any? = null): LuaHandle {
|
||||||
require(key !in namedHandles) { "Named handle '$key' already exists" }
|
dup()
|
||||||
val handle = createHandle()
|
moveStackValuesOnto(sharedState.handlesThread)
|
||||||
namedHandles[key] = handle
|
return sharedState.allocateHandle(name)
|
||||||
// onClose should be called only on same thread as Lua's, because it is invoked only on LuaHandle#close
|
|
||||||
handle.onClose { namedHandles.remove(key) }
|
|
||||||
return handle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1107,12 +1240,23 @@ class LuaThread private constructor(
|
|||||||
*
|
*
|
||||||
* @throws NoSuchElementException if no such handle exists
|
* @throws NoSuchElementException if no such handle exists
|
||||||
*/
|
*/
|
||||||
fun pushHandle(key: Any): LuaHandle {
|
fun pushNamedHandle(key: Any): LuaHandle {
|
||||||
val handle = namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
|
val handle = getNamedHandle(key)
|
||||||
push(handle)
|
push(handle)
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws NoSuchElementException if no such handle exists
|
||||||
|
*/
|
||||||
|
fun getNamedHandle(key: Any): LuaHandle {
|
||||||
|
return sharedState.getNamedHandle(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findNamedHandle(key: Any): LuaHandle? {
|
||||||
|
return sharedState.findNamedHandle(key)
|
||||||
|
}
|
||||||
|
|
||||||
fun copy(fromIndex: Int, toIndex: Int) {
|
fun copy(fromIndex: Int, toIndex: Int) {
|
||||||
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex)
|
||||||
}
|
}
|
||||||
@ -1164,6 +1308,11 @@ class LuaThread private constructor(
|
|||||||
setTableValue(key) { throw NotImplementedError("NYI: $key") }
|
setTableValue(key) { throw NotImplementedError("NYI: $key") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Lua function is a stub")
|
||||||
|
fun setTableValueToEmpty(key: String) {
|
||||||
|
setTableValue(key) { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
fun setTableValue(key: String, value: Int) {
|
fun setTableValue(key: String, value: Int) {
|
||||||
this.push(key)
|
this.push(key)
|
||||||
this.push(value.toLong())
|
this.push(value.toLong())
|
||||||
@ -1207,6 +1356,14 @@ class LuaThread private constructor(
|
|||||||
return setTableValue(key.toLong(), value)
|
return setTableValue(key.toLong(), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setTableValue(key: Int, value: Int?) {
|
||||||
|
return setTableValue(key.toLong(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTableValue(key: Int, value: Long?) {
|
||||||
|
return setTableValue(key.toLong(), value)
|
||||||
|
}
|
||||||
|
|
||||||
fun setTableValue(key: Int, value: String) {
|
fun setTableValue(key: Int, value: String) {
|
||||||
return setTableValue(key.toLong(), value)
|
return setTableValue(key.toLong(), value)
|
||||||
}
|
}
|
||||||
@ -1229,12 +1386,24 @@ class LuaThread private constructor(
|
|||||||
return setTableValue(key, value.toLong())
|
return setTableValue(key, value.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setTableValue(key: Long, value: Int?) {
|
||||||
|
return setTableValue(key, value?.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
fun setTableValue(key: Long, value: Long) {
|
fun setTableValue(key: Long, value: Long) {
|
||||||
this.push(key)
|
this.push(key)
|
||||||
this.push(value)
|
this.push(value)
|
||||||
this.setTableValue()
|
this.setTableValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setTableValue(key: Long, value: Long?) {
|
||||||
|
if (value != null) {
|
||||||
|
this.push(key)
|
||||||
|
this.push(value)
|
||||||
|
this.setTableValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setTableValue(key: Long, value: String) {
|
fun setTableValue(key: Long, value: String) {
|
||||||
this.push(key)
|
this.push(key)
|
||||||
this.push(value)
|
this.push(value)
|
||||||
@ -1251,7 +1420,13 @@ class LuaThread private constructor(
|
|||||||
this.setTableValue()
|
this.setTableValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun push(value: JsonElement?) {
|
fun push(value: JsonElement?, recCounter: Int = 0) {
|
||||||
|
// TODO: more accurate metric?
|
||||||
|
// this need to strike balance between safety and speed
|
||||||
|
if (recCounter != 0 && recCounter and 8 == 0) {
|
||||||
|
ensureExtraCapacity(9)
|
||||||
|
}
|
||||||
|
|
||||||
when (value) {
|
when (value) {
|
||||||
null, JsonNull.INSTANCE -> {
|
null, JsonNull.INSTANCE -> {
|
||||||
this.push()
|
this.push()
|
||||||
@ -1281,7 +1456,7 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
for ((i, v) in value.withIndex()) {
|
for ((i, v) in value.withIndex()) {
|
||||||
this.push(i + 1L)
|
this.push(i + 1L)
|
||||||
this.push(v)
|
this.push(v, recCounter + 1)
|
||||||
|
|
||||||
this.setTableValue(index)
|
this.setTableValue(index)
|
||||||
}
|
}
|
||||||
@ -1295,7 +1470,7 @@ class LuaThread private constructor(
|
|||||||
|
|
||||||
for ((k, v) in value.entrySet()) {
|
for ((k, v) in value.entrySet()) {
|
||||||
this.push(k)
|
this.push(k)
|
||||||
this.push(v)
|
this.push(v, recCounter + 1)
|
||||||
|
|
||||||
this.setTableValue(index)
|
this.setTableValue(index)
|
||||||
}
|
}
|
||||||
@ -1317,6 +1492,9 @@ class LuaThread private constructor(
|
|||||||
private val globalScript by lazy { loadInternalScript("global") }
|
private val globalScript by lazy { loadInternalScript("global") }
|
||||||
private val sharedBuffers = ThreadLocal<Long>()
|
private val sharedBuffers = ThreadLocal<Long>()
|
||||||
|
|
||||||
|
private val __nils = makeNativeString("__nils")
|
||||||
|
private val __typehint = makeNativeString("__typehint")
|
||||||
|
|
||||||
private fun loadStringIntoBuffer(value: String): Long {
|
private fun loadStringIntoBuffer(value: String): Long {
|
||||||
val bytes = value.toByteArray(Charsets.UTF_8)
|
val bytes = value.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
@ -1336,9 +1514,6 @@ class LuaThread private constructor(
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
private val __nils = makeNativeString("__nils")
|
|
||||||
private val __typehint = makeNativeString("__typehint")
|
|
||||||
|
|
||||||
private val sharedStringBufferPtr: Long get() {
|
private val sharedStringBufferPtr: Long get() {
|
||||||
var p: Long? = sharedBuffers.get()
|
var p: Long? = sharedBuffers.get()
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ fun provideEntityBindings(self: AbstractEntity, lua: LuaEnvironment) {
|
|||||||
if (self is NPCEntity)
|
if (self is NPCEntity)
|
||||||
provideNPCBindings(self, lua)
|
provideNPCBindings(self, lua)
|
||||||
|
|
||||||
provideWorldBindings(self.world, lua)
|
//provideWorldBindings(self.world, lua)
|
||||||
|
|
||||||
val table = lua.newTable()
|
val table = lua.newTable()
|
||||||
lua.globals["entity"] = table
|
lua.globals["entity"] = table
|
||||||
|
@ -24,6 +24,7 @@ import ru.dbotthepony.kstarbound.lua.nextVector2i
|
|||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||||
import ru.dbotthepony.kstarbound.lua.luaStub
|
import ru.dbotthepony.kstarbound.lua.luaStub
|
||||||
|
import ru.dbotthepony.kstarbound.lua.nextInt
|
||||||
import ru.dbotthepony.kstarbound.lua.push
|
import ru.dbotthepony.kstarbound.lua.push
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
@ -36,7 +37,7 @@ private val LOGGER = LogManager.getLogger()
|
|||||||
|
|
||||||
private fun <T : Any> lookup(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T>? {
|
private fun <T : Any> lookup(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T>? {
|
||||||
return when (val type = args.peek()) {
|
return when (val type = args.peek()) {
|
||||||
LuaType.NUMBER -> registry[args.nextLong().toInt()]
|
LuaType.NUMBER -> registry[args.nextInt()]
|
||||||
LuaType.STRING -> registry[args.nextString()]
|
LuaType.STRING -> registry[args.nextString()]
|
||||||
LuaType.NONE, LuaType.NIL -> null
|
LuaType.NONE, LuaType.NIL -> null
|
||||||
else -> throw IllegalArgumentException("Invalid registry key type: $type")
|
else -> throw IllegalArgumentException("Invalid registry key type: $type")
|
||||||
@ -46,7 +47,7 @@ private fun <T : Any> lookup(registry: Registry<T>, args: LuaThread.ArgStack): R
|
|||||||
private fun <T : Any> lookupStrict(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T> {
|
private fun <T : Any> lookupStrict(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T> {
|
||||||
return when (val type = args.peek()) {
|
return when (val type = args.peek()) {
|
||||||
LuaType.NUMBER -> {
|
LuaType.NUMBER -> {
|
||||||
val key = args.nextLong().toInt()
|
val key = args.nextInt()
|
||||||
registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key")
|
registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ private fun techConfig(args: LuaThread.ArgStack): Int {
|
|||||||
private fun createBiome(args: LuaThread.ArgStack): Int {
|
private fun createBiome(args: LuaThread.ArgStack): Int {
|
||||||
val name = args.nextString()
|
val name = args.nextString()
|
||||||
val seed = args.nextLong()
|
val seed = args.nextLong()
|
||||||
val verticalMidPoint = args.nextLong().toInt()
|
val verticalMidPoint = args.nextInt()
|
||||||
val threatLevel = args.nextDouble()
|
val threatLevel = args.nextDouble()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.bindings
|
package ru.dbotthepony.kstarbound.lua.bindings
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.ByteString
|
|
||||||
import org.classdump.luna.LuaRuntimeException
|
import org.classdump.luna.LuaRuntimeException
|
||||||
import org.classdump.luna.Table
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import org.classdump.luna.runtime.AbstractFunction1
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext
|
|
||||||
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
|
||||||
import ru.dbotthepony.kstarbound.Registries
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
@ -15,19 +11,16 @@ import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
|
|||||||
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
import ru.dbotthepony.kstarbound.defs.world.BiomeDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
|
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
|
||||||
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceablesDefinition
|
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceablesDefinition
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import ru.dbotthepony.kstarbound.lua.iterator
|
import ru.dbotthepony.kstarbound.lua.LuaType
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.nextAABB
|
||||||
import ru.dbotthepony.kstarbound.lua.luaStub
|
import ru.dbotthepony.kstarbound.lua.nextAABBi
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.nextVector2d
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
import ru.dbotthepony.kstarbound.lua.nextVector2i
|
||||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
import ru.dbotthepony.kstarbound.lua.push
|
||||||
import ru.dbotthepony.kstarbound.lua.toAABBi
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||||
|
import ru.dbotthepony.kstarbound.lua.userdata.push
|
||||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||||
import ru.dbotthepony.kstarbound.util.random.random
|
import ru.dbotthepony.kstarbound.util.random.random
|
||||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||||
@ -41,59 +34,25 @@ import java.util.concurrent.TimeUnit
|
|||||||
|
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
private class LoadUniqueEntityFunction(val self: ServerWorld) : AbstractFunction1<ByteString>() {
|
private fun placeDungeonImpl(self: ServerWorld, force: Boolean, async: Boolean, args: LuaThread.ArgStack): Int {
|
||||||
override fun invoke(context: ExecutionContext, arg1: ByteString) {
|
val name = args.nextString()
|
||||||
// no, I give up, this shit is beyond bonkers
|
val pos = args.nextVector2i()
|
||||||
// I don't care that mods could break
|
val dungeonID = args.nextOptionalLong()?.toInt()
|
||||||
// mods WILL break
|
val seed = args.nextOptionalLong()
|
||||||
// just please, I beg you, for the love of God and Jesus,
|
val actualSeed = seed ?: staticRandom64(pos.x, pos.y, "DungeonPlacement")
|
||||||
// stop fucking designing async functions as sync ones
|
val dungeonDef = Registries.dungeons[name]
|
||||||
LOGGER.warn("Lua function world.loadUniqueEntity() has been called. If after this line world stop responding, this is the reason. Called in ${LuaRuntimeException("Calling world.loadUniqueEntity()").errorLocation}")
|
|
||||||
LOGGER.warn("To modders: Please refer to Lua docs for information on non-busted version of this function, world.loadUniqueEntityAsync()")
|
|
||||||
|
|
||||||
val promise = self.loadUniqueEntity(arg1.decode())
|
|
||||||
|
|
||||||
if (promise.isDone) {
|
|
||||||
context.returnBuffer.setTo(promise.get()?.entityID)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
context.pause()
|
|
||||||
} catch (err: UnresolvedControlThrowable) {
|
|
||||||
err.resolve(this, promise)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
|
||||||
val promise = suspendedState as CompletableFuture<AbstractEntity?>
|
|
||||||
|
|
||||||
if (promise.isDone) {
|
|
||||||
context.returnBuffer.setTo(promise.get()?.entityID)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
context.pause()
|
|
||||||
} catch (err: UnresolvedControlThrowable) {
|
|
||||||
err.resolve(this, promise)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ExecutionContext.placeDungeonImpl(self: ServerWorld, force: Boolean, async: Boolean, name: ByteString, position: Table, dungeonID: Number?, seed: Number?) {
|
|
||||||
val pos = toVector2i(position)
|
|
||||||
val actualSeed = seed?.toLong() ?: staticRandom64(pos.x, pos.y, "DungeonPlacement")
|
|
||||||
val dungeonDef = Registries.dungeons[name.decode()]
|
|
||||||
|
|
||||||
if (dungeonDef == null) {
|
if (dungeonDef == null) {
|
||||||
LOGGER.error("Lua script tried to spawn dungeon $name, however, no such dungeon exist")
|
LOGGER.error("Lua script tried to spawn dungeon $name, however, no such dungeon exist")
|
||||||
|
|
||||||
if (async) {
|
if (async) {
|
||||||
returnBuffer.setTo(LuaFuture(
|
args.lua.push(LuaFuture(
|
||||||
future = CompletableFuture.failedFuture(NoSuchElementException("No such dungeon: $name")),
|
future = CompletableFuture.failedFuture(NoSuchElementException("No such dungeon: $name")),
|
||||||
isLocal = false
|
isLocal = false,
|
||||||
|
handler = { 0 }
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
returnBuffer.setTo(false)
|
args.lua.push(false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val future = dungeonDef.value.generate(
|
val future = dungeonDef.value.generate(
|
||||||
@ -109,232 +68,329 @@ private fun ExecutionContext.placeDungeonImpl(self: ServerWorld, force: Boolean,
|
|||||||
scope = if (async) Starbound.GLOBAL_SCOPE else self.eventLoop.scope).thenApply { it.hasGenerated }
|
scope = if (async) Starbound.GLOBAL_SCOPE else self.eventLoop.scope).thenApply { it.hasGenerated }
|
||||||
|
|
||||||
if (async) {
|
if (async) {
|
||||||
returnBuffer.setTo(LuaFuture(
|
args.lua.push(LuaFuture(
|
||||||
future = future,
|
future = future,
|
||||||
isLocal = false
|
isLocal = false,
|
||||||
|
handler = { push(it); 1 }
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
returnBuffer.setTo(future.getNow(true))
|
args.lua.push(future.getNow(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvironment) {
|
private fun breakObject(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
callbacks["breakObject"] = luaFunction { id: Number, smash: Boolean? ->
|
val id = args.nextInt()
|
||||||
val entity = self.entities[id.toInt()] as? WorldObject ?: return@luaFunction returnBuffer.setTo(false)
|
val entity = self.entities[id] as? WorldObject
|
||||||
|
val smash = args.nextOptionalBoolean()
|
||||||
|
|
||||||
if (entity.isRemote) {
|
if (entity == null) {
|
||||||
// we can't break objects now owned by us
|
args.lua.push(false)
|
||||||
returnBuffer.setTo(false)
|
} else if (entity.isRemote) {
|
||||||
} else {
|
// we can't break objects now owned by us
|
||||||
if (smash == true)
|
args.lua.push(false)
|
||||||
entity.health = 0.0
|
} else {
|
||||||
|
if (smash == true)
|
||||||
|
entity.health = 0.0
|
||||||
|
|
||||||
entity.remove(AbstractEntity.RemovalReason.DYING)
|
entity.remove(AbstractEntity.RemovalReason.DYING)
|
||||||
returnBuffer.setTo(true)
|
args.lua.push(true)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["isVisibleToPlayer"] = luaFunction { region: Table ->
|
return 1
|
||||||
val parse = toAABB(region)
|
}
|
||||||
returnBuffer.setTo(self.clients.any { it.isTracking(parse) })
|
|
||||||
|
private fun isVisibleToPlayer(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val region = args.nextAABB()
|
||||||
|
args.lua.push(self.clients.any { it.isTracking(region) })
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadRegion(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
// FIXME: in original engine this supposedly blocks everything else from happening until region is loaded
|
||||||
|
// And we can't block event loop, because this will make region be unable to be loaded in first place
|
||||||
|
val region = args.nextAABB()
|
||||||
|
|
||||||
|
// keep in ram for at most 2400 ticks
|
||||||
|
val tickets = self.temporaryChunkTicket(region, 2400).get()
|
||||||
|
val future = CompletableFuture.allOf(*tickets.map { it.chunk }.toTypedArray())
|
||||||
|
|
||||||
|
future.thenApply {
|
||||||
|
self.eventLoop.schedule(Runnable {
|
||||||
|
tickets.forEach { it.cancel() }
|
||||||
|
}, 4L, TimeUnit.SECONDS)
|
||||||
|
}.exceptionally {
|
||||||
|
self.eventLoop.schedule(Runnable {
|
||||||
|
tickets.forEach { it.cancel() }
|
||||||
|
}, 4L, TimeUnit.SECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["loadRegion"] = luaFunction { region: Table ->
|
args.lua.push(LuaFuture(
|
||||||
// FIXME: in original engine this supposedly blocks everything else from happening until region is loaded
|
future = future,
|
||||||
// And we can't block event loop, because this will make region be unable to be loaded in first place
|
isLocal = false,
|
||||||
// god damn it
|
handler = { 0 }
|
||||||
val parse = toAABB(region)
|
))
|
||||||
// keep in ram for at most 2400 ticks
|
|
||||||
val tickets = self.temporaryChunkTicket(parse, 2400).get()
|
|
||||||
val future = CompletableFuture.allOf(*tickets.map { it.chunk }.toTypedArray())
|
|
||||||
|
|
||||||
future.thenApply {
|
return 1
|
||||||
self.eventLoop.schedule(Runnable {
|
}
|
||||||
tickets.forEach { it.cancel() }
|
|
||||||
}, 4L, TimeUnit.SECONDS)
|
|
||||||
}.exceptionally {
|
|
||||||
self.eventLoop.schedule(Runnable {
|
|
||||||
tickets.forEach { it.cancel() }
|
|
||||||
}, 4L, TimeUnit.SECONDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
returnBuffer.setTo(LuaFuture(
|
private fun regionActive(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
future = future,
|
val chunks = self.geometry.region2Chunks(args.nextAABB())
|
||||||
isLocal = false
|
// TODO: i have no idea what mods expect this to return, so i'll have to guess
|
||||||
))
|
args.lua.push(chunks.all { self.chunkMap[it]?.state == ChunkState.FULL })
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTileProtection(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
self.switchDungeonIDProtection(args.nextInt(), args.nextBoolean())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPlayerModified(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(self.isPlayerModified(args.nextAABBi()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forceDestroyLiquid(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val position = args.nextVector2i()
|
||||||
|
val cell = self.getCell(position).mutable()
|
||||||
|
|
||||||
|
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > 0f) {
|
||||||
|
args.lua.push(cell.liquid.level.toDouble())
|
||||||
|
|
||||||
|
cell.liquid.reset()
|
||||||
|
self.setCell(position, cell.immutable())
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["regionActive"] = luaFunction { region: Table ->
|
return 0
|
||||||
val chunks = self.geometry.region2Chunks(toAABB(region))
|
}
|
||||||
// TODO: i have no idea what mods expect this to return, so i'll have to guess
|
|
||||||
returnBuffer.setTo(chunks.all { self.chunkMap[it]?.state == ChunkState.FULL })
|
private fun loadUniqueEntityAsync(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(
|
||||||
|
LuaFuture(
|
||||||
|
future = self.loadUniqueEntity(args.nextString()).thenApply { KOptional.ofNullable(it) },
|
||||||
|
isLocal = false,
|
||||||
|
handler = {
|
||||||
|
push(it.orNull()?.entityID?.toLong())
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUniqueId(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val id = args.nextInt()
|
||||||
|
val name = args.nextString()
|
||||||
|
val entity = self.entities[id] ?: throw IllegalStateException("No such entity with ID $id (tried to set unique id to $name)")
|
||||||
|
|
||||||
|
if (entity.isRemote)
|
||||||
|
throw IllegalStateException("Entity $entity is not owned by this side")
|
||||||
|
|
||||||
|
if (
|
||||||
|
entity.type == EntityType.NPC ||
|
||||||
|
entity.type == EntityType.STAGEHAND ||
|
||||||
|
entity.type == EntityType.MONSTER ||
|
||||||
|
entity.type == EntityType.OBJECT
|
||||||
|
) {
|
||||||
|
entity.uniqueID.accept(name.sbIntern())
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Entity type is restricted from having unique ID: $entity (tried to set unique id to $name)")
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["setTileProtection"] = luaFunction { id: Number, enable: Boolean ->
|
return 0
|
||||||
self.switchDungeonIDProtection(id.toInt(), enable)
|
}
|
||||||
|
|
||||||
|
private fun takeItemDrop(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val id = args.nextInt()
|
||||||
|
val entity = self.entities[id] as? ItemDropEntity ?: return 0
|
||||||
|
val takenBy = args.nextOptionalLong()?.toInt()
|
||||||
|
|
||||||
|
if (entity.isLocal && entity.canTake) {
|
||||||
|
entity.take(takenBy ?: 0).createDescriptor().store(args.lua)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["isPlayerModified"] = luaFunction { region: Table ->
|
return 0
|
||||||
returnBuffer.setTo(self.isPlayerModified(toAABBi(region)))
|
}
|
||||||
|
|
||||||
|
private fun setPlayerStart(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
self.setPlayerSpawn(args.nextVector2d(), args.nextOptionalBoolean() == true)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun players(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.pushTable(self.clients.size)
|
||||||
|
var i = 1L
|
||||||
|
|
||||||
|
for (p in self.clients) {
|
||||||
|
val entityID = p.client.playerEntity?.entityID
|
||||||
|
|
||||||
|
if (entityID != null)
|
||||||
|
args.lua.setTableValue(i++, entityID)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["forceDestroyLiquid"] = luaFunction { position: Table ->
|
return 1
|
||||||
val parse = toVector2i(position)
|
}
|
||||||
val cell = self.getCell(parse).mutable()
|
|
||||||
|
|
||||||
if (cell.liquid.state.isNotEmptyLiquid && cell.liquid.level > 0f) {
|
private fun setSkyTime(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
returnBuffer.setTo(cell.liquid.level.toDouble())
|
val time = args.nextDouble()
|
||||||
|
|
||||||
cell.liquid.reset()
|
if (time < 0.0)
|
||||||
self.setCell(parse, cell.immutable())
|
throw LuaRuntimeException("Negative time? $time")
|
||||||
}
|
|
||||||
|
self.sky.time = time
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUniverseFlag(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(self.server.addUniverseFlag(args.nextString().sbIntern()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unsetUniverseFlag(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(self.server.removeUniverseFlag(args.nextString()))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun universeFlags(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val flags = self.server.getUniverseFlags()
|
||||||
|
|
||||||
|
args.lua.pushTable(flags.size)
|
||||||
|
|
||||||
|
for ((i, flag) in flags.withIndex()) {
|
||||||
|
args.lua.setTableValue(i + 1L, flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["loadUniqueEntity"] = LoadUniqueEntityFunction(self)
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
callbacks["loadUniqueEntityAsync"] = luaFunction { name: ByteString ->
|
private fun universeFlagSet(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
returnBuffer.setTo(LuaFuture(
|
args.lua.push(self.server.hasUniverseFlag(args.nextString()))
|
||||||
future = self.loadUniqueEntity(name.decode()),
|
return 1
|
||||||
isLocal = false
|
}
|
||||||
))
|
|
||||||
|
private fun placeDungeon(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
return placeDungeonImpl(self, force = true, async = false, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryPlaceDungeon(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
return placeDungeonImpl(self, force = false, async = false, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun placeDungeonAsync(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
return placeDungeonImpl(self, force = true, async = true, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tryPlaceDungeonAsync(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
return placeDungeonImpl(self, force = false, async = true, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDungeonGravity(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
|
val id = args.nextInt()
|
||||||
|
val peek = args.peek()
|
||||||
|
|
||||||
|
if (peek.isNothing) {
|
||||||
|
self.setDungeonGravity(id, null)
|
||||||
|
} else if (peek == LuaType.NUMBER) {
|
||||||
|
self.setDungeonGravity(id, args.nextDouble())
|
||||||
|
} else if (peek == LuaType.TABLE) {
|
||||||
|
self.setDungeonGravity(id, args.nextVector2d())
|
||||||
|
} else {
|
||||||
|
throw LuaRuntimeException("Illegal gravity argument to setDungeonGravity: $peek")
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["setUniqueId"] = luaFunction { id: Number, name: ByteString ->
|
return 0
|
||||||
val entity = self.entities[id.toInt()] ?: throw LuaRuntimeException("No such entity with ID $id (tried to set unique id to $name)")
|
}
|
||||||
|
|
||||||
if (entity.isRemote)
|
private fun setDungeonBreathable(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
throw LuaRuntimeException("Entity $entity is not owned by this side")
|
self.setDungeonBreathable(args.nextInt(), args.nextOptionalBoolean())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
private fun setDungeonId(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
entity.type == EntityType.NPC ||
|
val region = args.nextAABBi()
|
||||||
entity.type == EntityType.STAGEHAND ||
|
val id = args.nextInt()
|
||||||
entity.type == EntityType.MONSTER ||
|
|
||||||
entity.type == EntityType.OBJECT
|
|
||||||
) {
|
|
||||||
entity.uniqueID.accept(name.decode().sbIntern())
|
|
||||||
} else {
|
|
||||||
throw LuaRuntimeException("Entity type is restricted from having unique ID: $entity (tried to set unique id to $name)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["takeItemDrop"] = luaFunction { id: Number, takenBy: Number? ->
|
for (x in region.mins.x .. region.maxs.x) {
|
||||||
val entity = self.entities[id.toInt()] as? ItemDropEntity ?: return@luaFunction returnBuffer.setTo()
|
for (y in region.mins.y .. region.maxs.y) {
|
||||||
|
val cell = self.getCell(x, y)
|
||||||
|
|
||||||
if (!entity.isRemote && entity.canTake) {
|
if (cell.dungeonId != id) {
|
||||||
returnBuffer.setTo(entity.take(takenBy?.toInt() ?: 0).toTable(this))
|
self.setCell(x, y, cell.mutable().also { it.dungeonId = id })
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setPlayerStart"] = luaFunction { position: Table, respawnInWorld: Boolean? ->
|
|
||||||
self.setPlayerSpawn(toVector2d(position), respawnInWorld == true)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["players"] = luaFunction {
|
|
||||||
returnBuffer.setTo(tableOf(*self.clients.map { it.client.playerID }.toTypedArray()))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["fidelity"] = luaFunction {
|
|
||||||
returnBuffer.setTo("high".toByteString())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setSkyTime"] = luaFunction { newTime: Number ->
|
|
||||||
val cast = newTime.toDouble()
|
|
||||||
|
|
||||||
if (cast < 0.0)
|
|
||||||
throw LuaRuntimeException("Negative time? $cast")
|
|
||||||
|
|
||||||
self.sky.time = cast
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setUniverseFlag"] = luaFunction { flag: ByteString ->
|
|
||||||
returnBuffer.setTo(self.server.addUniverseFlag(flag.decode().sbIntern()))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["unsetUniverseFlag"] = luaFunction { flag: ByteString ->
|
|
||||||
returnBuffer.setTo(self.server.removeUniverseFlag(flag.decode()))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["universeFlags"] = luaFunction {
|
|
||||||
returnBuffer.setTo(tableOf(*self.server.getUniverseFlags().toTypedArray()))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["universeFlagSet"] = luaFunction { flag: ByteString ->
|
|
||||||
returnBuffer.setTo(self.server.hasUniverseFlag(flag.decode()))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeDungeon"] = luaFunction { name: ByteString, position: Table, dungeonID: Number?, seed: Number? ->
|
|
||||||
placeDungeonImpl(self, true, false, name, position, dungeonID, seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["tryPlaceDungeon"] = luaFunction { name: ByteString, position: Table, dungeonID: Number?, seed: Number? ->
|
|
||||||
placeDungeonImpl(self, false, false, name, position, dungeonID, seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeDungeonAsync"] = luaFunction { name: ByteString, position: Table, dungeonID: Number?, seed: Number? ->
|
|
||||||
placeDungeonImpl(self, true, true, name, position, dungeonID, seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["tryPlaceDungeonAsync"] = luaFunction { name: ByteString, position: Table, dungeonID: Number?, seed: Number? ->
|
|
||||||
placeDungeonImpl(self, false, true, name, position, dungeonID, seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// terraforming
|
|
||||||
callbacks["addBiomeRegion"] = luaStub("addBiomeRegion")
|
|
||||||
callbacks["expandBiomeRegion"] = luaStub("expandBiomeRegion")
|
|
||||||
callbacks["pregenerateAddBiome"] = luaStub("pregenerateAddBiome")
|
|
||||||
callbacks["pregenerateExpandBiome"] = luaStub("pregenerateExpandBiome")
|
|
||||||
callbacks["setLayerEnvironmentBiome"] = luaStub("setLayerEnvironmentBiome")
|
|
||||||
callbacks["setPlanetType"] = luaStub("setPlanetType")
|
|
||||||
|
|
||||||
callbacks["setDungeonGravity"] = luaFunction { id: Number, gravity: Any? ->
|
|
||||||
if (gravity == null) {
|
|
||||||
self.setDungeonGravity(id.toInt(), null)
|
|
||||||
} else if (gravity is Table) {
|
|
||||||
self.setDungeonGravity(id.toInt(), toVector2d(gravity))
|
|
||||||
} else if (gravity is Number) {
|
|
||||||
self.setDungeonGravity(id.toInt(), gravity.toDouble())
|
|
||||||
} else {
|
|
||||||
throw LuaRuntimeException("Illegal argument to setDungeonGravity: $gravity")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setDungeonBreathable"] = luaFunction { id: Number, breathable: Boolean? ->
|
|
||||||
self.setDungeonBreathable(id.toInt(), breathable)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setDungeonId"] = luaFunction { region: Table, id: Number ->
|
|
||||||
val parse = toAABBi(region)
|
|
||||||
val actualId = id.toInt()
|
|
||||||
|
|
||||||
for (x in parse.mins.x .. parse.maxs.x) {
|
|
||||||
for (y in parse.mins.y .. parse.maxs.y) {
|
|
||||||
val cell = self.getCell(x, y)
|
|
||||||
|
|
||||||
if (cell.dungeonId != actualId) {
|
|
||||||
self.setCell(x, y, cell.mutable().also { it.dungeonId = actualId })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callbacks["enqueuePlacement"] = luaFunction { distributions: Table, dungeonId: Number? ->
|
return 0
|
||||||
val items = ArrayList<BiomePlaceables.DistributionItem>()
|
}
|
||||||
|
|
||||||
for ((_, v) in distributions) {
|
private fun enqueuePlacement(self: ServerWorld, args: LuaThread.ArgStack): Int {
|
||||||
// original engine treats distributions table as if it was originating from biome json files
|
val distributions = args.nextJsonArray()
|
||||||
val unprepared = Starbound.gson.fromJson(toJsonFromLua(v), BiomePlaceablesDefinition.DistributionItem::class.java)
|
val dungeonId = args.nextOptionalInt()
|
||||||
val prepared = unprepared.create(BiomeDefinition.CreationParams(hueShift = 0.0, random = lua.random))
|
val items = ArrayList<BiomePlaceables.DistributionItem>()
|
||||||
items.add(prepared)
|
|
||||||
}
|
for (v in distributions) {
|
||||||
|
// original engine treats distributions table as if it was originating from biome json files
|
||||||
// TODO: Enqueued placements are lost on world shutdown.
|
val unprepared = Starbound.gson.fromJson(toJsonFromLua(v), BiomePlaceablesDefinition.DistributionItem::class.java)
|
||||||
// Shouldn't we serialize them to persistent storage?
|
val prepared = unprepared.create(BiomeDefinition.CreationParams(hueShift = 0.0, random = args.lua.random))
|
||||||
returnBuffer.setTo(
|
items.add(prepared)
|
||||||
LuaFuture(
|
}
|
||||||
future = self.enqueuePlacement(items, dungeonId?.toInt()),
|
|
||||||
isLocal = false
|
// TODO: Enqueued placements are lost on world shutdown.
|
||||||
)
|
// Shouldn't we serialize them to persistent storage?
|
||||||
)
|
args.lua.push(
|
||||||
}
|
LuaFuture(
|
||||||
|
future = self.enqueuePlacement(items, dungeonId),
|
||||||
|
isLocal = false,
|
||||||
|
handler = { push(it); 1 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private val script by lazy { LuaThread.loadInternalScript("server_world") }
|
||||||
|
|
||||||
|
fun provideServerWorldBindings(self: ServerWorld, lua: LuaThread) {
|
||||||
|
lua.pushBinding(self, "breakObject", ::breakObject)
|
||||||
|
lua.pushBinding(self, "isVisibleToPlayer", ::isVisibleToPlayer)
|
||||||
|
lua.pushBinding(self, "loadRegion", ::loadRegion)
|
||||||
|
lua.pushBinding(self, "regionActive", ::regionActive)
|
||||||
|
lua.pushBinding(self, "setTileProtection", ::setTileProtection)
|
||||||
|
lua.pushBinding(self, "isPlayerModified", ::isPlayerModified)
|
||||||
|
lua.pushBinding(self, "forceDestroyLiquid", ::forceDestroyLiquid)
|
||||||
|
lua.pushBinding(self, "loadUniqueEntityAsync", ::loadUniqueEntityAsync)
|
||||||
|
lua.pushBinding(self, "setUniqueId", ::setUniqueId)
|
||||||
|
lua.pushBinding(self, "takeItemDrop", ::takeItemDrop)
|
||||||
|
lua.pushBinding(self, "setPlayerStart", ::setPlayerStart)
|
||||||
|
lua.pushBinding(self, "players", ::players)
|
||||||
|
lua.pushBinding(self, "setSkyTime", ::setSkyTime)
|
||||||
|
lua.pushBinding(self, "setUniverseFlag", ::setUniverseFlag)
|
||||||
|
lua.pushBinding(self, "unsetUniverseFlag", ::unsetUniverseFlag)
|
||||||
|
lua.pushBinding(self, "universeFlags", ::universeFlags)
|
||||||
|
lua.pushBinding(self, "universeFlagSet", ::universeFlagSet)
|
||||||
|
lua.pushBinding(self, "placeDungeon", ::placeDungeon)
|
||||||
|
lua.pushBinding(self, "tryPlaceDungeon", ::tryPlaceDungeon)
|
||||||
|
lua.pushBinding(self, "placeDungeonAsync", ::placeDungeonAsync)
|
||||||
|
lua.pushBinding(self, "tryPlaceDungeonAsync", ::tryPlaceDungeonAsync)
|
||||||
|
lua.pushBinding(self, "setDungeonGravity", ::setDungeonGravity)
|
||||||
|
lua.pushBinding(self, "setDungeonBreathable", ::setDungeonBreathable)
|
||||||
|
lua.pushBinding(self, "setDungeonId", ::setDungeonId)
|
||||||
|
lua.pushBinding(self, "enqueuePlacement", ::enqueuePlacement)
|
||||||
|
|
||||||
|
// terraforming
|
||||||
|
lua.setTableValueToStub("addBiomeRegion")
|
||||||
|
lua.setTableValueToStub("expandBiomeRegion")
|
||||||
|
lua.setTableValueToStub("pregenerateAddBiome")
|
||||||
|
lua.setTableValueToStub("pregenerateExpandBiome")
|
||||||
|
lua.setTableValueToStub("setLayerEnvironmentBiome")
|
||||||
|
lua.setTableValueToStub("setPlanetType")
|
||||||
|
|
||||||
|
lua.load(script, "@/internal/server_world.lua")
|
||||||
|
lua.call()
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,26 @@ fun provideUtilityBindings(lua: LuaThread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeGlobal("__random_seed")
|
storeGlobal("__random_seed")
|
||||||
|
|
||||||
|
push {
|
||||||
|
push(it.lua.getNamedHandle(it.nextString()))
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
storeGlobal("gethandle")
|
||||||
|
|
||||||
|
push {
|
||||||
|
val find = it.lua.findNamedHandle(it.nextString())
|
||||||
|
|
||||||
|
if (find == null) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
push(find)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storeGlobal("findhandle")
|
||||||
}
|
}
|
||||||
|
|
||||||
lua.pushTable()
|
lua.pushTable()
|
||||||
@ -250,7 +270,7 @@ fun provideUtilityBindings(lua: LuaThread) {
|
|||||||
lua.setTableValue("staticRandomDoubleRange", ::staticRandomDoubleRange)
|
lua.setTableValue("staticRandomDoubleRange", ::staticRandomDoubleRange)
|
||||||
|
|
||||||
lua.pushTable()
|
lua.pushTable()
|
||||||
val randomMeta = lua.createHandle()
|
val randomMeta = lua.createHandle("RandomGenerator")
|
||||||
|
|
||||||
lua.pushBinding("init", LuaRandom::init)
|
lua.pushBinding("init", LuaRandom::init)
|
||||||
lua.pushBinding("addEntropy", LuaRandom::addEntropy)
|
lua.pushBinding("addEntropy", LuaRandom::addEntropy)
|
||||||
@ -273,7 +293,7 @@ fun provideUtilityBindings(lua: LuaThread) {
|
|||||||
val seed = args.nextOptionalLong() ?: args.lua.random.nextLong()
|
val seed = args.nextOptionalLong() ?: args.lua.random.nextLong()
|
||||||
lua.pushTable()
|
lua.pushTable()
|
||||||
lua.push("__index")
|
lua.push("__index")
|
||||||
lua.push(randomMeta) // cyclic reference through GC root
|
lua.push(randomMeta)
|
||||||
lua.setTableValue()
|
lua.setTableValue()
|
||||||
lua.pushObject(LuaRandom(random(seed)))
|
lua.pushObject(LuaRandom(random(seed)))
|
||||||
1
|
1
|
||||||
@ -282,7 +302,7 @@ fun provideUtilityBindings(lua: LuaThread) {
|
|||||||
lua.setTableValue()
|
lua.setTableValue()
|
||||||
|
|
||||||
lua.pushTable()
|
lua.pushTable()
|
||||||
val noiseMeta = lua.createHandle()
|
val noiseMeta = lua.createHandle("PerlinSource")
|
||||||
|
|
||||||
lua.pushBinding("get", LuaPerlinNoise::get)
|
lua.pushBinding("get", LuaPerlinNoise::get)
|
||||||
lua.pushBinding("seed", LuaPerlinNoise::seed)
|
lua.pushBinding("seed", LuaPerlinNoise::seed)
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,36 +1,20 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.bindings
|
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.lib.ArgumentIterator
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext
|
|
||||||
import ru.dbotthepony.kommons.collect.map
|
|
||||||
import ru.dbotthepony.kommons.collect.toList
|
|
||||||
import ru.dbotthepony.kstarbound.Registries
|
import ru.dbotthepony.kstarbound.Registries
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageResult
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||||
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.isNotEmptyModifier
|
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
|
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.getVector2i
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
import ru.dbotthepony.kstarbound.lua.nextVector2d
|
||||||
import ru.dbotthepony.kstarbound.lua.iterator
|
import ru.dbotthepony.kstarbound.lua.nextVector2i
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
import ru.dbotthepony.kstarbound.lua.push
|
||||||
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.tableFrom
|
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
|
||||||
|
import ru.dbotthepony.kstarbound.lua.userdata.push
|
||||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||||
import ru.dbotthepony.kstarbound.util.valueOf
|
import ru.dbotthepony.kstarbound.util.valueOf
|
||||||
import ru.dbotthepony.kstarbound.world.TileModification
|
import ru.dbotthepony.kstarbound.world.TileModification
|
||||||
@ -39,51 +23,39 @@ import ru.dbotthepony.kstarbound.world.api.TileColor
|
|||||||
import ru.dbotthepony.kstarbound.world.tileAreaBrush
|
import ru.dbotthepony.kstarbound.world.tileAreaBrush
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
private val foregroundStr = ByteString.of("foreground")
|
private fun damageTilesImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<TileDamageResult> {
|
||||||
private val backgroundStr = ByteString.of("background")
|
val positions = args.readTableValues { getVector2i() ?: throw IllegalArgumentException("Positions table contains invalid positions") }
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
|
||||||
private fun isBackground(layer: ByteString): Boolean {
|
val sourcePosition = args.nextVector2d()
|
||||||
return if (layer == backgroundStr)
|
val damageType = TileDamageType.entries.valueOf(args.nextString())
|
||||||
true
|
val damage = args.nextDouble()
|
||||||
else if (layer == foregroundStr)
|
val harvestLevel = args.nextOptionalLong()?.toInt() ?: 999
|
||||||
false
|
val sourceEntity = self.entities[args.nextOptionalLong()?.toInt() ?: 0]
|
||||||
else
|
|
||||||
throw LuaRuntimeException("Invalid tile layer $layer")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ExecutionContext.damageTilesImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<TileDamageResult> {
|
|
||||||
val positions = it.nextTable().iterator().map { toVector2i(it.value) }.toList()
|
|
||||||
val isBackground = isBackground(it.nextString())
|
|
||||||
|
|
||||||
val sourcePosition = toVector2d(it.nextTable())
|
|
||||||
val damageType = TileDamageType.entries.valueOf(it.nextString().decode())
|
|
||||||
val damage = it.nextFloat()
|
|
||||||
val harvestLevel = it.nextOptionalInteger()?.toInt() ?: 999
|
|
||||||
val sourceEntity = self.entities[it.nextOptionalInteger()?.toInt() ?: 0]
|
|
||||||
|
|
||||||
return self.damageTiles(positions, isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
return self.damageTiles(positions, isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ExecutionContext.damageTileAreaImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<TileDamageResult> {
|
private fun damageTileAreaImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<TileDamageResult> {
|
||||||
val center = toVector2i(it.nextTable())
|
val center = args.nextVector2i()
|
||||||
val radius = it.nextFloat()
|
val radius = args.nextDouble()
|
||||||
val isBackground = isBackground(it.nextString())
|
val isBackground = args.nextBoolean()
|
||||||
|
|
||||||
val sourcePosition = toVector2d(it.nextTable())
|
val sourcePosition = args.nextVector2d()
|
||||||
val damageType = TileDamageType.entries.valueOf(it.nextString().decode())
|
val damageType = TileDamageType.entries.valueOf(args.nextString())
|
||||||
val damage = it.nextFloat()
|
val damage = args.nextDouble()
|
||||||
val harvestLevel = it.nextOptionalInteger()?.toInt() ?: 999
|
val harvestLevel = args.nextOptionalLong()?.toInt() ?: 999
|
||||||
val sourceEntity = self.entities[it.nextOptionalInteger()?.toInt() ?: 0]
|
val sourceEntity = self.entities[args.nextOptionalLong()?.toInt() ?: 0]
|
||||||
|
|
||||||
return self.damageTiles(tileAreaBrush(center, radius), isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
return self.damageTiles(tileAreaBrush(center, radius), isBackground, sourcePosition, TileDamage(damageType, damage, harvestLevel), sourceEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ExecutionContext.placeMaterialImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<List<Vector2i>> {
|
private fun placeMaterialImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<List<Vector2i>> {
|
||||||
val pos = toVector2i(it.nextTable())
|
val pos = args.nextVector2i()
|
||||||
val isBackground = isBackground(it.nextString())
|
val isBackground = args.nextBoolean()
|
||||||
val material = Registries.tiles.getOrThrow(it.nextString().decode())
|
val material = Registries.tiles.getOrThrow(args.nextString())
|
||||||
val hueShift: Float? = (it.nextAny() as? Number)?.let { it.toFloat() / 255f * 360f }
|
val hueShift: Float? = args.nextOptionalDouble()?.let { it.toFloat() / 255f * 360f }
|
||||||
val allowOverlap = it.nextBoolean()
|
val allowOverlap = args.nextBoolean()
|
||||||
|
|
||||||
val action = TileModification.PlaceMaterial(isBackground, material.ref, hueShift)
|
val action = TileModification.PlaceMaterial(isBackground, material.ref, hueShift)
|
||||||
|
|
||||||
@ -91,12 +63,12 @@ private fun ExecutionContext.placeMaterialImpl(self: World<*, *>, it: ArgumentIt
|
|||||||
return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } }
|
return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ExecutionContext.placeModImpl(self: World<*, *>, it: ArgumentIterator): CompletableFuture<List<Vector2i>> {
|
private fun placeModImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<List<Vector2i>> {
|
||||||
val pos = toVector2i(it.nextTable())
|
val pos = args.nextVector2i()
|
||||||
val isBackground = isBackground(it.nextString())
|
val isBackground = args.nextBoolean()
|
||||||
val material = Registries.tileModifiers.getOrThrow(it.nextString().decode())
|
val material = Registries.tileModifiers.getOrThrow(args.nextString())
|
||||||
val hueShift: Float? = (it.nextAny() as? Number)?.let { it.toFloat() / 255f * 360f }
|
val hueShift: Float? = args.nextOptionalDouble()?.let { it.toFloat() / 255f * 360f }
|
||||||
val allowOverlap = it.nextBoolean()
|
val allowOverlap = args.nextBoolean()
|
||||||
|
|
||||||
val action = TileModification.PlaceModifier(isBackground, material.ref, hueShift)
|
val action = TileModification.PlaceModifier(isBackground, material.ref, hueShift)
|
||||||
|
|
||||||
@ -104,137 +76,261 @@ private fun ExecutionContext.placeModImpl(self: World<*, *>, it: ArgumentIterato
|
|||||||
return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } }
|
return self.applyTileModifications(listOf(pos to action), allowOverlap, false).thenApply { it.map { it.first } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun provideWorldEnvironmentalBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) {
|
private fun breathable(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
callbacks["lightLevel"] = luaStub("lightLevel")
|
args.lua.push(self.chunkMap.isBreathable(args.nextVector2d()))
|
||||||
callbacks["windLevel"] = luaStub("windLevel")
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
callbacks["breathable"] = luaFunction { pos: Table ->
|
private fun underground(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
returnBuffer.setTo(self.chunkMap.isBreathable(toVector2i(pos)))
|
args.lua.push(self.template.undergroundLevel >= args.nextVector2d().y)
|
||||||
}
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
callbacks["underground"] = luaFunction { pos: Table ->
|
private fun material(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
returnBuffer.setTo(self.template.undergroundLevel >= toVector2d(pos).y)
|
val pos = args.nextVector2i()
|
||||||
}
|
val isBackground = args.nextBoolean()
|
||||||
|
|
||||||
callbacks["material"] = luaFunction { pos: Table, layer: ByteString ->
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
|
|
||||||
if (tile.material.isNullTile) {
|
if (tile.material.isNullTile) {
|
||||||
returnBuffer.setTo()
|
return 0
|
||||||
} else if (tile.material.isEmptyTile) {
|
} else if (tile.material.isEmptyTile) {
|
||||||
returnBuffer.setTo(false)
|
args.lua.push(false)
|
||||||
} else {
|
return 1
|
||||||
returnBuffer.setTo(tile.material.key.toByteString())
|
} else {
|
||||||
}
|
args.lua.push(tile.material.key)
|
||||||
|
return 1
|
||||||
}
|
|
||||||
callbacks["mod"] = luaFunction { pos: Table, layer: ByteString ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
|
|
||||||
if (tile.modifier.isNotEmptyModifier) {
|
|
||||||
returnBuffer.setTo(tile.modifier.key.toByteString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["materialHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
returnBuffer.setTo(tile.hueShift.toDouble())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["modHueShift"] = luaFunction { pos: Table, layer: ByteString ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
returnBuffer.setTo(tile.modifierHueShift.toDouble())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["materialColor"] = luaFunction { pos: Table, layer: ByteString ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
returnBuffer.setTo(tile.color.ordinal.toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["materialColorName"] = luaFunction { pos: Table, layer: ByteString ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
val tile = self.getCell(toVector2i(pos)).tile(isBackground)
|
|
||||||
returnBuffer.setTo(tile.color.jsonName.toByteString())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["setMaterialColor"] = luaFunction { pos: Table, layer: ByteString, color: Any ->
|
|
||||||
val isBackground = isBackground(layer)
|
|
||||||
|
|
||||||
val actualColor = if (color is Number)
|
|
||||||
TileColor.entries[color.toInt()]
|
|
||||||
else if (color is ByteString)
|
|
||||||
TileColor.entries.valueOf(color.decode())
|
|
||||||
else
|
|
||||||
throw LuaRuntimeException("Unknown tile color $color")
|
|
||||||
|
|
||||||
val actualPos = toVector2i(pos)
|
|
||||||
val cell = self.getCell(actualPos).mutable()
|
|
||||||
cell.tile(isBackground).color = actualColor
|
|
||||||
returnBuffer.setTo(self.setCell(actualPos, cell))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["oceanLevel"] = luaFunction { pos: Table ->
|
|
||||||
returnBuffer.setTo(self.template.cellInfo(toVector2i(pos)).oceanLiquidLevel.toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["environmentStatusEffects"] = luaFunction { pos: Table ->
|
|
||||||
returnBuffer.setTo(tableFrom(self.environmentStatusEffects(toVector2i(pos)).map { from(Starbound.gson.toJsonTree(it)) }))
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["damageTiles"] = luaFunctionN("damageTiles") {
|
|
||||||
returnBuffer.setTo(damageTilesImpl(self, it).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["damageTilesPromise"] = luaFunctionN("damageTilesPromise") {
|
|
||||||
returnBuffer.setTo(
|
|
||||||
LuaFuture(
|
|
||||||
future = damageTilesImpl(self, it).thenApply { it.jsonName },
|
|
||||||
isLocal = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["damageTileArea"] = luaFunctionN("damageTileArea") {
|
|
||||||
returnBuffer.setTo(damageTileAreaImpl(self, it).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["damageTileAreaPromise"] = luaFunctionN("damageTileAreaPromise") {
|
|
||||||
returnBuffer.setTo(
|
|
||||||
LuaFuture(
|
|
||||||
future = damageTileAreaImpl(self, it).thenApply { it.jsonName },
|
|
||||||
isLocal = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeMaterial"] = luaFunctionN("placeMaterial") {
|
|
||||||
returnBuffer.setTo(placeMaterialImpl(self, it).getNow(listOf()).isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeMaterialPromise"] = luaFunctionN("placeMaterialPromise") {
|
|
||||||
returnBuffer.setTo(
|
|
||||||
LuaFuture(
|
|
||||||
future = placeMaterialImpl(self, it).thenApply { tableFrom(it.map { from(it) }) },
|
|
||||||
isLocal = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeMod"] = luaFunctionN("placeMod") {
|
|
||||||
returnBuffer.setTo(placeModImpl(self, it).getNow(listOf()).isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks["placeModPromise"] = luaFunctionN("placeModPromise") {
|
|
||||||
returnBuffer.setTo(
|
|
||||||
LuaFuture(
|
|
||||||
future = placeModImpl(self, it).thenApply { tableFrom(it.map { from(it) }) },
|
|
||||||
isLocal = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mod(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
|
|
||||||
|
if (tile.modifier.isNotEmptyModifier) {
|
||||||
|
args.lua.push(tile.modifier.key)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun materialHueShift(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
|
|
||||||
|
args.lua.push(tile.hueShift)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modHueShift(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
|
|
||||||
|
args.lua.push(tile.modifierHueShift)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun materialColor(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
|
|
||||||
|
args.lua.push(tile.color.ordinal.toLong())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun materialColorName(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val tile = self.getCell(pos).tile(isBackground)
|
||||||
|
|
||||||
|
args.lua.push(tile.color.jsonName)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMaterialColorNumber(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val getColor = args.nextInt()
|
||||||
|
val color = TileColor.entries.getOrNull(getColor) ?: throw IllegalArgumentException("invalid color: $getColor")
|
||||||
|
|
||||||
|
val cell = self.getCell(pos).mutable()
|
||||||
|
cell.tile(isBackground).color = color
|
||||||
|
args.lua.push(self.setCell(pos, cell))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMaterialColorName(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val pos = args.nextVector2i()
|
||||||
|
val isBackground = args.nextBoolean()
|
||||||
|
val getColor = args.nextString()
|
||||||
|
val color = TileColor.entries.valueOf(getColor)
|
||||||
|
|
||||||
|
val cell = self.getCell(pos).mutable()
|
||||||
|
cell.tile(isBackground).color = color
|
||||||
|
args.lua.push(self.setCell(pos, cell))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun oceanLevel(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(self.template.cellInfo(args.nextVector2d()).oceanLiquidLevel.toLong())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun environmentStatusEffects(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val effects = self.environmentStatusEffects(args.nextVector2d())
|
||||||
|
|
||||||
|
args.lua.pushTable(effects.size)
|
||||||
|
|
||||||
|
for ((i, effect) in effects.withIndex()) {
|
||||||
|
args.lua.push(i + 1L)
|
||||||
|
args.lua.push(Starbound.gson.toJsonTree(effect))
|
||||||
|
args.lua.setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun weatherStatusEffects(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
val effects = self.weatherStatusEffects(args.nextVector2d())
|
||||||
|
|
||||||
|
args.lua.pushTable(effects.size)
|
||||||
|
|
||||||
|
for ((i, effect) in effects.withIndex()) {
|
||||||
|
args.lua.push(i + 1L)
|
||||||
|
args.lua.push(Starbound.gson.toJsonTree(effect))
|
||||||
|
args.lua.setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun damageTiles(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(damageTilesImpl(self, args).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun damageTilesPromise(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(
|
||||||
|
LuaFuture(
|
||||||
|
future = damageTilesImpl(self, args).thenApply { it.jsonName },
|
||||||
|
isLocal = false,
|
||||||
|
handler = {
|
||||||
|
push(it)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun damageTileArea(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(damageTileAreaImpl(self, args).getNow(TileDamageResult.NONE) != TileDamageResult.NONE)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun damageTileAreaPromise(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(
|
||||||
|
LuaFuture(
|
||||||
|
future = damageTileAreaImpl(self, args).thenApply { it.jsonName },
|
||||||
|
isLocal = false,
|
||||||
|
handler = {
|
||||||
|
push(it)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun placeMaterial(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(placeMaterialImpl(self, args).getNow(listOf()).isEmpty())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun placeMaterialPromise(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(
|
||||||
|
LuaFuture(
|
||||||
|
future = placeMaterialImpl(self, args),
|
||||||
|
isLocal = false,
|
||||||
|
handler = {
|
||||||
|
pushTable(it.size)
|
||||||
|
|
||||||
|
for ((i, pos) in it.withIndex()) {
|
||||||
|
push(i + 1L)
|
||||||
|
push(pos)
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun placeMod(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(placeModImpl(self, args).getNow(listOf()).isEmpty())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun placeModPromise(self: World<*, *>, args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(
|
||||||
|
LuaFuture(
|
||||||
|
future = placeModImpl(self, args),
|
||||||
|
isLocal = false,
|
||||||
|
handler = {
|
||||||
|
pushTable(it.size)
|
||||||
|
|
||||||
|
for ((i, pos) in it.withIndex()) {
|
||||||
|
push(i + 1L)
|
||||||
|
push(pos)
|
||||||
|
setTableValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun provideWorldEnvironmentalBindings(self: World<*, *>, lua: LuaThread) {
|
||||||
|
lua.setTableValueToStub("lightLevel")
|
||||||
|
lua.setTableValueToStub("windLevel")
|
||||||
|
|
||||||
|
lua.pushBinding(self, "breathable", ::breathable)
|
||||||
|
lua.pushBinding(self, "underground", ::underground)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "material", ::material)
|
||||||
|
lua.pushBinding(self, "mod", ::mod)
|
||||||
|
lua.pushBinding(self, "materialHueShift", ::materialHueShift)
|
||||||
|
lua.pushBinding(self, "modHueShift", ::modHueShift)
|
||||||
|
lua.pushBinding(self, "materialColor", ::materialColor)
|
||||||
|
lua.pushBinding(self, "materialColorName", ::materialColorName)
|
||||||
|
lua.pushBinding(self, "setMaterialColorNumber", ::setMaterialColorNumber)
|
||||||
|
lua.pushBinding(self, "setMaterialColorName", ::setMaterialColorName)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "oceanLevel", ::oceanLevel)
|
||||||
|
lua.pushBinding(self, "environmentStatusEffects", ::environmentStatusEffects)
|
||||||
|
lua.pushBinding(self, "weatherStatusEffects", ::weatherStatusEffects)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "damageTiles", ::damageTiles)
|
||||||
|
lua.pushBinding(self, "damageTilesPromise", ::damageTilesPromise)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "damageTileArea", ::damageTileArea)
|
||||||
|
lua.pushBinding(self, "damageTileAreaPromise", ::damageTileAreaPromise)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "placeMaterial", ::placeMaterial)
|
||||||
|
lua.pushBinding(self, "placeMaterialPromise", ::placeMaterialPromise)
|
||||||
|
|
||||||
|
lua.pushBinding(self, "placeMod", ::placeMod)
|
||||||
|
lua.pushBinding(self, "placeModPromise", ::placeModPromise)
|
||||||
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.userdata
|
package ru.dbotthepony.kstarbound.lua.userdata
|
||||||
|
|
||||||
import org.classdump.luna.Table
|
import ru.dbotthepony.kstarbound.lua.LuaHandle
|
||||||
import org.classdump.luna.Userdata
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import org.classdump.luna.impl.ImmutableTable
|
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.CompletionException
|
import java.util.concurrent.CompletionException
|
||||||
@ -15,68 +12,83 @@ import java.util.concurrent.CompletionException
|
|||||||
*
|
*
|
||||||
* god damn it.
|
* god damn it.
|
||||||
*/
|
*/
|
||||||
class LuaFuture(val future: CompletableFuture<out Any?>, val isLocal: Boolean) : Userdata<CompletableFuture<out Any?>>() {
|
class LuaFuture<T : Any>(val future: CompletableFuture<T>, val isLocal: Boolean, val handler: LuaThread.(T) -> Int) {
|
||||||
override fun getMetatable(): Table {
|
fun finished(args: LuaThread.ArgStack): Int {
|
||||||
return metadata
|
args.lua.push(future.isDone)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMetatable(mt: Table?): Table {
|
fun succeeded(args: LuaThread.ArgStack): Int {
|
||||||
throw UnsupportedOperationException()
|
args.lua.push(future.isDone && !future.isCompletedExceptionally)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserValue(): CompletableFuture<out Any?> {
|
fun failed(args: LuaThread.ArgStack): Int {
|
||||||
return future
|
args.lua.push(future.isCompletedExceptionally)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setUserValue(value: CompletableFuture<out Any?>?): CompletableFuture<out Any?> {
|
fun result(args: LuaThread.ArgStack): Int {
|
||||||
throw UnsupportedOperationException()
|
try {
|
||||||
|
if (future.isCompletedExceptionally) {
|
||||||
|
return 0
|
||||||
|
} else if (isLocal) {
|
||||||
|
handler(args.lua, future.join())
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
val result = future.getNow(null) ?: return 0
|
||||||
|
return handler(args.lua, result)
|
||||||
|
}
|
||||||
|
} catch (err: CompletionException) {
|
||||||
|
return 0
|
||||||
|
} catch (err: CancellationException) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(args: LuaThread.ArgStack): Int {
|
||||||
|
// this is slow, but we can't get Exception out of CompletableFuture without latter throwing former
|
||||||
|
try {
|
||||||
|
if (isLocal) {
|
||||||
|
future.join()
|
||||||
|
} else {
|
||||||
|
future.getNow(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
} catch (err: CompletionException) {
|
||||||
|
args.lua.push(err.message ?: "internal error")
|
||||||
|
return 1
|
||||||
|
} catch (err: CancellationException) {
|
||||||
|
args.lua.push(err.message ?: "internal error")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun __index(): Table {
|
fun initializeHandle(lua: LuaThread): LuaHandle {
|
||||||
return metadata
|
lua.pushTable()
|
||||||
}
|
val handle = lua.createHandle("LuaFuture")
|
||||||
|
|
||||||
private val metadata = ImmutableTable.Builder()
|
lua.pushBinding("finished", LuaFuture<*>::finished)
|
||||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
lua.pushBinding("succeeded", LuaFuture<*>::succeeded)
|
||||||
.add("finished", luaFunction { self: LuaFuture ->
|
lua.pushBinding("failed", LuaFuture<*>::failed)
|
||||||
returnBuffer.setTo(self.future.isDone)
|
lua.pushBinding("result", LuaFuture<*>::result)
|
||||||
})
|
lua.pushBinding("error", LuaFuture<*>::error)
|
||||||
.add("succeeded", luaFunction { self: LuaFuture ->
|
|
||||||
returnBuffer.setTo(!self.future.isCompletedExceptionally)
|
lua.pop()
|
||||||
})
|
|
||||||
.add("failed", luaFunction { self: LuaFuture ->
|
return handle
|
||||||
returnBuffer.setTo(self.future.isCompletedExceptionally)
|
}
|
||||||
})
|
|
||||||
.add("result", luaFunction { self: LuaFuture ->
|
|
||||||
try {
|
|
||||||
if (self.future.isCompletedExceptionally) {
|
|
||||||
returnBuffer.setTo()
|
|
||||||
} else if (self.isLocal) {
|
|
||||||
returnBuffer.setTo(self.future.join())
|
|
||||||
} else {
|
|
||||||
returnBuffer.setTo(self.future.getNow(null))
|
|
||||||
}
|
|
||||||
} catch (err: CompletionException) {
|
|
||||||
returnBuffer.setTo()
|
|
||||||
} catch (err: CancellationException) {
|
|
||||||
returnBuffer.setTo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.add("error", luaFunction { self: LuaFuture ->
|
|
||||||
// this is slow, but we can't get Exception out of CompletableFuture without latter throwing former
|
|
||||||
try {
|
|
||||||
if (self.isLocal) {
|
|
||||||
returnBuffer.setTo(self.future.join())
|
|
||||||
} else {
|
|
||||||
returnBuffer.setTo(self.future.getNow(null))
|
|
||||||
}
|
|
||||||
} catch (err: CompletionException) {
|
|
||||||
returnBuffer.setTo(err.message ?: "internal error")
|
|
||||||
} catch (err: CancellationException) {
|
|
||||||
returnBuffer.setTo(err.message ?: "internal error")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LuaThread.push(value: LuaFuture<*>) {
|
||||||
|
pushTable()
|
||||||
|
|
||||||
|
push("__index")
|
||||||
|
push(commonHandles.future)
|
||||||
|
setTableValue()
|
||||||
|
|
||||||
|
pushObject(value)
|
||||||
|
}
|
||||||
|
@ -1,101 +1,126 @@
|
|||||||
package ru.dbotthepony.kstarbound.lua.userdata
|
package ru.dbotthepony.kstarbound.lua.userdata
|
||||||
|
|
||||||
import org.classdump.luna.Table
|
import ru.dbotthepony.kstarbound.lua.LuaHandle
|
||||||
import org.classdump.luna.TableFactory
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import org.classdump.luna.Userdata
|
import ru.dbotthepony.kstarbound.lua.setTableValue
|
||||||
import org.classdump.luna.impl.ImmutableTable
|
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
|
||||||
import ru.dbotthepony.kstarbound.lua.get
|
|
||||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
|
||||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
|
||||||
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState.Companion
|
|
||||||
import ru.dbotthepony.kstarbound.util.CarriedExecutor
|
import ru.dbotthepony.kstarbound.util.CarriedExecutor
|
||||||
import ru.dbotthepony.kstarbound.util.supplyAsync
|
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||||
import ru.dbotthepony.kstarbound.world.entities.PathFinder
|
import ru.dbotthepony.kstarbound.world.entities.PathFinder
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
|
|
||||||
class LuaPathFinder(val pacer: CarriedExecutor, val self: PathFinder) : Userdata<PathFinder>() {
|
|
||||||
override fun getMetatable(): Table {
|
|
||||||
return Companion.metatable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setMetatable(mt: Table?): Table {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUserValue(): PathFinder {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setUserValue(value: PathFinder?): PathFinder {
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class LuaPathFinder(private val pacer: CarriedExecutor, private val self: PathFinder) {
|
||||||
private var isRunning = false
|
private var isRunning = false
|
||||||
|
|
||||||
companion object {
|
fun result(args: LuaThread.ArgStack): Int {
|
||||||
private val cost = "const".toByteString()!!
|
if (self.result.isEmpty)
|
||||||
private val action = "action".toByteString()!!
|
return 0
|
||||||
private val jumpVelocity = "jumpVelocity".toByteString()!!
|
|
||||||
private val source = "source".toByteString()!!
|
|
||||||
private val target = "target".toByteString()!!
|
|
||||||
|
|
||||||
fun convertPath(tables: TableFactory, list: List<PathFinder.Edge>?): Table? {
|
val list = self.result.value ?: return 0
|
||||||
list ?: return null
|
return convertPath(args.lua, list)
|
||||||
val table = tables.newTable(list.size, 0)
|
}
|
||||||
|
|
||||||
|
fun explore(args: LuaThread.ArgStack): Int {
|
||||||
|
val maxExplore = args.nextOptionalLong()?.toInt()
|
||||||
|
val useOffThread = args.nextOptionalBoolean() ?: true
|
||||||
|
|
||||||
|
if (isRunning) {
|
||||||
|
if (self.result.isEmpty)
|
||||||
|
return 0
|
||||||
|
else {
|
||||||
|
args.lua.push(self.result.value != null)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
} else if (self.result.isPresent) {
|
||||||
|
args.lua.push(self.result.value != null)
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
val immediateMaxExplore = maxExplore?.times(4)?.coerceAtMost(800) ?: 800
|
||||||
|
val result = self.run(immediateMaxExplore)
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
args.lua.push(result)
|
||||||
|
return 1
|
||||||
|
} else if (self.result.isEmpty && useOffThread) {
|
||||||
|
// didn't explore enough, run in separate thread to not block main thread
|
||||||
|
pacer.supplyAsync(self)
|
||||||
|
isRunning = true
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isExploringOffThread(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(isRunning && self.result.isEmpty)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun usedOffThread(args: LuaThread.ArgStack): Int {
|
||||||
|
args.lua.push(isRunning)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runAsync(args: LuaThread.ArgStack): Int {
|
||||||
|
if (!isRunning) {
|
||||||
|
pacer.supplyAsync(self)
|
||||||
|
isRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun convertPath(lua: LuaThread, list: List<PathFinder.Edge>?): Int {
|
||||||
|
list ?: return 0
|
||||||
|
lua.pushTable(list.size)
|
||||||
var i = 1L
|
var i = 1L
|
||||||
|
|
||||||
for (edge in list) {
|
for (edge in list) {
|
||||||
val edgeTable = tables.tableOf()
|
lua.push(i++)
|
||||||
edgeTable[cost] = edge.cost
|
|
||||||
edgeTable[action] = edge.action.luaName
|
lua.pushTable(hashSize = 5)
|
||||||
edgeTable[jumpVelocity] = tables.from(edge.velocity)
|
|
||||||
edgeTable[source] = edge.source.toTable(tables)
|
lua.setTableValue("cost", edge.cost)
|
||||||
edgeTable[target] = edge.target.toTable(tables)
|
lua.setTableValue("action", edge.action.jsonName)
|
||||||
table[i++] = edgeTable
|
lua.setTableValue("jumpVelocity", edge.velocity)
|
||||||
|
|
||||||
|
lua.push("source")
|
||||||
|
edge.source.store(lua)
|
||||||
|
lua.setTableValue()
|
||||||
|
|
||||||
|
lua.push("target")
|
||||||
|
edge.target.store(lua)
|
||||||
|
lua.setTableValue()
|
||||||
|
|
||||||
|
lua.setTableValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
return table
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun __index(): Table {
|
fun initializeHandle(lua: LuaThread): LuaHandle {
|
||||||
return metatable
|
lua.pushTable()
|
||||||
|
val handle = lua.createHandle("PathFinder")
|
||||||
|
|
||||||
|
lua.pushBinding("explore", LuaPathFinder::explore)
|
||||||
|
lua.pushBinding("result", LuaPathFinder::result)
|
||||||
|
lua.pushBinding("runAsync", LuaPathFinder::runAsync)
|
||||||
|
lua.pushBinding("isExploringOffThread", LuaPathFinder::isExploringOffThread)
|
||||||
|
lua.pushBinding("usedOffThread", LuaPathFinder::usedOffThread)
|
||||||
|
|
||||||
|
lua.pop()
|
||||||
|
|
||||||
|
return handle
|
||||||
}
|
}
|
||||||
|
|
||||||
private val metatable = ImmutableTable.Builder()
|
|
||||||
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
|
|
||||||
.add("explore", luaFunction { self: LuaPathFinder, maxExplore: Number? ->
|
|
||||||
if (self.isRunning) {
|
|
||||||
if (self.self.result.isEmpty)
|
|
||||||
returnBuffer.setTo(null)
|
|
||||||
else
|
|
||||||
returnBuffer.setTo(self.self.result.value != null)
|
|
||||||
} else if (self.self.result.isPresent) {
|
|
||||||
returnBuffer.setTo(self.self.result.value != null)
|
|
||||||
} else {
|
|
||||||
val immediateMaxExplore = maxExplore?.toInt()?.times(4)?.coerceAtMost(800) ?: 800
|
|
||||||
val result = self.self.run(immediateMaxExplore)
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
returnBuffer.setTo(result)
|
|
||||||
} else if (self.self.result.isEmpty) {
|
|
||||||
// didn't explore enough, run in separate thread to not block main thread
|
|
||||||
self.pacer.supplyAsync(self.self)
|
|
||||||
self.isRunning = true
|
|
||||||
returnBuffer.setTo(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.add("result", luaFunction { self: LuaPathFinder ->
|
|
||||||
if (self.self.result.isEmpty)
|
|
||||||
return@luaFunction returnBuffer.setTo()
|
|
||||||
|
|
||||||
val list = self.self.result.value ?: return@luaFunction returnBuffer.setTo()
|
|
||||||
returnBuffer.setTo(convertPath(this, list))
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LuaThread.push(value: LuaPathFinder) {
|
||||||
|
pushTable()
|
||||||
|
|
||||||
|
push("__index")
|
||||||
|
push(commonHandles.pathFinder)
|
||||||
|
setTableValue()
|
||||||
|
|
||||||
|
pushObject(value)
|
||||||
|
}
|
||||||
|
@ -308,7 +308,7 @@ class ServerWorld private constructor(
|
|||||||
queuedPlacementsInternal.remove(entry)
|
queuedPlacementsInternal.remove(entry)
|
||||||
}.exceptionally {
|
}.exceptionally {
|
||||||
queuedPlacementsInternal.remove(entry)
|
queuedPlacementsInternal.remove(entry)
|
||||||
null
|
throw it
|
||||||
}
|
}
|
||||||
|
|
||||||
return future
|
return future
|
||||||
|
@ -13,6 +13,7 @@ import java.util.stream.Stream
|
|||||||
import kotlin.NoSuchElementException
|
import kotlin.NoSuchElementException
|
||||||
import kotlin.collections.Collection
|
import kotlin.collections.Collection
|
||||||
import kotlin.collections.List
|
import kotlin.collections.List
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
fun String.sbIntern(): String {
|
fun String.sbIntern(): String {
|
||||||
return Starbound.STRINGS.intern(this)
|
return Starbound.STRINGS.intern(this)
|
||||||
@ -103,3 +104,8 @@ fun <T> Collection<T>.getWrapAround(index: Int): T {
|
|||||||
|
|
||||||
return this.elementAt(positiveModulo(index, size))
|
return this.elementAt(positiveModulo(index, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds value down towards negative infinity (1.4 -> 1, 1.6 -> 1, -0.1 -> -1)
|
||||||
|
*/
|
||||||
|
fun Double.floorToInt() = floor(this).toInt()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.math.Line2d
|
||||||
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.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
@ -171,3 +172,10 @@ fun ICellAccess.castRay(
|
|||||||
|
|
||||||
return RayCastResult(hitTiles, null, 1.0, start, start + direction * distance, direction)
|
return RayCastResult(hitTiles, null, 1.0, start, start + direction * distance, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ICellAccess.castRay(
|
||||||
|
line: Line2d,
|
||||||
|
filter: TileRayFilter
|
||||||
|
): RayCastResult {
|
||||||
|
return castRay(line.p0, line.p1, filter)
|
||||||
|
}
|
||||||
|
@ -48,6 +48,7 @@ import ru.dbotthepony.kstarbound.network.packets.EntityMessagePacket
|
|||||||
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
|
||||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
import ru.dbotthepony.kstarbound.network.packets.clientbound.SetPlayerStartPacket
|
||||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||||
|
import ru.dbotthepony.kstarbound.util.floorToInt
|
||||||
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
|
||||||
@ -72,6 +73,7 @@ import java.util.stream.Stream
|
|||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.floor
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val template: WorldTemplate) : ICellAccess {
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val template: WorldTemplate) : ICellAccess {
|
||||||
@ -252,6 +254,10 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collide(with: AABB, filter: Predicate<CollisionPoly>): Stream<Poly.Penetration> {
|
||||||
|
return collide(Poly(with), filter)
|
||||||
|
}
|
||||||
|
|
||||||
fun collide(point: Vector2d, filter: Predicate<CollisionPoly>): Boolean {
|
fun collide(point: Vector2d, filter: Predicate<CollisionPoly>): Boolean {
|
||||||
return queryTileCollisions(AABB.withSide(point, 2.0)).any { filter.test(it) && point in it.poly }
|
return queryTileCollisions(AABB.withSide(point, 2.0)).any { filter.test(it) && point in it.poly }
|
||||||
}
|
}
|
||||||
@ -838,7 +844,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun environmentStatusEffects(x: Double, y: Double): Collection<PersistentStatusEffect> {
|
fun environmentStatusEffects(x: Double, y: Double): Collection<PersistentStatusEffect> {
|
||||||
return environmentStatusEffects(x.toInt(), y.toInt())
|
return environmentStatusEffects(x.floorToInt(), y.floorToInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun environmentStatusEffects(pos: IStruct2i): Collection<PersistentStatusEffect> {
|
fun environmentStatusEffects(pos: IStruct2i): Collection<PersistentStatusEffect> {
|
||||||
@ -857,7 +863,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun weatherStatusEffects(x: Double, y: Double): Collection<EphemeralStatusEffect> {
|
fun weatherStatusEffects(x: Double, y: Double): Collection<EphemeralStatusEffect> {
|
||||||
return weatherStatusEffects(x.toInt(), y.toInt())
|
return weatherStatusEffects(x.floorToInt(), y.floorToInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun weatherStatusEffects(pos: IStruct2i): Collection<EphemeralStatusEffect> {
|
fun weatherStatusEffects(pos: IStruct2i): Collection<EphemeralStatusEffect> {
|
||||||
|
@ -11,8 +11,10 @@ import ru.dbotthepony.kstarbound.Starbound
|
|||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
import ru.dbotthepony.kstarbound.lua.from
|
import ru.dbotthepony.kstarbound.lua.from
|
||||||
import ru.dbotthepony.kstarbound.lua.set
|
import ru.dbotthepony.kstarbound.lua.set
|
||||||
|
import ru.dbotthepony.kstarbound.lua.setTableValue
|
||||||
import ru.dbotthepony.kstarbound.lua.tableOf
|
import ru.dbotthepony.kstarbound.lua.tableOf
|
||||||
import ru.dbotthepony.kstarbound.lua.toByteString
|
import ru.dbotthepony.kstarbound.lua.toByteString
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
@ -159,6 +161,12 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
|
|||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun store(lua: LuaThread) {
|
||||||
|
lua.pushTable(hashSize = 2)
|
||||||
|
lua.setTableValue("position", position)
|
||||||
|
lua.setTableValue("velocity", velocity)
|
||||||
|
}
|
||||||
|
|
||||||
fun reconstructPath(): MutableList<Edge> {
|
fun reconstructPath(): MutableList<Edge> {
|
||||||
val result = ArrayList<Edge>()
|
val result = ArrayList<Edge>()
|
||||||
var parent = parent
|
var parent = parent
|
||||||
|
@ -63,6 +63,9 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
|
|||||||
|
|
||||||
private var config: JsonObject = JsonObject()
|
private var config: JsonObject = JsonObject()
|
||||||
|
|
||||||
|
val typeName: String
|
||||||
|
get() = config["type"]?.asString ?: ""
|
||||||
|
|
||||||
var isScripted = false
|
var isScripted = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.entities.api
|
package ru.dbotthepony.kstarbound.world.entities.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
|
||||||
interface ScriptedEntity {
|
interface ScriptedEntity {
|
||||||
// Call a script function directly with the given arguments, should return
|
// Call a script function directly with the given arguments, should return
|
||||||
// nothing only on failure.
|
// nothing only on failure.
|
||||||
fun callScript(fnName: String, vararg arguments: Any?): Array<Any?>
|
fun callScript(fnName: String, arguments: JsonArray): JsonElement
|
||||||
|
|
||||||
// Execute the given code directly in the underlying context, return nothing
|
// Execute the given code directly in the underlying context, return nothing
|
||||||
// on failure.
|
// on failure.
|
||||||
|
@ -16,12 +16,14 @@ local math = math
|
|||||||
local string = string
|
local string = string
|
||||||
local format = string.format
|
local format = string.format
|
||||||
|
|
||||||
local function checkarg(value, index, expected, fnName, overrideExpected)
|
function checkarg(value, index, expected, fnName, overrideExpected)
|
||||||
if type(value) ~= expected then
|
if type(value) ~= expected then
|
||||||
error(string.format('bad argument #%d to %s: %s expected, got %s', index, fnName, overrideExpected or expected, type(value)), 3)
|
error(string.format('bad argument #%d to %s: %s expected, got %s', index, fnName, overrideExpected or expected, type(value)), 3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local checkarg = checkarg
|
||||||
|
|
||||||
-- this replicates original engine code, but it shouldn't work in first place
|
-- this replicates original engine code, but it shouldn't work in first place
|
||||||
local function __newindex(self, key, value)
|
local function __newindex(self, key, value)
|
||||||
local nils = getmetatable(self).__nils
|
local nils = getmetatable(self).__nils
|
||||||
|
20
src/main/resources/scripts/server_world.lua
Normal file
20
src/main/resources/scripts/server_world.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
function world.loadUniqueEntity(name)
|
||||||
|
-- no, I give up, this shit is beyond bonkers
|
||||||
|
-- I don't care that mods could break
|
||||||
|
-- mods WILL break
|
||||||
|
-- just please, I beg you, for the love of God and Jesus,
|
||||||
|
-- stop fucking designing async functions as sync ones
|
||||||
|
|
||||||
|
local future = world.loadUniqueEntityAsync(name)
|
||||||
|
|
||||||
|
while not future:finished() do
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
|
||||||
|
return future:result()
|
||||||
|
end
|
||||||
|
|
||||||
|
function world.fidelity()
|
||||||
|
return 'high'
|
||||||
|
end
|
502
src/main/resources/scripts/world.lua
Normal file
502
src/main/resources/scripts/world.lua
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
|
||||||
|
local string_lower = string.lower
|
||||||
|
local checkarg = checkarg
|
||||||
|
|
||||||
|
local function determineLayer(layer)
|
||||||
|
if layer == 'foreground' then
|
||||||
|
return false
|
||||||
|
elseif layer == 'background' then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
error('Unknown tile layer ' .. tostring(layer), 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local functionsWithLayers = {
|
||||||
|
'material',
|
||||||
|
'mod',
|
||||||
|
'materialHueShift',
|
||||||
|
'modHueShift',
|
||||||
|
'materialColor',
|
||||||
|
'materialColorName',
|
||||||
|
'damageTiles',
|
||||||
|
'damageTilesPromise',
|
||||||
|
'placeMaterial',
|
||||||
|
'placeMaterialPromise',
|
||||||
|
'placeMod',
|
||||||
|
'placeModPromise',
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fnName in ipairs(functionsWithLayers) do
|
||||||
|
local impl = assert(world[fnName])
|
||||||
|
|
||||||
|
world[fnName] = function(pos, layer, ...)
|
||||||
|
return impl(pos, determineLayer(layer), ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local __setMaterialColorNumber = world.setMaterialColorNumber
|
||||||
|
local __setMaterialColorName = world.setMaterialColorName
|
||||||
|
|
||||||
|
function world.setMaterialColor(pos, layer, value)
|
||||||
|
if type(value) == 'number' then
|
||||||
|
return __setMaterialColorNumber(pos, determineLayer(layer), value)
|
||||||
|
else
|
||||||
|
return __setMaterialColorName(pos, determineLayer(layer), value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, fnName in ipairs({'damageTileArea', 'damageTileAreaPromise'}) do
|
||||||
|
local impl = assert(world[fnName])
|
||||||
|
|
||||||
|
world[fnName] = function(a, b, layer, ...)
|
||||||
|
return impl(a, b, determineLayer(layer), ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, name in ipairs({'entityHandItem', 'entityHandItemDescriptor'}) do
|
||||||
|
local impl = assert(world[name])
|
||||||
|
local fullName = 'world.' .. name
|
||||||
|
|
||||||
|
world[name] = function(id, hand, ...)
|
||||||
|
checkarg(hand, 2, 'string', fullName)
|
||||||
|
|
||||||
|
local lower = string_lower(hand)
|
||||||
|
|
||||||
|
if hand == 'primary' then
|
||||||
|
return impl(id, true, ...)
|
||||||
|
elseif hand == 'alt' or hand == 'secondary' then
|
||||||
|
return impl(id, false, ...)
|
||||||
|
else
|
||||||
|
error('unknown tool hand ' .. hand, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local entityTypes = {
|
||||||
|
plant = 0,
|
||||||
|
object = 1,
|
||||||
|
vehicle = 2,
|
||||||
|
itemDrop = 3,
|
||||||
|
plantDrop = 4,
|
||||||
|
projectile = 5,
|
||||||
|
stagehand = 6,
|
||||||
|
monster = 7,
|
||||||
|
npc = 8,
|
||||||
|
player = 9,
|
||||||
|
mobile = 10,
|
||||||
|
creature = 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function entityTypeNamesToIntegers(input, fullName)
|
||||||
|
if input then
|
||||||
|
for i, v in ipairs(input) do
|
||||||
|
-- could have used string.lower, but original engine does case-sensitive comparison here
|
||||||
|
-- so we can save quite a lot cpu cycles for ourselves by not doing string.lower
|
||||||
|
local lookup = entityTypes[v]
|
||||||
|
|
||||||
|
if not lookup then
|
||||||
|
error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
entityTypes[i] = lookup
|
||||||
|
end
|
||||||
|
|
||||||
|
return input
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local boundModes = {
|
||||||
|
metaboundbox = 0,
|
||||||
|
collisionarea = 1,
|
||||||
|
position = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
local function boundMode(input, fullName)
|
||||||
|
-- collision area by default
|
||||||
|
if not input then return 1 end
|
||||||
|
|
||||||
|
if type(input) ~= 'string' then
|
||||||
|
error('bad "boundMode" for ' .. fullName .. ', not a string: ' .. type(input), 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
local lookup = boundModes[string_lower(input)]
|
||||||
|
|
||||||
|
if not lookup then
|
||||||
|
error('bad "boundMode": ' .. tostring(input), 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lookup
|
||||||
|
end
|
||||||
|
|
||||||
|
local function order(input, fullName)
|
||||||
|
if not input then
|
||||||
|
return 0
|
||||||
|
elseif input == 'random' then
|
||||||
|
return 1
|
||||||
|
elseif input == 'nearest' then
|
||||||
|
return 2
|
||||||
|
else
|
||||||
|
error('bad "order" for ' .. fullName .. ': ' .. tostring(input), 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local regularQueryFunctions = {
|
||||||
|
['entity%sQuery'] = 'query%sImpl',
|
||||||
|
['monster%sQuery'] = 'monsterQuery%sImpl',
|
||||||
|
['npc%sQuery'] = 'npcQuery%sImpl',
|
||||||
|
['itemDrop%sQuery'] = 'itemDropQuery%sImpl',
|
||||||
|
['player%sQuery'] = 'playerQuery%sImpl',
|
||||||
|
}
|
||||||
|
|
||||||
|
for fnName, implName in pairs(regularQueryFunctions) do
|
||||||
|
do
|
||||||
|
local aabbImpl = assert(world[string.format(implName, 'AABB')])
|
||||||
|
local radiusImpl = assert(world[string.format(implName, 'Radius')])
|
||||||
|
local fnName = string.format(fnName, '')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(point, pointOrRadius, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(point, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(options, 3, 'table', fullName)
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(pointOrRadius) == 'table' then
|
||||||
|
-- AABB
|
||||||
|
return aabbImpl(
|
||||||
|
{point[1], point[2], pointOrRadius[1], pointOrRadius[2]},
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- point + radius
|
||||||
|
checkarg(pointOrRadius, 2, 'number', fullName, 'number or Vector2d')
|
||||||
|
|
||||||
|
return radiusImpl(
|
||||||
|
point,
|
||||||
|
pointOrRadius,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local lineImpl = assert(world[string.format(implName, 'Line')])
|
||||||
|
local fnName = string.format(fnName, 'Line')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(p0, p1, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(p0, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(p1, 2, 'table', fullName, 'Vector2d')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lineImpl(
|
||||||
|
p0, p1,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local polyImpl = assert(world[string.format(implName, 'Poly')])
|
||||||
|
local fnName = string.format(fnName, 'Poly')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(poly, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(poly, 1, 'table', fullName, 'Poly')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return polyImpl(
|
||||||
|
poly,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local fnName = 'object%sQuery'
|
||||||
|
local implName = 'queryObject%sImpl'
|
||||||
|
|
||||||
|
do
|
||||||
|
local aabbImpl = assert(world[string.format(implName, 'AABB')])
|
||||||
|
local radiusImpl = assert(world[string.format(implName, 'Radius')])
|
||||||
|
local fnName = string.format(fnName, '')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(point, pointOrRadius, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(point, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(options, 3, 'table', fullName)
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.name and type(options.name) ~= 'string' then
|
||||||
|
error('bad "name" in "options" for ' .. fullName .. ', not a table: ' .. type(options.name), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(pointOrRadius) == 'table' then
|
||||||
|
-- AABB
|
||||||
|
return aabbImpl(
|
||||||
|
options.name,
|
||||||
|
{point[1], point[2], pointOrRadius[1], pointOrRadius[2]},
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- point + radius
|
||||||
|
checkarg(pointOrRadius, 2, 'number', fullName, 'number or Vector2d')
|
||||||
|
|
||||||
|
return radiusImpl(
|
||||||
|
options.name,
|
||||||
|
point,
|
||||||
|
pointOrRadius,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local lineImpl = assert(world[string.format(implName, 'Line')])
|
||||||
|
local fnName = string.format(fnName, 'Line')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(p0, p1, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(p0, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(p1, 2, 'table', fullName, 'Vector2d')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.name and type(options.name) ~= 'string' then
|
||||||
|
error('bad "name" in "options" for ' .. fullName .. ', not a table: ' .. type(options.name), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lineImpl(
|
||||||
|
options.name,
|
||||||
|
p0, p1,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local polyImpl = assert(world[string.format(implName, 'Poly')])
|
||||||
|
local fnName = string.format(fnName, 'Poly')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(poly, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(poly, 1, 'table', fullName, 'Poly')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.name and type(options.name) ~= 'string' then
|
||||||
|
error('bad "name" in "options" for ' .. fullName .. ', not a table: ' .. type(options.name), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return polyImpl(
|
||||||
|
options.name,
|
||||||
|
poly,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local fnName = 'loungeable%sQuery'
|
||||||
|
local implName = 'queryLoungeable%sImpl'
|
||||||
|
|
||||||
|
local orientations = {
|
||||||
|
none = 0,
|
||||||
|
sit = 1,
|
||||||
|
lay = 2,
|
||||||
|
stand = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
local function orientation(input, fullName)
|
||||||
|
if not input then return 0 end
|
||||||
|
|
||||||
|
local lookup = orientations[input]
|
||||||
|
|
||||||
|
if not lookup then
|
||||||
|
if type(input) ~= 'string' then
|
||||||
|
error('bad "orientation" in "options" for ' .. fullName .. ', not a string: ' .. type(input), 3)
|
||||||
|
else
|
||||||
|
error('bad "orientation" in "options" for ' .. fullName .. ', not a valid orientation: ' .. input, 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lookup
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local aabbImpl = assert(world[string.format(implName, 'AABB')])
|
||||||
|
local radiusImpl = assert(world[string.format(implName, 'Radius')])
|
||||||
|
local fnName = string.format(fnName, '')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(point, pointOrRadius, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(point, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(options, 3, 'table', fullName)
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(pointOrRadius) == 'table' then
|
||||||
|
-- AABB
|
||||||
|
return aabbImpl(
|
||||||
|
orientation(options.orientation),
|
||||||
|
{point[1], point[2], pointOrRadius[1], pointOrRadius[2]},
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- point + radius
|
||||||
|
checkarg(pointOrRadius, 2, 'number', fullName, 'number or Vector2d')
|
||||||
|
|
||||||
|
return radiusImpl(
|
||||||
|
orientation(options.orientation),
|
||||||
|
point,
|
||||||
|
pointOrRadius,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local lineImpl = assert(world[string.format(implName, 'Line')])
|
||||||
|
local fnName = string.format(fnName, 'Line')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(p0, p1, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(p0, 1, 'table', fullName, 'Vector2d')
|
||||||
|
checkarg(p1, 2, 'table', fullName, 'Vector2d')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lineImpl(
|
||||||
|
orientation(options.orientation),
|
||||||
|
p0, p1,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local polyImpl = assert(world[string.format(implName, 'Poly')])
|
||||||
|
local fnName = string.format(fnName, 'Poly')
|
||||||
|
local fullName = 'world.' .. fnName
|
||||||
|
|
||||||
|
world[fnName] = function(poly, options)
|
||||||
|
options = options or {}
|
||||||
|
checkarg(poly, 1, 'table', fullName, 'Poly')
|
||||||
|
|
||||||
|
if options.callScriptArgs and type(options.callScriptArgs) ~= 'table' then
|
||||||
|
error('bad "callScriptArgs" in "options" for ' .. fullName .. ', not a table: ' .. type(options.callScriptArgs), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return polyImpl(
|
||||||
|
orientation(options.orientation),
|
||||||
|
poly,
|
||||||
|
entityTypeNamesToIntegers(options.includedTypes, fullName),
|
||||||
|
options.withoutEntityId,
|
||||||
|
boundMode(options.boundMode, fullName),
|
||||||
|
options.callScript,
|
||||||
|
options.callScriptArgs or {},
|
||||||
|
options.callScriptResult,
|
||||||
|
order(options.order, fullName),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,14 +1,24 @@
|
|||||||
package ru.dbotthepony.kstarbound.test
|
package ru.dbotthepony.kstarbound.test
|
||||||
|
|
||||||
|
import com.kenai.jffi.MemoryIO
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaState
|
import ru.dbotthepony.kstarbound.lua.LuaHandle
|
||||||
|
import ru.dbotthepony.kstarbound.lua.LuaThread
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
object LuaTests {
|
object LuaTests {
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
val lua = LuaState()
|
val lua = LuaThread()
|
||||||
lua.load("print('Hello, world!')")
|
|
||||||
lua.call()
|
lua.ensureExtraCapacity(1000)
|
||||||
|
|
||||||
|
lua.loadGlobal("collectgarbage")
|
||||||
|
lua.push("count")
|
||||||
|
lua.call(1, 1)
|
||||||
|
println(lua.popDouble()!! * 1024)
|
||||||
|
|
||||||
lua.close()
|
lua.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user