Some initial Lua bindings (object, animator, root, sb)
This commit is contained in:
parent
ba05b27deb
commit
8ee60c0f4d
21
ADDITIONS.md
21
ADDITIONS.md
@ -39,7 +39,7 @@ val color: TileColor = TileColor.DEFAULT
|
||||
```
|
||||
* `item` brush now can accept proper item descriptors (in json object tag),
|
||||
* Previous behavior remains unchanged (if specified as string, creates _randomized_ item, if as object, creates _exactly_ what have been specified)
|
||||
* To stop randomizing as Tiled tileset brush, specify `"randomize"` as `false`
|
||||
* To stop randomizing as Tiled tileset brush, specify `"dont_randomize"` as anything (e.g. as `""`)
|
||||
* `liquid` brush now can accept 'level' as second argument
|
||||
* Previous behavior is unchanged, `["liquid", "water", true]` will result into infinite water as before, but `["liquid", "water", 0.5, false]` will spawn half-filled water
|
||||
* In tiled, you already can do this using `"quantity"` property
|
||||
@ -62,3 +62,22 @@ val color: TileColor = TileColor.DEFAULT
|
||||
* Used by object and plant anchoring code to determine valid placement
|
||||
* Used by world tile rendering code (render piece rule `Connects`)
|
||||
* And finally, used by `canPlaceMaterial` to determine whenever player can place blocks next to it (at least one such tile should be present for player to be able to place blocks next to it)
|
||||
|
||||
---------------
|
||||
|
||||
### Scripting
|
||||
|
||||
#### animator
|
||||
|
||||
* Added `animator.targetRotationAngle(rotationGroup: string): double`
|
||||
* Added `animator.hasRotationGroup(rotationGroup: string): boolean`
|
||||
* Added `animator.rotationGroups(): List<string>` (returns valid names for `rotateGroup`, `currentRotationAngle` and `targetRotationAngle`)
|
||||
* Added `animator.transformationGroups(): List<string>`
|
||||
* Added `animator.particleEmitters(): List<string>`
|
||||
* Added `animator.hasParticleEmitter(emitter: string): boolean`
|
||||
* Added `animator.lights(): List<string>`
|
||||
* Added `animator.hasLight(light: string): boolean`
|
||||
* Added `animator.sounds(): List<string>`
|
||||
* Added `animator.effects(): List<string>`
|
||||
* Added `animator.hasEffect(effect: string): boolean`
|
||||
* Added `animator.parts(): List<string>`
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
### Starbound engine recreation project
|
||||
|
||||
Make sure to specify next settings as startup options to JVM:
|
||||
|
||||
```
|
||||
-Dfile.encoding=UTF8
|
||||
```
|
||||
|
@ -83,7 +83,7 @@ dependencies {
|
||||
implementation("ru.dbotthepony.kommons:kommons-gson-linear-algebra:[$kommonsVersion,)") { setTransitive(false) }
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||
implementation("org.classdump.luna:luna-all-shaded:0.4.1")
|
||||
implementation(project(":luna"))
|
||||
|
||||
implementation("io.netty:netty-transport:4.1.105.Final")
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
rootProject.name = "KStarBound"
|
||||
|
||||
include("luna")
|
||||
|
@ -7,6 +7,9 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||
import org.classdump.luna.compiler.CompilerSettings
|
||||
import org.classdump.luna.load.ChunkFactory
|
||||
import ru.dbotthepony.kommons.gson.AABBTypeAdapter
|
||||
import ru.dbotthepony.kommons.gson.AABBiTypeAdapter
|
||||
import ru.dbotthepony.kommons.gson.EitherTypeAdapter
|
||||
@ -68,6 +71,7 @@ import java.io.*
|
||||
import java.lang.ref.Cleaner
|
||||
import java.text.DateFormat
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
@ -150,7 +154,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
||||
// Hrm.
|
||||
// val strings: Interner<String> = Interner.newWeakInterner()
|
||||
// val strings: Interner<String> = Interner { it }
|
||||
@JvmField
|
||||
@JvmField
|
||||
val STRINGS: Interner<String> = interner(5)
|
||||
|
||||
// immeasurably lazy and fragile solution, too bad!
|
||||
@ -220,6 +224,27 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
||||
}
|
||||
}
|
||||
|
||||
private val loader = CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua_")
|
||||
private val scriptCache = ConcurrentHashMap<String, ChunkFactory>()
|
||||
|
||||
private fun loadScript0(path: String): ChunkFactory {
|
||||
val find = locate(path)
|
||||
|
||||
if (!find.exists) {
|
||||
throw NoSuchElementException("Script $path does not exist")
|
||||
}
|
||||
|
||||
return loader.compileTextChunk(path, find.readToString())
|
||||
}
|
||||
|
||||
fun loadScript(path: String): ChunkFactory {
|
||||
return scriptCache.computeIfAbsent(path, ::loadScript0)
|
||||
}
|
||||
|
||||
fun compileScriptChunk(name: String, chunk: String): ChunkFactory {
|
||||
return loader.compileTextChunk(name, chunk)
|
||||
}
|
||||
|
||||
val gson: Gson = with(GsonBuilder()) {
|
||||
// serializeNulls()
|
||||
setDateFormat(DateFormat.LONG)
|
||||
|
@ -66,7 +66,7 @@ data class AnimatedPartsDefinition(
|
||||
@JsonFactory
|
||||
data class State(
|
||||
val properties: JsonObject = JsonObject(),
|
||||
val frameProperties: JsonObject = JsonObject(),
|
||||
val frameProperties: ImmutableMap<String, JsonArray> = ImmutableMap.of(),
|
||||
)
|
||||
}
|
||||
}
|
@ -239,7 +239,7 @@ enum class DungeonBrushType(override val jsonName: String) : IStringSerializable
|
||||
|
||||
override fun readTiled(json: JsonObject): DungeonBrush? {
|
||||
if ("item" in json) {
|
||||
return DungeonBrush.DropItem(ItemDescriptor(json["item"].asString, json.get("count", 1L), json.get("parameters") { JsonObject() }), json.get("randomize", true))
|
||||
return DungeonBrush.DropItem(ItemDescriptor(json["item"].asString, json.get("count", 1L), json.get("parameters") { JsonObject() }), "dont_randomize" !in json)
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -15,12 +15,11 @@ import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.TableFactory
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.lua.StateMachine
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonObject
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
@ -36,6 +35,8 @@ import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.function.Supplier
|
||||
@ -80,17 +81,34 @@ fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier<ItemDescri
|
||||
stateMachine.add {
|
||||
val iname = name.get()
|
||||
val icount = count.get().orElse(1L)
|
||||
val iparameters = parameters.get().map { if (it is Table) it.toJsonObject() else throw LuaRuntimeException("Invalid item descriptor parameters ($it)") }.orElse { JsonObject() }
|
||||
val iparameters = parameters.get().map { if (it is Table) it.toJson(true) else throw LuaRuntimeException("Invalid item descriptor parameters ($it)") }.orElse { JsonObject() }
|
||||
|
||||
if (iname !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${iname})")
|
||||
if (icount !is Number) throw LuaRuntimeException("Invalid item descriptor count (${icount})")
|
||||
|
||||
result = KOptional(ItemDescriptor(iname.decode(), icount.toLong(), iparameters))
|
||||
result = KOptional(ItemDescriptor(iname.decode(), icount.toLong(), iparameters as JsonObject))
|
||||
}
|
||||
|
||||
return Supplier { result.value }
|
||||
}
|
||||
|
||||
fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
|
||||
val name = indexNoYield(data, 1L) ?: indexNoYield(data, "name") ?: indexNoYield(data, "item")
|
||||
val count = indexNoYield(data, 2L) ?: indexNoYield(data, "count") ?: 1L
|
||||
val parameters = indexNoYield(data, 3L) ?: indexNoYield(data, "parameters") ?: indexNoYield(data, "data")
|
||||
|
||||
if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})")
|
||||
if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})")
|
||||
|
||||
if (parameters == null) {
|
||||
return ItemDescriptor(name.decode(), count.toLong())
|
||||
} else if (parameters is Table) {
|
||||
return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject)
|
||||
} else {
|
||||
throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)")
|
||||
}
|
||||
}
|
||||
|
||||
fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
|
||||
val name = stream.readInternedString()
|
||||
val count = stream.readVarLong()
|
||||
|
@ -38,11 +38,11 @@ enum class SkyType {
|
||||
}
|
||||
}
|
||||
|
||||
enum class FlyingType {
|
||||
NONE,
|
||||
DISEMBARKING,
|
||||
WARP,
|
||||
ARRIVING;
|
||||
enum class FlyingType(override val jsonName: String) : IStringSerializable {
|
||||
NONE("none"),
|
||||
DISEMBARKING("disembarking"),
|
||||
WARP("warp"),
|
||||
ARRIVING("arriving");
|
||||
|
||||
companion object {
|
||||
val CODEC = StreamCodec.Enum(FlyingType::class.java)
|
||||
|
375
src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt
Normal file
375
src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt
Normal file
@ -0,0 +1,375 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Conversions
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.TableFactory
|
||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
import org.classdump.luna.runtime.AbstractFunction3
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.IStruct2d
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.util.IStruct3i
|
||||
import ru.dbotthepony.kommons.util.IStruct4i
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2f
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
|
||||
fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
|
||||
return Vector2i(x.toInt(), y.toInt())
|
||||
}
|
||||
|
||||
fun ExecutionContext.toVector2d(table: Any): Vector2d {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
|
||||
return Vector2d(x.toDouble(), y.toDouble())
|
||||
}
|
||||
|
||||
fun ExecutionContext.toPoly(table: Table): Poly {
|
||||
val vertices = ArrayList<Vector2d>()
|
||||
|
||||
for ((_, v) in table) {
|
||||
vertices.add(toVector2d(v))
|
||||
}
|
||||
|
||||
return Poly(vertices)
|
||||
}
|
||||
|
||||
fun ExecutionContext.toVector2f(table: Any): Vector2f {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
|
||||
|
||||
return Vector2f(x.toFloat(), y.toFloat())
|
||||
}
|
||||
|
||||
fun ExecutionContext.toColor(table: Any): RGBAColor {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
val z = indexNoYield(table, 3L)
|
||||
val w = indexNoYield(table, 4L) ?: 255
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y")
|
||||
if (z !is Number) throw ClassCastException("Expected table representing a Color, but value at [3] is not a number: $z")
|
||||
if (w !is Number) throw ClassCastException("Expected table representing a Color, but value at [4] is not a number: $w")
|
||||
|
||||
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt())
|
||||
}
|
||||
|
||||
fun ExecutionContext.toAABB(table: Any): AABB {
|
||||
val x = indexNoYield(table, 1L)
|
||||
val y = indexNoYield(table, 2L)
|
||||
val z = indexNoYield(table, 3L)
|
||||
val w = indexNoYield(table, 4L)
|
||||
|
||||
if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x")
|
||||
if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y")
|
||||
if (z !is Number) throw ClassCastException("Expected table representing a AABB, but value at [3] is not a number: $z")
|
||||
if (w !is Number) throw ClassCastException("Expected table representing a AABB, but value at [4] is not a number: $w")
|
||||
|
||||
return AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(z.toDouble(), w.toDouble()))
|
||||
}
|
||||
|
||||
fun toJsonFromLua(value: Any?): JsonElement {
|
||||
return when (value) {
|
||||
null, is JsonNull -> JsonNull.INSTANCE
|
||||
is String -> JsonPrimitive(value)
|
||||
is ByteString -> JsonPrimitive(value.decode())
|
||||
is Number -> JsonPrimitive(value)
|
||||
is Boolean -> InternedJsonElementAdapter.of(value)
|
||||
is Table -> value.toJson()
|
||||
else -> throw IllegalArgumentException("Unable to translate $value into json!")
|
||||
}
|
||||
}
|
||||
|
||||
fun Table.toJson(forceObject: Boolean = false): JsonElement {
|
||||
val arrayValues = Long2ObjectAVLTreeMap<JsonElement>()
|
||||
val hashValues = HashMap<String, JsonElement>()
|
||||
|
||||
val meta = metatable
|
||||
var hint = LUA_HINT_NONE
|
||||
|
||||
if (meta != null) {
|
||||
val getHint = meta["__typehint"]
|
||||
|
||||
if (getHint is Number) {
|
||||
hint = getHint.toLong()
|
||||
}
|
||||
|
||||
val nils = meta["__nils"]
|
||||
|
||||
if (nils is Table) {
|
||||
// Nil entries just have a garbage integer as their value
|
||||
for ((k, v) in nils) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null) {
|
||||
arrayValues[ik] = JsonNull.INSTANCE
|
||||
} else {
|
||||
hashValues[k.toString()] = JsonNull.INSTANCE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((k, v) in this) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null) {
|
||||
arrayValues[ik] = toJsonFromLua(v)
|
||||
} else {
|
||||
hashValues[k.toString()] = toJsonFromLua(v)
|
||||
}
|
||||
}
|
||||
|
||||
val interpretAsList = !forceObject && hashValues.isEmpty() && hint != LUA_HINT_OBJECT
|
||||
|
||||
if (interpretAsList) {
|
||||
val list = JsonArray()
|
||||
|
||||
for ((k, v) in arrayValues) {
|
||||
val ik = k.toInt() - 1
|
||||
|
||||
while (list.size() < ik) {
|
||||
list.add(JsonNull.INSTANCE)
|
||||
}
|
||||
|
||||
list[ik] = v
|
||||
}
|
||||
|
||||
return list
|
||||
} else {
|
||||
for ((k, v) in arrayValues) {
|
||||
hashValues[(k - 1L).toString()] = v
|
||||
}
|
||||
|
||||
return JsonObject().apply {
|
||||
for ((k, v) in hashValues) {
|
||||
this[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonElement?): Any? {
|
||||
when (value) {
|
||||
is JsonPrimitive -> {
|
||||
if (value.isNumber) {
|
||||
return value.asDouble
|
||||
} else if (value.isString) {
|
||||
return ByteString.of(value.asString)
|
||||
} else if (value.isBoolean) {
|
||||
return value.asBoolean
|
||||
} else {
|
||||
throw RuntimeException("unreachable code")
|
||||
}
|
||||
}
|
||||
|
||||
is JsonArray -> return from(value)
|
||||
is JsonObject -> return from(value)
|
||||
null, is JsonNull -> return null
|
||||
else -> throw RuntimeException(value::class.qualifiedName)
|
||||
}
|
||||
}
|
||||
|
||||
private data class JsonTable(val metatable: Table, val nils: Table, val data: Table)
|
||||
|
||||
fun Any?.toLuaInteger(): Long? {
|
||||
if (this == null)
|
||||
return null
|
||||
else if (this is Long)
|
||||
return this
|
||||
else if (this is Double) {
|
||||
if (this % 1.0 == 0.0) {
|
||||
return this.toLong()
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else if (this is ByteString) {
|
||||
val decoded = decode()
|
||||
|
||||
if (decoded.contains('.'))
|
||||
return null
|
||||
|
||||
return decoded.toLongOrNull()
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const val LUA_HINT_NONE = 0L
|
||||
const val LUA_HINT_ARRAY = 1L
|
||||
const val LUA_HINT_OBJECT = 2L
|
||||
|
||||
private object JsonTableIndex : AbstractFunction3<Table, Any, Any?>() {
|
||||
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, self: Table, key: Any, value: Any?) {
|
||||
val meta = self.metatable
|
||||
val nils = meta["__nils"] as Table
|
||||
|
||||
// If we are setting an entry to nil, need to add a bogus integer entry
|
||||
// to the __nils table, otherwise need to set the entry *in* the __nils
|
||||
// table to nil and remove it.
|
||||
|
||||
// TODO: __newindex is called only when assigning non-existing keys to values,
|
||||
// TODO: as per Lua manual.
|
||||
// TODO: Chucklefish weren't aware of this?
|
||||
|
||||
if (value == null) {
|
||||
nils[key] = 0L
|
||||
} else {
|
||||
nils[key] = null as Any?
|
||||
}
|
||||
|
||||
self[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun TableFactory.createJsonTable(typeHint: Long, size: Int, hash: Int): JsonTable {
|
||||
val metatable = newTable()
|
||||
val nils = newTable()
|
||||
val data = newTable(size, hash)
|
||||
|
||||
metatable["__newindex"] = JsonTableIndex
|
||||
metatable["__nils"] = nils
|
||||
metatable["__typehint"] = typeHint
|
||||
|
||||
data.metatable = metatable
|
||||
return JsonTable(metatable, nils, data)
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonObject): Table {
|
||||
val (_, nils, data) = createJsonTable(LUA_HINT_OBJECT, 0, value.size())
|
||||
|
||||
for ((k, v) in value.entrySet()) {
|
||||
if (v.isJsonNull) {
|
||||
nils[k] = 0L
|
||||
} else {
|
||||
data[k] = from(v)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonArray): Table {
|
||||
val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size())
|
||||
|
||||
for ((i, v) in value.withIndex()) {
|
||||
if (v.isJsonNull) {
|
||||
nils[i + 1L] = 0L
|
||||
} else {
|
||||
data[i + 1L] = from(v)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
fun TableFactory.createJsonObject(): Table {
|
||||
return createJsonTable(LUA_HINT_OBJECT, 0, 0).data
|
||||
}
|
||||
|
||||
fun TableFactory.createJsonArray(): Table {
|
||||
return createJsonTable(LUA_HINT_ARRAY, 0, 0).data
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct2d): Table {
|
||||
return newTable(2, 0).apply {
|
||||
this[1L] = value.component1()
|
||||
this[2L] = value.component2()
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: Poly): Table {
|
||||
return newTable(value.vertices.size, 0).apply {
|
||||
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.fromCollection(value: Collection<JsonElement?>): Table {
|
||||
val table = newTable(value.size, 0)
|
||||
|
||||
for ((k, v) in value.withIndex()) {
|
||||
table.rawset(Conversions.normaliseKey(k + 1), from(v))
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct2i): Table {
|
||||
return newTable(2, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct3i): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
it.rawset(3L, value.component3())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct4i): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
it.rawset(3L, value.component3())
|
||||
it.rawset(4L, value.component4())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: RGBAColor): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.redInt.toLong())
|
||||
it.rawset(2L, value.greenInt.toLong())
|
||||
it.rawset(3L, value.blueInt.toLong())
|
||||
it.rawset(4L, value.alphaInt.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: Collection<Any>): Table {
|
||||
return newTable(value.size, 0).also {
|
||||
for ((i, v) in value.withIndex()) {
|
||||
it.rawset(i + 1L, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: AABB): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.mins.x)
|
||||
it.rawset(2L, value.mins.y)
|
||||
it.rawset(3L, value.maxs.x)
|
||||
it.rawset(4L, value.maxs.y)
|
||||
}
|
||||
}
|
@ -1,33 +1,38 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterators
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Conversions
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.TableFactory
|
||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
import org.classdump.luna.lib.ArgumentIterator
|
||||
import org.classdump.luna.lib.BadArgumentException
|
||||
import org.classdump.luna.runtime.AbstractFunction0
|
||||
import org.classdump.luna.runtime.AbstractFunction1
|
||||
import org.classdump.luna.runtime.AbstractFunction2
|
||||
import org.classdump.luna.runtime.AbstractFunction3
|
||||
import org.classdump.luna.runtime.AbstractFunction4
|
||||
import org.classdump.luna.runtime.AbstractFunctionAnyArg
|
||||
import org.classdump.luna.runtime.Dispatch
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
import ru.dbotthepony.kommons.util.IStruct2i
|
||||
import ru.dbotthepony.kommons.util.IStruct3i
|
||||
import ru.dbotthepony.kommons.util.IStruct4i
|
||||
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
||||
import org.classdump.luna.runtime.UnresolvedControlThrowable
|
||||
|
||||
fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? {
|
||||
return try {
|
||||
Dispatch.index(this, table, key)
|
||||
returnBuffer.get0()
|
||||
} catch (err: UnresolvedControlThrowable) {
|
||||
throw RuntimeException("attempt to yield across C boundary", err)
|
||||
}
|
||||
}
|
||||
|
||||
fun ExecutionContext.indexSetNoYield(table: Any, key: Any, value: Any?) {
|
||||
try {
|
||||
Dispatch.setindex(this, table, key, value)
|
||||
} catch (err: UnresolvedControlThrowable) {
|
||||
throw RuntimeException("attempt to yield across C boundary", err)
|
||||
}
|
||||
}
|
||||
|
||||
fun ArgumentIterator.nextOptionalFloat(): Double? {
|
||||
if (hasNext()) {
|
||||
@ -94,161 +99,6 @@ operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
||||
}
|
||||
}
|
||||
|
||||
fun Table.toJson(): JsonElement {
|
||||
val arrayValues = Long2ObjectAVLTreeMap<Any>()
|
||||
val hashValues = HashMap<Any, Any>()
|
||||
|
||||
for ((k, v) in this) {
|
||||
if (k is Number) {
|
||||
arrayValues[k.toLong()] = v
|
||||
} else {
|
||||
hashValues[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if (hashValues.isEmpty()) {
|
||||
return JsonArray(arrayValues.size).also {
|
||||
for (v in arrayValues.values) {
|
||||
if (v is ByteString) {
|
||||
it.add(JsonPrimitive(v.decode()))
|
||||
} else if (v is Number) {
|
||||
it.add(JsonPrimitive(v))
|
||||
} else if (v is Boolean) {
|
||||
it.add(InternedJsonElementAdapter.of(v))
|
||||
} else if (v is Table) {
|
||||
it.add(v.toJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return JsonObject().also {
|
||||
for ((k, v) in arrayValues) {
|
||||
if (v is ByteString) {
|
||||
it.add(k.toString(), JsonPrimitive(v.decode()))
|
||||
} else if (v is Number) {
|
||||
it.add(k.toString(), JsonPrimitive(v))
|
||||
} else if (v is Boolean) {
|
||||
it.add(k.toString(), InternedJsonElementAdapter.of(v))
|
||||
} else if (v is Table) {
|
||||
it.add(k.toString(), v.toJson())
|
||||
}
|
||||
}
|
||||
|
||||
for ((k, v) in hashValues) {
|
||||
if (v is ByteString) {
|
||||
it.add(k.toString(), JsonPrimitive(v.decode()))
|
||||
} else if (v is Number) {
|
||||
it.add(k.toString(), JsonPrimitive(v))
|
||||
} else if (v is Boolean) {
|
||||
it.add(k.toString(), InternedJsonElementAdapter.of(v))
|
||||
} else if (v is Table) {
|
||||
it.add(k.toString(), v.toJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Table.toJsonObject(): JsonObject {
|
||||
return JsonObject().also {
|
||||
for ((k, v) in this) {
|
||||
if (v is ByteString) {
|
||||
it.add(k.toString(), JsonPrimitive(v.decode()))
|
||||
} else if (v is Number) {
|
||||
it.add(k.toString(), JsonPrimitive(v))
|
||||
} else if (v is Boolean) {
|
||||
it.add(k.toString(), InternedJsonElementAdapter.of(v))
|
||||
} else if (v is Table) {
|
||||
it.add(k.toString(), v.toJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonElement?): Any? {
|
||||
when (value) {
|
||||
is JsonPrimitive -> {
|
||||
if (value.isNumber) {
|
||||
return value.asDouble
|
||||
} else if (value.isString) {
|
||||
return ByteString.of(value.asString)
|
||||
} else if (value.isBoolean) {
|
||||
return value.asBoolean
|
||||
} else {
|
||||
throw RuntimeException("unreachable code")
|
||||
}
|
||||
}
|
||||
|
||||
is JsonNull -> return null
|
||||
is JsonArray -> return from(value)
|
||||
is JsonObject -> return from(value)
|
||||
null -> return null
|
||||
else -> throw RuntimeException(value::class.qualifiedName)
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonObject): Table {
|
||||
val table = newTable(0, value.size())
|
||||
|
||||
for ((k, v) in value.entrySet()) {
|
||||
table.rawset(Conversions.normaliseKey(k), from(v))
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: JsonArray): Table {
|
||||
val table = newTable(value.size(), 0)
|
||||
|
||||
for ((k, v) in value.withIndex()) {
|
||||
table.rawset(Conversions.normaliseKey(k + 1), from(v))
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
fun TableFactory.fromCollection(value: Collection<JsonElement?>): Table {
|
||||
val table = newTable(value.size, 0)
|
||||
|
||||
for ((k, v) in value.withIndex()) {
|
||||
table.rawset(Conversions.normaliseKey(k + 1), from(v))
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct2i): Table {
|
||||
return newTable(2, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct3i): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
it.rawset(3L, value.component3())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: IStruct4i): Table {
|
||||
return newTable(3, 0).also {
|
||||
it.rawset(1L, value.component1())
|
||||
it.rawset(2L, value.component2())
|
||||
it.rawset(3L, value.component3())
|
||||
it.rawset(4L, value.component4())
|
||||
}
|
||||
}
|
||||
|
||||
fun TableFactory.from(value: Collection<Any>): Table {
|
||||
return newTable(value.size, 0).also {
|
||||
for ((i, v) in value.withIndex()) {
|
||||
it.rawset(i + 1L, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Function is a stub")
|
||||
fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, Any?, Any?, Any?> {
|
||||
return object : LuaFunction<Any?, Any?, Any?, Any?, Any?>() {
|
||||
@ -286,42 +136,19 @@ fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, An
|
||||
}
|
||||
}
|
||||
|
||||
fun luaFunction0String(name: String, callable: (String) -> Any?): LuaFunction<Any?, *, *, *, *> {
|
||||
return object : AbstractFunction1<Any?>() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, arg: Any?) {
|
||||
if (arg !is ByteString)
|
||||
throw BadArgumentException(1, name, "string expected, got ${if (arg == null) "nil" else arg::class.qualifiedName}")
|
||||
|
||||
val result = callable.invoke(arg.decode())
|
||||
|
||||
if (result != null && result !== Unit) {
|
||||
context.returnBuffer.setTo(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun luaFunctionN(name: String, callable: (ExecutionContext, ArgumentIterator) -> Any?): LuaFunction<Any, Any, Any, Any, Any> {
|
||||
fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
|
||||
return object : AbstractFunctionAnyArg() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
|
||||
val result = callable.invoke(context, ArgumentIterator.of(context, name, args))
|
||||
|
||||
if (result != null && result !== Unit) {
|
||||
context.returnBuffer.setTo(result)
|
||||
}
|
||||
callable.invoke(context, ArgumentIterator.of(context, name, args))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun luaFunctionNS(name: String, callable: (ExecutionContext, ArgumentIterator) -> StateMachine): LuaFunction<Any, Any, Any, Any, Any> {
|
||||
fun luaFunctionNS(name: String, callable: ExecutionContext.(ArgumentIterator) -> StateMachine): LuaFunction<Any, Any, Any, Any, Any> {
|
||||
return object : AbstractFunctionAnyArg() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
(suspendedState as StateMachine).run(context)
|
||||
@ -333,7 +160,19 @@ fun luaFunctionNS(name: String, callable: (ExecutionContext, ArgumentIterator) -
|
||||
}
|
||||
}
|
||||
|
||||
fun luaFunction(callable: (ExecutionContext) -> Unit): LuaFunction<*, *, *, *, *> {
|
||||
fun luaFunctionArray(callable: ExecutionContext.(Array<out Any?>) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
|
||||
return object : AbstractFunctionAnyArg() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, args: Array<out Any?>) {
|
||||
callable.invoke(context, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun luaFunction(callable: ExecutionContext.() -> Unit): LuaFunction<*, *, *, *, *> {
|
||||
return object : AbstractFunction0() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
@ -345,7 +184,7 @@ fun luaFunction(callable: (ExecutionContext) -> Unit): LuaFunction<*, *, *, *, *
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> luaFunction(callable: (ExecutionContext, T) -> Unit): LuaFunction<T, *, *, *, *> {
|
||||
fun <T> luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction<T, *, *, *, *> {
|
||||
return object : AbstractFunction1<T>() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
@ -357,7 +196,7 @@ fun <T> luaFunction(callable: (ExecutionContext, T) -> Unit): LuaFunction<T, *,
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, T2> luaFunction(callable: (ExecutionContext, T, T2) -> Unit): LuaFunction<T, T2, *, *, *> {
|
||||
fun <T, T2> luaFunction(callable: ExecutionContext.(T, T2) -> Unit): LuaFunction<T, T2, *, *, *> {
|
||||
return object : AbstractFunction2<T, T2>() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
@ -369,7 +208,7 @@ fun <T, T2> luaFunction(callable: (ExecutionContext, T, T2) -> Unit): LuaFunctio
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, T2, T3> luaFunction(callable: (ExecutionContext, T, T2, T3) -> Unit): LuaFunction<T, T2, T3, *, *> {
|
||||
fun <T, T2, T3> luaFunction(callable: ExecutionContext.(T, T2, T3) -> Unit): LuaFunction<T, T2, T3, *, *> {
|
||||
return object : AbstractFunction3<T, T2, T3>() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
@ -381,7 +220,7 @@ fun <T, T2, T3> luaFunction(callable: (ExecutionContext, T, T2, T3) -> Unit): Lu
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, T2, T3, T4> luaFunction(callable: (ExecutionContext, T, T2, T3, T4) -> Unit): LuaFunction<T, T2, T3, T4, *> {
|
||||
fun <T, T2, T3, T4> luaFunction(callable: ExecutionContext.(T, T2, T3, T4) -> Unit): LuaFunction<T, T2, T3, T4, *> {
|
||||
return object : AbstractFunction4<T, T2, T3, T4>() {
|
||||
override fun resume(context: ExecutionContext, suspendedState: Any) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
|
264
src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt
Normal file
264
src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaEnvironment.kt
Normal file
@ -0,0 +1,264 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaObject
|
||||
import org.classdump.luna.LuaType
|
||||
import org.classdump.luna.StateContext
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Variable
|
||||
import org.classdump.luna.env.RuntimeEnvironments
|
||||
import org.classdump.luna.exec.DirectCallExecutor
|
||||
import org.classdump.luna.impl.DefaultTable
|
||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
import org.classdump.luna.lib.BasicLib
|
||||
import org.classdump.luna.lib.CoroutineLib
|
||||
import org.classdump.luna.lib.MathLib
|
||||
import org.classdump.luna.lib.OsLib
|
||||
import org.classdump.luna.lib.StringLib
|
||||
import org.classdump.luna.lib.TableLib
|
||||
import org.classdump.luna.lib.Utf8Lib
|
||||
import org.classdump.luna.load.ChunkFactory
|
||||
import org.classdump.luna.runtime.AbstractFunction1
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
|
||||
|
||||
class LuaEnvironment : StateContext {
|
||||
private var nilMeta: Table? = null
|
||||
private var booleanMeta: Table? = null
|
||||
private var numberMeta: Table? = null
|
||||
private var stringMeta: Table? = null
|
||||
private var functionMeta: Table? = null
|
||||
private var threadMeta: Table? = null
|
||||
private var lightUserdataMeta: Table? = null
|
||||
|
||||
override fun getNilMetatable(): Table? {
|
||||
return nilMeta
|
||||
}
|
||||
|
||||
override fun getBooleanMetatable(): Table? {
|
||||
return booleanMeta
|
||||
}
|
||||
|
||||
override fun getNumberMetatable(): Table? {
|
||||
return numberMeta
|
||||
}
|
||||
|
||||
override fun getStringMetatable(): Table? {
|
||||
return stringMeta
|
||||
}
|
||||
|
||||
override fun getFunctionMetatable(): Table? {
|
||||
return functionMeta
|
||||
}
|
||||
|
||||
override fun getThreadMetatable(): Table? {
|
||||
return threadMeta
|
||||
}
|
||||
|
||||
override fun getLightUserdataMetatable(): Table? {
|
||||
return lightUserdataMeta
|
||||
}
|
||||
|
||||
override fun getMetatable(instance: Any?): Table? {
|
||||
if (instance is LuaObject)
|
||||
return instance.metatable
|
||||
|
||||
return when (val type = LuaType.typeOf(instance)!!) {
|
||||
LuaType.NIL -> nilMeta
|
||||
LuaType.BOOLEAN -> booleanMeta
|
||||
LuaType.NUMBER -> numberMeta
|
||||
LuaType.STRING -> stringMeta
|
||||
LuaType.FUNCTION -> functionMeta
|
||||
LuaType.USERDATA -> lightUserdataMeta
|
||||
LuaType.THREAD -> threadMeta
|
||||
else -> throw IllegalArgumentException("Illegal type: $type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNilMetatable(table: Table?): Table? {
|
||||
val old = nilMeta; nilMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setBooleanMetatable(table: Table?): Table? {
|
||||
val old = booleanMeta; booleanMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setNumberMetatable(table: Table?): Table? {
|
||||
val old = numberMeta; numberMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setStringMetatable(table: Table?): Table? {
|
||||
val old = stringMeta; stringMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setFunctionMetatable(table: Table?): Table? {
|
||||
val old = functionMeta; functionMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setThreadMetatable(table: Table?): Table? {
|
||||
val old = threadMeta; threadMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setLightUserdataMetatable(table: Table?): Table? {
|
||||
val old = lightUserdataMeta; lightUserdataMeta = table; return old
|
||||
}
|
||||
|
||||
override fun setMetatable(instance: Any?, table: Table?): Table? {
|
||||
if (instance is LuaObject)
|
||||
return instance.setMetatable(table)
|
||||
else
|
||||
throw IllegalArgumentException("Can not set metatable of ${LuaType.typeOf(instance)}")
|
||||
}
|
||||
|
||||
override fun newTable(): Table {
|
||||
return DefaultTable()
|
||||
}
|
||||
|
||||
override fun newTable(array: Int, hash: Int): Table {
|
||||
return DefaultTable()
|
||||
}
|
||||
|
||||
val globals: Table = newTable()
|
||||
val executor: DirectCallExecutor = DirectCallExecutor.newExecutor()
|
||||
|
||||
init {
|
||||
globals["_G"] = globals
|
||||
|
||||
globals["assert"] = BasicLib.assertFn()
|
||||
globals["error"] = BasicLib.error()
|
||||
globals["getmetatable"] = BasicLib.getmetatable()
|
||||
globals["ipairs"] = BasicLib.ipairs()
|
||||
globals["next"] = BasicLib.next()
|
||||
globals["pairs"] = BasicLib.pairs()
|
||||
globals["pcall"] = BasicLib.pcall()
|
||||
|
||||
globals["rawequal"] = BasicLib.rawequal()
|
||||
globals["rawget"] = BasicLib.rawget()
|
||||
globals["rawlen"] = BasicLib.rawlen()
|
||||
globals["rawset"] = BasicLib.rawset()
|
||||
globals["select"] = BasicLib.select()
|
||||
globals["setmetatable"] = BasicLib.setmetatable()
|
||||
globals["tostring"] = BasicLib.tostring()
|
||||
globals["tonumber"] = BasicLib.tonumber()
|
||||
globals["type"] = BasicLib.type()
|
||||
globals["_VERSION"] = BasicLib._VERSION
|
||||
globals["xpcall"] = BasicLib.xpcall()
|
||||
|
||||
globals["print"] = PrintFunction(globals)
|
||||
|
||||
globals["require"] = LuaRequire()
|
||||
|
||||
// why not use _ENV anyway lol
|
||||
globals["self"] = newTable()
|
||||
|
||||
CoroutineLib.installInto(this, globals)
|
||||
TableLib.installInto(this, globals)
|
||||
MathLib.installInto(this, globals)
|
||||
StringLib.installInto(this, globals)
|
||||
OsLib.installInto(this, globals, RuntimeEnvironments.system())
|
||||
|
||||
// TODO: NYI, maybe polyfill?
|
||||
Utf8Lib.installInto(this, globals)
|
||||
|
||||
provideRootBindings(this)
|
||||
provideUtilityBindings(this)
|
||||
}
|
||||
|
||||
private val scripts = ObjectArraySet<ChunkFactory>()
|
||||
private var initCalled = false
|
||||
private val loadedScripts = ObjectArraySet<String>()
|
||||
|
||||
inner class LuaRequire : AbstractFunction1<ByteString>() {
|
||||
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, arg1: ByteString) {
|
||||
val name = arg1.decode()
|
||||
|
||||
if (loadedScripts.add(name)) {
|
||||
val script = Starbound.loadScript(name)
|
||||
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun attach(script: ChunkFactory) {
|
||||
scripts.add(script)
|
||||
}
|
||||
|
||||
fun attach(scripts: Collection<AssetPath>) {
|
||||
for (script in scripts) {
|
||||
attach(Starbound.loadScript(script.fullPath))
|
||||
}
|
||||
}
|
||||
|
||||
fun run(chunk: ChunkFactory): Array<out Any?> {
|
||||
return executor.call(this, chunk.newInstance(Variable(globals)))
|
||||
}
|
||||
|
||||
var errorState = false
|
||||
private set
|
||||
|
||||
fun markErrored() {
|
||||
errorState = true
|
||||
}
|
||||
|
||||
fun init(): Boolean {
|
||||
check(!initCalled) { "Already called init()" }
|
||||
initCalled = true
|
||||
|
||||
for (script in scripts) {
|
||||
try {
|
||||
executor.call(this, script.newInstance(Variable(globals)))
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
LOGGER.error("Failed to attach script to environment", err)
|
||||
scripts.clear()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
scripts.clear()
|
||||
|
||||
val init = globals["init"]
|
||||
|
||||
if (init is LuaFunction<*, *, *, *, *>) {
|
||||
try {
|
||||
executor.call(this, init)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
LOGGER.error("Exception on init()", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun invokeGlobal(name: String) {
|
||||
if (errorState)
|
||||
return
|
||||
|
||||
val load = globals[name]
|
||||
|
||||
if (load is LuaFunction<*, *, *, *, *>) {
|
||||
try {
|
||||
executor.call(this, load)
|
||||
} catch (err: Throwable) {
|
||||
errorState = true
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.exec.CallPausedException
|
||||
import org.classdump.luna.runtime.LuaFunction
|
||||
|
||||
class LuaMessageHandler(val lua: LuaEnvironment) {
|
||||
private val handlers = HashMap<String, LuaFunction<*, *, *, *, *>>()
|
||||
|
||||
init {
|
||||
val table = lua.newTable()
|
||||
|
||||
table["setHandler"] = luaFunction { name: ByteString, handler: LuaFunction<*, *, *, *, *>? ->
|
||||
if (handler == null) {
|
||||
handlers.remove(name.decode())
|
||||
} else {
|
||||
handlers[name.decode()] = handler
|
||||
}
|
||||
}
|
||||
|
||||
lua.globals["message"] = table
|
||||
}
|
||||
|
||||
fun handle(message: String, isLocal: Boolean, parameters: JsonArray): JsonElement {
|
||||
if (lua.errorState)
|
||||
return JsonNull.INSTANCE
|
||||
|
||||
val handler = handlers[message] ?: return JsonNull.INSTANCE
|
||||
|
||||
try {
|
||||
val unpacked = arrayOfNulls<Any>(parameters.size() + 2)
|
||||
|
||||
unpacked[0] = ByteString.of(message)
|
||||
unpacked[1] = isLocal
|
||||
|
||||
for ((i, v) in parameters.withIndex()) {
|
||||
unpacked[i + 2] = lua.from(v)
|
||||
}
|
||||
|
||||
val result = lua.executor.call(lua, handler, *unpacked)
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return JsonNull.INSTANCE
|
||||
} else if (result.size == 1) {
|
||||
return toJsonFromLua(result[0])
|
||||
} else {
|
||||
val array = JsonArray()
|
||||
|
||||
for (v in result) {
|
||||
array.add(toJsonFromLua(v))
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
} catch (err: CallPausedException) {
|
||||
lua.markErrored()
|
||||
LOGGER.error("Message handler for $message tried to yield", err)
|
||||
return JsonNull.INSTANCE
|
||||
} catch (err: Throwable) {
|
||||
lua.markErrored()
|
||||
LOGGER.error("Message handler for $message errored", err)
|
||||
return JsonNull.INSTANCE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
import org.classdump.luna.runtime.AbstractFunction1
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
|
||||
class LuaRequire(private val state: NewLuaState) : AbstractFunction1<ByteString>() {
|
||||
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
|
||||
throw NonsuspendableFunctionException(this::class.java)
|
||||
}
|
||||
|
||||
override fun invoke(context: ExecutionContext, arg1: ByteString) {
|
||||
val name = arg1.decode()
|
||||
|
||||
val file = Starbound.locate(name)
|
||||
|
||||
if (!file.exists) {
|
||||
throw LuaRuntimeException("File $name does not exist")
|
||||
}
|
||||
|
||||
if (!file.isFile) {
|
||||
throw LuaRuntimeException("File $name is a directory")
|
||||
}
|
||||
|
||||
state.load(context, file.readToString(), file.computeFullPath())
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
|
||||
class LuaUpdateComponent(val lua: LuaEnvironment) {
|
||||
var stepCount = 1
|
||||
private var steps = 0
|
||||
|
||||
init {
|
||||
val script = lua.newTable()
|
||||
lua.globals["script"] = script
|
||||
|
||||
script["updateDt"] = luaFunction {
|
||||
returnBuffer.setTo(stepCount * Starbound.TIMESTEP)
|
||||
}
|
||||
|
||||
script["setUpdateDelta"] = luaFunction { ticks: Int ->
|
||||
stepCount = ticks
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (steps++ >= stepCount) {
|
||||
steps = 0
|
||||
lua.invokeGlobal("update")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
|
||||
import org.classdump.luna.StateContext
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Variable
|
||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||
import org.classdump.luna.compiler.CompilerSettings
|
||||
import org.classdump.luna.env.RuntimeEnvironments
|
||||
import org.classdump.luna.exec.DirectCallExecutor
|
||||
import org.classdump.luna.impl.StateContexts
|
||||
import org.classdump.luna.lib.BasicLib
|
||||
import org.classdump.luna.lib.CoroutineLib
|
||||
import org.classdump.luna.lib.MathLib
|
||||
import org.classdump.luna.lib.OsLib
|
||||
import org.classdump.luna.lib.StringLib
|
||||
import org.classdump.luna.lib.TableLib
|
||||
import org.classdump.luna.lib.Utf8Lib
|
||||
import org.classdump.luna.runtime.Dispatch
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class NewLuaState {
|
||||
val state: StateContext = StateContexts.newDefaultInstance()
|
||||
val chunkLoader: CompilerChunkLoader = CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "kstarbound_${COUNTER.getAndIncrement()}_")
|
||||
val env: Table = state.newTable()
|
||||
val executor: DirectCallExecutor = DirectCallExecutor.newExecutor()
|
||||
|
||||
init {
|
||||
env["_G"] = env
|
||||
|
||||
env["assert"] = BasicLib.assertFn()
|
||||
env["error"] = BasicLib.error()
|
||||
env["getmetatable"] = BasicLib.getmetatable()
|
||||
env["ipairs"] = BasicLib.ipairs()
|
||||
env["next"] = BasicLib.next()
|
||||
env["pairs"] = BasicLib.pairs()
|
||||
env["pcall"] = BasicLib.pcall()
|
||||
|
||||
env["rawequal"] = BasicLib.rawequal()
|
||||
env["rawget"] = BasicLib.rawget()
|
||||
env["rawlen"] = BasicLib.rawlen()
|
||||
env["rawset"] = BasicLib.rawset()
|
||||
env["select"] = BasicLib.select()
|
||||
env["setmetatable"] = BasicLib.setmetatable()
|
||||
env["tostring"] = BasicLib.tostring()
|
||||
env["tonumber"] = BasicLib.tonumber()
|
||||
env["type"] = BasicLib.type()
|
||||
env["_VERSION"] = BasicLib._VERSION
|
||||
env["xpcall"] = BasicLib.xpcall()
|
||||
|
||||
env["print"] = PrintFunction(env)
|
||||
|
||||
env["require"] = LuaRequire(this)
|
||||
|
||||
CoroutineLib.installInto(state, env)
|
||||
TableLib.installInto(state, env)
|
||||
MathLib.installInto(state, env)
|
||||
StringLib.installInto(state, env)
|
||||
OsLib.installInto(state, env, RuntimeEnvironments.system())
|
||||
|
||||
// TODO: NYI, maybe polyfill?
|
||||
Utf8Lib.installInto(state, env)
|
||||
}
|
||||
|
||||
fun load(code: String, name: String = "main chunk") {
|
||||
val main = chunkLoader.loadTextChunk(Variable(env), name, code)
|
||||
executor.call(state, main)
|
||||
}
|
||||
|
||||
fun load(context: ExecutionContext, code: String, name: String = "main chunk") {
|
||||
Dispatch.call(context, chunkLoader.loadTextChunk(Variable(env), name, code))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COUNTER = AtomicInteger()
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.collect.map
|
||||
import ru.dbotthepony.kommons.collect.toList
|
||||
import ru.dbotthepony.kommons.vector.Vector2f
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toAABB
|
||||
import ru.dbotthepony.kstarbound.lua.toColor
|
||||
import ru.dbotthepony.kstarbound.lua.toPoly
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2f
|
||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||
|
||||
fun provideAnimatorBindings(self: Animator, lua: LuaEnvironment) {
|
||||
val callbacks = lua.newTable()
|
||||
lua.globals["animator"] = callbacks
|
||||
|
||||
callbacks["setAnimationState"] = luaFunction { type: ByteString, state: ByteString, alwaysStart: Boolean? ->
|
||||
returnBuffer.setTo(self.setActiveState(type.decode(), state.decode(), alwaysStart ?: false))
|
||||
}
|
||||
|
||||
callbacks["animationState"] = luaFunction { type: ByteString ->
|
||||
returnBuffer.setTo(self.animationState(type.decode()))
|
||||
}
|
||||
|
||||
callbacks["animationStateProperty"] = luaFunction { type: ByteString, key: ByteString ->
|
||||
returnBuffer.setTo(self.stateProperty(type.decode(), key.decode()))
|
||||
}
|
||||
|
||||
callbacks["setGlobalTag"] = luaFunction { key: ByteString, value: ByteString ->
|
||||
self.setGlobalTag(key.decode(), value.decode())
|
||||
}
|
||||
|
||||
callbacks["setPartTag"] = luaFunction { part: ByteString, key: ByteString, value: ByteString ->
|
||||
self.setPartTag(part.decode(), key.decode(), value.decode())
|
||||
}
|
||||
|
||||
callbacks["setFlipped"] = luaFunction { isFlipped: Boolean, centerLine: Double? ->
|
||||
self.isFlipped = isFlipped
|
||||
self.flippedRelativeCenterLine = centerLine ?: 0.0
|
||||
}
|
||||
|
||||
callbacks["setAnimationRate"] = luaFunction { rate: Double ->
|
||||
self.animationRate = rate
|
||||
}
|
||||
|
||||
callbacks["rotateGroup"] = luaFunction { group: ByteString, rotation: Number, immediate: Boolean? ->
|
||||
self.rotateGroup(group.decode(), rotation.toDouble(), immediate ?: false)
|
||||
}
|
||||
|
||||
callbacks["currentRotationAngle"] = luaFunction { group: ByteString ->
|
||||
returnBuffer.setTo(self.currentRotationAngle(group.decode()))
|
||||
}
|
||||
|
||||
callbacks["targetRotationAngle"] = luaFunction { group: ByteString ->
|
||||
returnBuffer.setTo(self.targetRotationAngle(group.decode()))
|
||||
}
|
||||
|
||||
callbacks["hasRotationGroup"] = luaFunction { group: ByteString ->
|
||||
returnBuffer.setTo(self.hasRotationGroup(group.decode()))
|
||||
}
|
||||
|
||||
callbacks["rotationGroups"] = luaFunction {
|
||||
val groups = self.rotationGroups()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["hasTransformationGroup"] = luaFunction { group: ByteString ->
|
||||
returnBuffer.setTo(self.hasTransformationGroup(group.decode()))
|
||||
}
|
||||
|
||||
callbacks["transformationGroups"] = luaFunction {
|
||||
val groups = self.transformationGroups()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["translateTransformGroup"] = luaFunction { group: ByteString, translation: Table ->
|
||||
self.translateTransformGroup(group.decode(), toVector2f(translation))
|
||||
}
|
||||
|
||||
callbacks["rotateTransformGroup"] = luaFunction { group: ByteString, rotation: Double, center: Table? ->
|
||||
self.rotateTransformGroup(group.decode(), rotation.toFloat(), if (center == null) Vector2f.ZERO else toVector2f(center))
|
||||
}
|
||||
|
||||
callbacks["scaleTransformationGroup"] = luaFunction { group: ByteString, scale: Any, center: Table? ->
|
||||
if (scale is Number) {
|
||||
self.scaleTransformationGroup(group.decode(), Vector2f(scale.toFloat(), scale.toFloat()), if (center == null) Vector2f.ZERO else toVector2f(center))
|
||||
} else {
|
||||
self.scaleTransformationGroup(group.decode(), toVector2f(scale), if (center == null) Vector2f.ZERO else toVector2f(center))
|
||||
}
|
||||
}
|
||||
|
||||
callbacks["transformTransformationGroup"] = luaFunctionArray { arguments: Array<out Any?> ->
|
||||
val group = arguments[0] as ByteString
|
||||
val r00 = arguments[1] as Number
|
||||
val r01 = arguments[2] as Number
|
||||
val r02 = arguments[3] as Number
|
||||
val r10 = arguments[4] as Number
|
||||
val r11 = arguments[5] as Number
|
||||
val r12 = arguments[6] as Number
|
||||
|
||||
self.transformTransformationGroup(group.decode(), r00.toFloat(),
|
||||
r01.toFloat(),
|
||||
r02.toFloat(),
|
||||
r10.toFloat(),
|
||||
r11.toFloat(),
|
||||
r12.toFloat())
|
||||
}
|
||||
|
||||
callbacks["resetTransformationGroup"] = luaFunction { group: ByteString ->
|
||||
self.resetTransformationGroup(group.decode())
|
||||
}
|
||||
|
||||
callbacks["particleEmitters"] = luaFunction {
|
||||
val groups = self.particleEmitters()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["hasParticleEmitter"] = luaFunction { group: ByteString ->
|
||||
returnBuffer.setTo(self.hasParticleEmitter(group.decode()))
|
||||
}
|
||||
|
||||
callbacks["setParticleEmitterActive"] = luaFunction { emitter: ByteString, state: Boolean ->
|
||||
self.setParticleEmitterActive(emitter.decode(), state)
|
||||
}
|
||||
|
||||
callbacks["setParticleEmitterEmissionRate"] = luaFunction { emitter: ByteString, rate: Number ->
|
||||
self.setParticleEmitterEmissionRate(emitter.decode(), rate.toDouble())
|
||||
}
|
||||
|
||||
callbacks["setParticleEmitterBurstCount"] = luaFunction { emitter: ByteString, count: Number ->
|
||||
self.setParticleEmitterBurstCount(emitter.decode(), count.toInt())
|
||||
}
|
||||
|
||||
callbacks["setParticleEmitterOffsetRegion"] = luaFunction { emitter: ByteString, region: Table ->
|
||||
self.setParticleEmitterOffsetRegion(emitter.decode(), toAABB(region))
|
||||
}
|
||||
|
||||
callbacks["burstParticleEmitter"] = luaFunction { emitter: ByteString ->
|
||||
self.burstParticleEmitter(emitter.decode())
|
||||
}
|
||||
|
||||
callbacks["lights"] = luaFunction {
|
||||
val groups = self.lights()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["hasLight"] = luaFunction { light: ByteString ->
|
||||
returnBuffer.setTo(self.hasLight(light.decode()))
|
||||
}
|
||||
|
||||
callbacks["setLightActive"] = luaFunction { light: ByteString, state: Boolean ->
|
||||
self.setLightActive(light.decode(), state)
|
||||
}
|
||||
|
||||
callbacks["setLightPosition"] = luaFunction { light: ByteString, position: Table ->
|
||||
self.setLightPosition(light.decode(), toVector2d(position))
|
||||
}
|
||||
|
||||
callbacks["setLightColor"] = luaFunction { light: ByteString, color: Table ->
|
||||
self.setLightColor(light.decode(), toColor(color))
|
||||
}
|
||||
|
||||
callbacks["setLightPointAngle"] = luaFunction { light: ByteString, angle: Number ->
|
||||
self.setLightPointAngle(light.decode(), angle.toDouble())
|
||||
}
|
||||
|
||||
callbacks["sounds"] = luaFunction {
|
||||
val groups = self.sounds()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["hasSound"] = luaFunction { sound: ByteString ->
|
||||
returnBuffer.setTo(self.hasSound(sound.decode()))
|
||||
}
|
||||
|
||||
callbacks["setSoundPool"] = luaFunction { sound: ByteString, sounds: Table ->
|
||||
self.setSoundPool(sound.decode(), sounds.iterator().map { (it.value as ByteString).decode() }.toList())
|
||||
}
|
||||
|
||||
callbacks["setSoundPosition"] = luaFunction { sound: ByteString, position: Table ->
|
||||
self.setSoundPosition(sound.decode(), toVector2d(position))
|
||||
}
|
||||
|
||||
callbacks["playSound"] = luaFunction { sound: ByteString, loops: Number? ->
|
||||
self.playSound(sound.decode(), loops?.toInt() ?: 0)
|
||||
}
|
||||
|
||||
callbacks["setSoundVolume"] = luaFunction { sound: ByteString, volume: Number, rampTime: Number? ->
|
||||
self.setSoundVolume(sound.decode(), volume.toDouble(), rampTime?.toDouble() ?: 0.0)
|
||||
}
|
||||
|
||||
callbacks["setSoundPitch"] = luaFunction { sound: ByteString, pitch: Number, rampTime: Number? ->
|
||||
self.setSoundPitch(sound.decode(), pitch.toDouble(), rampTime?.toDouble() ?: 0.0)
|
||||
}
|
||||
|
||||
callbacks["stopAllSounds"] = luaFunction { sound: ByteString, rampTime: Number? ->
|
||||
self.stopAllSounds(sound.decode(), rampTime?.toDouble() ?: 0.0)
|
||||
}
|
||||
|
||||
callbacks["effects"] = luaFunction {
|
||||
val groups = self.effects()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["hasEffect"] = luaFunction { effect: ByteString ->
|
||||
returnBuffer.setTo(self.hasEffect(effect.decode()))
|
||||
}
|
||||
|
||||
callbacks["setEffectActive"] = luaFunction { effect: ByteString, state: Boolean ->
|
||||
self.setEffectActive(effect.decode(), state)
|
||||
}
|
||||
|
||||
callbacks["parts"] = luaFunction {
|
||||
val groups = self.parts()
|
||||
val keys = newTable(groups.size, 0)
|
||||
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
|
||||
returnBuffer.setTo(keys)
|
||||
}
|
||||
|
||||
callbacks["partPoint"] = luaFunction { part: ByteString, property: ByteString ->
|
||||
returnBuffer.setTo(self.partPoint(part.decode(), property.decode())?.let { from(it) })
|
||||
}
|
||||
|
||||
callbacks["partPoly"] = luaFunction { part: ByteString, property: ByteString ->
|
||||
returnBuffer.setTo(self.partPoly(part.decode(), property.decode())?.let { from(it) })
|
||||
}
|
||||
|
||||
callbacks["partProperty"] = luaFunction { part: ByteString, property: ByteString ->
|
||||
returnBuffer.setTo(from(self.partProperty(part.decode(), property.decode())))
|
||||
}
|
||||
|
||||
callbacks["transformPoint"] = luaFunction { point: Table, part: ByteString ->
|
||||
returnBuffer.setTo(from(toVector2d(point).times(self.partTransformation(part.decode()))))
|
||||
}
|
||||
|
||||
callbacks["transformPoly"] = luaFunction { poly: Table, part: ByteString ->
|
||||
returnBuffer.setTo(from(toPoly(poly).transform(self.partTransformation(part.decode()))))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package ru.dbotthepony.kstarbound.lua
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
@ -7,6 +7,7 @@ import org.classdump.luna.impl.NonsuspendableFunctionException
|
||||
import org.classdump.luna.runtime.AbstractFunction1
|
||||
import org.classdump.luna.runtime.ExecutionContext
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
|
||||
class AssetJsonFunction(private val tables: TableFactory) : AbstractFunction1<ByteString>() {
|
||||
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
|
@ -0,0 +1,42 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
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.world.entities.AbstractEntity
|
||||
|
||||
fun provideEntityBindings(self: AbstractEntity, lua: LuaEnvironment) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["entity"] = table
|
||||
|
||||
table["id"] = luaFunction { returnBuffer.setTo(self.entityID) }
|
||||
table["position"] = luaFunction { returnBuffer.setTo(from(self.position)) }
|
||||
table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName) }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().orNull()) }
|
||||
table["persistent"] = luaFunction { returnBuffer.setTo(self.isPersistent) }
|
||||
|
||||
table["entityInSight"] = luaFunction { TODO() }
|
||||
table["isValidTarget"] = luaFunction { TODO() }
|
||||
|
||||
table["damageTeam"] = luaFunction {
|
||||
val result = newTable()
|
||||
|
||||
result["team"] = self.team.get().team
|
||||
result["type"] = self.type.jsonName
|
||||
|
||||
returnBuffer.setTo(result)
|
||||
}
|
||||
|
||||
table["distanceToEntity"] = luaFunction { entity: Number ->
|
||||
val find = self.world.entities[entity.toInt()]
|
||||
|
||||
if (find != null) {
|
||||
returnBuffer.setTo(from(self.world.geometry.diff(find.position, self.position)))
|
||||
} else {
|
||||
returnBuffer.setTo(from(Vector2d.ZERO))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
@ -14,10 +15,15 @@ import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.lua.AssetJsonFunction
|
||||
import ru.dbotthepony.kstarbound.lua.NewLuaState
|
||||
import ru.dbotthepony.kstarbound.lua.LUA_HINT_ARRAY
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.StateMachine
|
||||
import ru.dbotthepony.kstarbound.lua.createJsonArray
|
||||
import ru.dbotthepony.kstarbound.lua.createJsonObject
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||
import ru.dbotthepony.kstarbound.lua.indexSetNoYield
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||
@ -26,12 +32,14 @@ import ru.dbotthepony.kstarbound.lua.luaStub
|
||||
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
|
||||
import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toLuaInteger
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.random
|
||||
import kotlin.collections.set
|
||||
import kotlin.collections.withIndex
|
||||
import kotlin.math.max
|
||||
|
||||
private fun <T : Any> lookup(registry: Registry<T>, key: Any?): Registry.Entry<T>? {
|
||||
if (key is ByteString) {
|
||||
@ -94,30 +102,30 @@ private fun nonEmptyRegion(context: ExecutionContext, name: ByteString) {
|
||||
}
|
||||
|
||||
private fun registryDef(registry: Registry<*>): LuaFunction<ByteString, *, *, *, *> {
|
||||
return luaFunction { context, name ->
|
||||
return luaFunction { name ->
|
||||
val value = registry[name.decode()] ?: throw LuaRuntimeException("No such NPC type $name")
|
||||
context.returnBuffer.setTo(context.from(value.json))
|
||||
returnBuffer.setTo(from(value.json))
|
||||
}
|
||||
}
|
||||
|
||||
private fun registryDef2(registry: Registry<*>): LuaFunction<Any?, *, *, *, *> {
|
||||
return luaFunction { context, name ->
|
||||
return luaFunction { name ->
|
||||
val def = lookup(registry, name)
|
||||
|
||||
if (def != null) {
|
||||
context.returnBuffer.setTo(context.newTable(0, 2).also {
|
||||
returnBuffer.setTo(newTable(0, 2).also {
|
||||
it["path"] = def.file?.computeFullPath()
|
||||
it["config"] = context.from(def.json)
|
||||
it["config"] = from(def.json)
|
||||
})
|
||||
} else {
|
||||
context.returnBuffer.setTo()
|
||||
returnBuffer.setTo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registryDefExists(registry: Registry<*>): LuaFunction<ByteString, *, *, *, *> {
|
||||
return luaFunction { context, name ->
|
||||
context.returnBuffer.setTo(name.decode() in registry)
|
||||
return luaFunction { name ->
|
||||
returnBuffer.setTo(name.decode() in registry)
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,11 +286,105 @@ private fun techConfig(context: ExecutionContext, arguments: ArgumentIterator) {
|
||||
context.returnBuffer.setTo(lookupStrict(Registries.techs, arguments.nextAny()).json)
|
||||
}
|
||||
|
||||
fun provideRootBindings(state: NewLuaState) {
|
||||
val table = state.state.newTable()
|
||||
state.env["root"] = table
|
||||
private val jobject = luaFunction { returnBuffer.setTo(createJsonObject()) }
|
||||
private val jarray = luaFunction { returnBuffer.setTo(createJsonArray()) }
|
||||
|
||||
table["assetJson"] = AssetJsonFunction(state.state)
|
||||
private val jremove = luaFunction { self: Table, key: Any ->
|
||||
val nils = self.metatable?.rawget("__nils") as? Table
|
||||
|
||||
if (nils != null) {
|
||||
nils[key] = 0L
|
||||
}
|
||||
|
||||
self[key] = null as Any?
|
||||
}
|
||||
|
||||
private val jsize = luaFunction { self: Table ->
|
||||
var elemCount = 0L
|
||||
var highestIndex = 0L
|
||||
var hintList = false
|
||||
|
||||
val meta = self.metatable
|
||||
|
||||
if (meta != null) {
|
||||
if (meta["__typehint"] == LUA_HINT_ARRAY) {
|
||||
hintList = true
|
||||
}
|
||||
|
||||
val nils = meta["__nils"]
|
||||
|
||||
if (nils is Table) {
|
||||
for ((k, v) in nils) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null) {
|
||||
highestIndex = max(ik, highestIndex)
|
||||
} else {
|
||||
hintList = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((k, v) in self) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null) {
|
||||
highestIndex = max(ik, highestIndex)
|
||||
} else {
|
||||
hintList = false
|
||||
}
|
||||
|
||||
elemCount++
|
||||
}
|
||||
|
||||
if (hintList) {
|
||||
returnBuffer.setTo(highestIndex)
|
||||
} else {
|
||||
returnBuffer.setTo(elemCount)
|
||||
}
|
||||
}
|
||||
|
||||
// why is this a thing?
|
||||
private val jresize = luaFunction { self: Table, target: Long ->
|
||||
val nils = self.metatable?.rawget("__nils") as? Table
|
||||
|
||||
if (nils != null) {
|
||||
val keysToRemove = ArrayList<Any>()
|
||||
|
||||
for ((k, v) in nils) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null && ik > 0L && ik > target)
|
||||
keysToRemove.add(k)
|
||||
}
|
||||
|
||||
for (k in keysToRemove) {
|
||||
nils[k] = null as Any?
|
||||
}
|
||||
}
|
||||
|
||||
val keysToRemove = ArrayList<Any>()
|
||||
|
||||
for ((k, v) in self) {
|
||||
val ik = k.toLuaInteger()
|
||||
|
||||
if (ik != null && ik > 0L && ik > target)
|
||||
keysToRemove.add(k)
|
||||
}
|
||||
|
||||
for (k in keysToRemove) {
|
||||
self[k] = null as Any?
|
||||
}
|
||||
|
||||
indexSetNoYield(self, target, indexNoYield(self, target))
|
||||
}
|
||||
|
||||
fun provideRootBindings(lua: LuaEnvironment) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["root"] = table
|
||||
|
||||
table["assetJson"] = AssetJsonFunction(lua)
|
||||
table["makeCurrentVersionedJson"] = luaStub("makeCurrentVersionedJson")
|
||||
table["loadVersionedJson"] = luaStub("loadVersionedJson")
|
||||
|
||||
@ -346,6 +448,9 @@ fun provideRootBindings(state: NewLuaState) {
|
||||
table["dungeonMetadata"] = luaStub("dungeonMetadata")
|
||||
table["behavior"] = luaStub("behavior")
|
||||
|
||||
state.env["jobject"] = luaFunction { executionContext -> executionContext.returnBuffer.setTo(executionContext.newTable()) }
|
||||
state.env["jarray"] = luaFunction { executionContext -> executionContext.returnBuffer.setTo(executionContext.newTable()) }
|
||||
lua.globals["jobject"] = jobject
|
||||
lua.globals["jarray"] = jarray
|
||||
lua.globals["jremove"] = jremove
|
||||
lua.globals["jsize"] = jsize
|
||||
lua.globals["jresize"] = jresize
|
||||
}
|
||||
|
@ -0,0 +1,135 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandomGenerator
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.util.random.nextNormalDouble
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom32
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomLong
|
||||
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||
import java.util.*
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
private val logInfo = luaFunctionN("logInfo") { args ->
|
||||
LOGGER.info(args.nextString().toString().format(*args.copyRemaining()))
|
||||
}
|
||||
|
||||
private val logWarn = luaFunctionN("logWarn") { args ->
|
||||
LOGGER.warn(args.nextString().toString().format(*args.copyRemaining()))
|
||||
}
|
||||
|
||||
private val logError = luaFunctionN("logError") { args ->
|
||||
LOGGER.error(args.nextString().toString().format(*args.copyRemaining()))
|
||||
}
|
||||
|
||||
private val interpolateSinEase = luaFunctionArray { args ->
|
||||
if (args.size < 3)
|
||||
throw IllegalArgumentException("Invalid amount of arguments to interpolateSinEase: ${args.size}")
|
||||
|
||||
val offset = args[0] as? Number ?: throw IllegalArgumentException("Invalid 'offset' argument: ${args[0]}")
|
||||
|
||||
if (args[1] is Number && args[2] is Number) {
|
||||
returnBuffer.setTo(Interpolator.Sin.interpolate(offset.toDouble(), (args[1] as Number).toDouble(), (args[2] as Number).toDouble()))
|
||||
} else {
|
||||
// assume vectors
|
||||
val a = toVector2d(args[1]!!)
|
||||
val b = toVector2d(args[2]!!)
|
||||
|
||||
val result = Vector2d(
|
||||
Interpolator.Sin.interpolate(offset.toDouble(), a.x, b.x),
|
||||
Interpolator.Sin.interpolate(offset.toDouble(), a.y, b.y),
|
||||
)
|
||||
|
||||
returnBuffer.setTo(from(result))
|
||||
}
|
||||
}
|
||||
|
||||
private val replaceTags = luaFunction { string: ByteString, tags: Table ->
|
||||
returnBuffer.setTo(SBPattern.of(string.toString()).resolveOrSkip({ tags[it]?.toString() }))
|
||||
}
|
||||
|
||||
private val makePerlinSource = luaFunction { settings: Table ->
|
||||
returnBuffer.setTo(LuaPerlinNoise(AbstractPerlinNoise.of(Starbound.gson.fromJson(settings.toJson(), PerlinNoiseParameters::class.java))))
|
||||
}
|
||||
|
||||
private val staticRandomI32 = luaFunctionArray {
|
||||
returnBuffer.setTo(staticRandom32(*it))
|
||||
}
|
||||
|
||||
private val staticRandomDouble = luaFunctionArray {
|
||||
returnBuffer.setTo(staticRandomDouble(*it))
|
||||
}
|
||||
|
||||
private val staticRandomDoubleRange = luaFunctionN("staticRandomDoubleRange") {
|
||||
val min = it.nextFloat()
|
||||
val max = it.nextFloat()
|
||||
returnBuffer.setTo(staticRandomDouble(*it.copyRemaining()) * (max - min) + min)
|
||||
}
|
||||
|
||||
private val staticRandomI32Range = luaFunctionN("staticRandomI32Range") {
|
||||
val min = it.nextInteger()
|
||||
val max = it.nextInteger()
|
||||
returnBuffer.setTo(staticRandomLong(min, max, *it.copyRemaining()))
|
||||
}
|
||||
|
||||
fun provideUtilityBindings(
|
||||
lua: LuaEnvironment,
|
||||
random: RandomGenerator = random()
|
||||
) {
|
||||
val table = lua.newTable()
|
||||
lua.globals["sb"] = table
|
||||
|
||||
table["makeUuid"] = luaFunction {
|
||||
returnBuffer.setTo(UUID(random.nextLong(), random.nextLong()).toStarboundString())
|
||||
}
|
||||
|
||||
table["logInfo"] = logInfo
|
||||
table["logWarn"] = logWarn
|
||||
table["logError"] = logError
|
||||
|
||||
table["nrand"] = luaFunctionN("nrand") { args ->
|
||||
val stdev = args.nextOptionalFloat() ?: 1.0
|
||||
val mean = args.nextOptionalFloat() ?: 0.0
|
||||
random.nextNormalDouble(stdev, mean)
|
||||
}
|
||||
|
||||
table["print"] = lua.globals["tostring"]
|
||||
table["printJson"] = lua.globals["tostring"]
|
||||
table["interpolateSinEase"] = interpolateSinEase
|
||||
table["replaceTags"] = replaceTags
|
||||
table["makeRandomSource"] = luaFunction { seed: Long? ->
|
||||
returnBuffer.setTo(LuaRandomGenerator(random(seed ?: random.nextLong())))
|
||||
}
|
||||
|
||||
table["makePerlinSource"] = makePerlinSource
|
||||
|
||||
table["staticRandomI32"] = staticRandomI32
|
||||
table["staticRandomI64"] = staticRandomI32
|
||||
table["staticRandomDouble"] = staticRandomDouble
|
||||
|
||||
table["staticRandomDoubleRange"] = staticRandomDoubleRange
|
||||
table["staticRandomI32Range"] = staticRandomI32Range
|
||||
table["staticRandomI64Range"] = staticRandomI32Range
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
|
||||
fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
val callbacks = lua.newTable()
|
||||
lua.globals["world"] = callbacks
|
||||
|
||||
callbacks["flyingType"] = luaFunction {
|
||||
returnBuffer.setTo(self.sky.flyingType.jsonName)
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import com.google.gson.JsonPrimitive
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.DamageSource
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
|
||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.indexNoYield
|
||||
import ru.dbotthepony.kstarbound.lua.iterator
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toColor
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2d
|
||||
import ru.dbotthepony.kstarbound.lua.toVector2i
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
|
||||
fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) {
|
||||
val config = lua.newTable()
|
||||
lua.globals["config"] = config
|
||||
|
||||
config["getParameter"] = luaFunction { name: ByteString, default: Any? ->
|
||||
val path = JsonPath.query(name.decode())
|
||||
val find = self.lookupProperty(path)
|
||||
|
||||
if (find.isJsonNull) {
|
||||
returnBuffer.setTo(default)
|
||||
} else {
|
||||
returnBuffer.setTo(from(find))
|
||||
}
|
||||
}
|
||||
|
||||
val table = lua.newTable()
|
||||
lua.globals["object"] = table
|
||||
|
||||
table["name"] = luaFunction { returnBuffer.setTo(self.config.key) }
|
||||
table["direction"] = luaFunction { returnBuffer.setTo(self.direction.luaValue) }
|
||||
table["position"] = luaFunction { returnBuffer.setTo(from(self.tilePosition)) }
|
||||
table["setInteractive"] = luaFunction { interactive: Boolean -> self.isInteractive = interactive }
|
||||
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().orNull()) }
|
||||
table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(KOptional.ofNullable(id?.decode())) }
|
||||
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
|
||||
|
||||
// original engine parity, it returns occupied spaces in local coordinates
|
||||
table["spaces"] = luaFunction { returnBuffer.setTo(from(self.occupySpaces.map { from(it - self.tilePosition) })) }
|
||||
|
||||
table["setProcessingDirectives"] = luaFunction { directives: ByteString -> self.animator.processingDirectives = directives.decode() }
|
||||
table["setSoundEffectEnabled"] = luaFunction { state: Boolean -> self.soundEffectEnabled = state }
|
||||
table["smash"] = luaFunction { smash: Boolean? -> self.callBreak(smash ?: false) }
|
||||
table["level"] = luaFunction { returnBuffer.setTo(self.lookupProperty(JsonPath("level")) { JsonPrimitive(self.world.template.threatLevel) }.asDouble) }
|
||||
table["toAbsolutePosition"] = luaFunction { pos: Table -> returnBuffer.setTo(from(toVector2d(pos) + self.position)) }
|
||||
|
||||
table["say"] = luaFunction { line: ByteString, tags: Table?, config: Table ->
|
||||
if (tags == null) {
|
||||
if (line.isEmpty) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
self.addChatMessage(line.decode(), config.toJson())
|
||||
returnBuffer.setTo(true)
|
||||
}
|
||||
} else {
|
||||
if (line.isEmpty) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
self.addChatMessage(SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() }), config.toJson())
|
||||
returnBuffer.setTo(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table?, config: Table ->
|
||||
if (tags == null) {
|
||||
if (line.isEmpty) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
self.addChatMessage(line.decode(), config.toJson(), portrait.decode())
|
||||
returnBuffer.setTo(true)
|
||||
}
|
||||
} else {
|
||||
if (line.isEmpty) {
|
||||
returnBuffer.setTo(false)
|
||||
} else {
|
||||
self.addChatMessage(SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() }), config.toJson(), portrait.decode())
|
||||
returnBuffer.setTo(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table["isTouching"] = luaFunction { entity: Number ->
|
||||
val find = self.world.entities[entity]
|
||||
|
||||
if (find != null) {
|
||||
returnBuffer.setTo(find.collisionArea.intersect(self.volumeBoundingBox))
|
||||
} else {
|
||||
returnBuffer.setTo(false)
|
||||
}
|
||||
}
|
||||
|
||||
table["setLightColor"] = luaFunction { color: Table ->
|
||||
self.lightSourceColor = toColor(color)
|
||||
}
|
||||
|
||||
table["getLightColor"] = luaFunction {
|
||||
returnBuffer.setTo(from(self.lightSourceColor))
|
||||
}
|
||||
|
||||
table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size) }
|
||||
table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size) }
|
||||
|
||||
table["getInputNodePosition"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(from(self.inputNodes[index.toInt()].position))
|
||||
}
|
||||
|
||||
table["getOutputNodePosition"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(from(self.outputNodes[index.toInt()].position))
|
||||
}
|
||||
|
||||
table["getInputNodeLevel"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(self.inputNodes[index.toInt()].state)
|
||||
}
|
||||
|
||||
table["getOutputNodeLevel"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(self.outputNodes[index.toInt()].state)
|
||||
}
|
||||
|
||||
table["isInputNodeConnected"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(self.inputNodes[index.toInt()].connections.isNotEmpty())
|
||||
}
|
||||
|
||||
table["isOutputNodeConnected"] = luaFunction { index: Long ->
|
||||
returnBuffer.setTo(self.outputNodes[index.toInt()].connections.isNotEmpty())
|
||||
}
|
||||
|
||||
table["getInputNodeIds"] = luaFunction { index: Long ->
|
||||
val results = newTable()
|
||||
|
||||
for (connection in self.inputNodes[index.toInt()].connections) {
|
||||
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation) as? WorldObject
|
||||
|
||||
if (entity != null) {
|
||||
results[entity.entityID] = connection.index
|
||||
}
|
||||
}
|
||||
|
||||
returnBuffer.setTo(results)
|
||||
}
|
||||
|
||||
table["getOutputNodeIds"] = luaFunction { index: Long ->
|
||||
val results = newTable()
|
||||
|
||||
for (connection in self.outputNodes[index.toInt()].connections) {
|
||||
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation) as? WorldObject
|
||||
|
||||
if (entity != null) {
|
||||
results[entity.entityID] = connection.index
|
||||
}
|
||||
}
|
||||
|
||||
returnBuffer.setTo(results)
|
||||
}
|
||||
|
||||
table["setOutputNodeLevel"] = luaFunction { index: Long, state: Boolean ->
|
||||
self.outputNodes[index.toInt()].state = state
|
||||
}
|
||||
|
||||
table["setAllOutputNodes"] = luaFunction { state: Boolean ->
|
||||
self.outputNodes.forEach { it.state = state }
|
||||
}
|
||||
|
||||
table["setOfferedQuests"] = luaFunction { quests: Table? ->
|
||||
self.offeredQuests.clear()
|
||||
|
||||
if (quests != null) {
|
||||
for ((_, v) in quests) {
|
||||
v as Table
|
||||
self.offeredQuests.add(Starbound.gson.fromJson(v.toJson(), QuestArcDescriptor::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table["setTurnInQuests"] = luaFunction { quests: Table? ->
|
||||
self.turnInQuests.clear()
|
||||
|
||||
if (quests != null) {
|
||||
for ((_, v) in quests) {
|
||||
self.turnInQuests.add((v as ByteString).decode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table["setConfigParameter"] = luaFunction { key: ByteString, value: Any? ->
|
||||
self.parameters[key.decode()] = toJsonFromLua(value)
|
||||
}
|
||||
|
||||
table["setAnimationParameter"] = luaFunction { key: ByteString, value: Any? ->
|
||||
self.scriptedAnimationParameters[key.decode()] = toJsonFromLua(value)
|
||||
}
|
||||
|
||||
table["setMaterialSpaces"] = luaFunction { spaces: Table ->
|
||||
self.networkedMaterialSpaces.clear()
|
||||
|
||||
for ((i, pair) in spaces) {
|
||||
pair as Table
|
||||
|
||||
val position = toVector2i(indexNoYield(pair, 1L) ?: throw NullPointerException("invalid space at $i"))
|
||||
val material = indexNoYield(pair, 2L) as? ByteString ?: throw NullPointerException("invalid space at $i")
|
||||
|
||||
self.networkedMaterialSpaces.add(position to Registries.tiles.ref(material.decode()))
|
||||
}
|
||||
|
||||
self.markSpacesDirty()
|
||||
}
|
||||
|
||||
table["setDamageSources"] = luaFunction { sources: Table? ->
|
||||
self.damageSources.clear()
|
||||
|
||||
if (sources != null) {
|
||||
for ((_, v) in sources) {
|
||||
self.damageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table["health"] = luaFunction { returnBuffer.setTo(self.health) }
|
||||
table["setHealth"] = luaFunction { health: Double -> self.health = health }
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package ru.dbotthepony.kstarbound.lua.userdata
|
||||
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
|
||||
class LuaPerlinNoise(val noise: AbstractPerlinNoise) : Userdata<AbstractPerlinNoise>() {
|
||||
private var metatable: Table? = Companion.metatable
|
||||
|
||||
override fun getMetatable(): Table? {
|
||||
return metatable
|
||||
}
|
||||
|
||||
override fun setMetatable(mt: Table?): Table? {
|
||||
val old = metatable
|
||||
metatable = mt
|
||||
return old
|
||||
}
|
||||
|
||||
override fun getUserValue(): AbstractPerlinNoise {
|
||||
return noise
|
||||
}
|
||||
|
||||
override fun setUserValue(value: AbstractPerlinNoise?): AbstractPerlinNoise {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("get", luaFunction { self: LuaPerlinNoise, x: Number, y: Number?, z: Number? ->
|
||||
if (y != null && z != null) {
|
||||
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble(), z.toDouble()])
|
||||
} else if (y != null) {
|
||||
returnBuffer.setTo(self.noise[x.toDouble(), y.toDouble()])
|
||||
} else {
|
||||
returnBuffer.setTo(self.noise[x.toDouble()])
|
||||
}
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package ru.dbotthepony.kstarbound.lua.userdata
|
||||
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Userdata
|
||||
import org.classdump.luna.impl.ImmutableTable
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunction
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
class LuaRandomGenerator(var random: RandomGenerator) : Userdata<RandomGenerator>() {
|
||||
private var metatable: Table? = Companion.metatable
|
||||
|
||||
override fun getMetatable(): Table? {
|
||||
return metatable
|
||||
}
|
||||
|
||||
override fun setMetatable(mt: Table?): Table? {
|
||||
val old = metatable
|
||||
metatable = mt
|
||||
return old
|
||||
}
|
||||
|
||||
override fun getUserValue(): RandomGenerator {
|
||||
return random
|
||||
}
|
||||
|
||||
override fun setUserValue(value: RandomGenerator?): RandomGenerator {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
fun randf(origin: Double?, bound: Double?): Double {
|
||||
if (origin != null && bound != null) {
|
||||
if (origin == bound) {
|
||||
random.nextDouble() // to keep old behavior
|
||||
return origin
|
||||
} else {
|
||||
return random.nextDouble()
|
||||
}
|
||||
} else {
|
||||
return random.nextDouble()
|
||||
}
|
||||
}
|
||||
|
||||
fun randomInt(arg1: Long, arg2: Long?): Long {
|
||||
if (arg2 == null) {
|
||||
if (arg1 == 0L) {
|
||||
random.nextLong() // to keep old behavior
|
||||
return 0L
|
||||
} else if (arg1 < 0L) {
|
||||
return random.nextLong(arg1, 0L)
|
||||
} else {
|
||||
return random.nextLong(0L, arg1)
|
||||
}
|
||||
} else {
|
||||
if (arg2 == arg1) {
|
||||
random.nextLong() // to keep old behavior
|
||||
return arg1
|
||||
} else {
|
||||
return random.nextLong(arg1, arg2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val metatable = ImmutableTable.Builder()
|
||||
.add("init", luaFunction { self: LuaRandomGenerator, seed: Long? ->
|
||||
self.random = random(seed ?: System.nanoTime())
|
||||
})
|
||||
.add("addEntropy", luaFunction { self: LuaRandomGenerator ->
|
||||
throw UnsupportedOperationException("Adding entropy is not supported on new engine. If you have legitimate usecase for this, please let us know at issue tracker or discord")
|
||||
})
|
||||
// TODO: Lua, by definition, has no unsigned numbers,
|
||||
// and before 5.3, there were only doubles, longs (integers) were added
|
||||
// in 5.3
|
||||
.add("randu32", luaFunction { self: LuaRandomGenerator ->
|
||||
returnBuffer.setTo(self.random.nextLong(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()))
|
||||
})
|
||||
.add("randi32", luaFunction { self: LuaRandomGenerator ->
|
||||
returnBuffer.setTo(self.random.nextLong(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()))
|
||||
})
|
||||
.add("randu64", luaFunction { self: LuaRandomGenerator ->
|
||||
returnBuffer.setTo(self.random.nextLong())
|
||||
})
|
||||
.add("randi64", luaFunction { self: LuaRandomGenerator ->
|
||||
returnBuffer.setTo(self.random.nextLong())
|
||||
})
|
||||
.add("randf", luaFunction { self: LuaRandomGenerator, origin: Double?, bound: Double? ->
|
||||
returnBuffer.setTo(self.randf(origin, bound))
|
||||
})
|
||||
.add("randd", luaFunction { self: LuaRandomGenerator, origin: Double?, bound: Double? ->
|
||||
returnBuffer.setTo(self.randf(origin, bound))
|
||||
})
|
||||
.add("randb", luaFunction { self: LuaRandomGenerator ->
|
||||
returnBuffer.setTo(self.random.nextBoolean())
|
||||
})
|
||||
.add("randInt", luaFunction { self: LuaRandomGenerator, arg1: Long, arg2: Long? ->
|
||||
returnBuffer.setTo(self.randomInt(arg1, arg2))
|
||||
})
|
||||
.add("randUInt", luaFunction { self: LuaRandomGenerator, arg1: Long, arg2: Long? ->
|
||||
returnBuffer.setTo(self.randomInt(arg1, arg2))
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
@ -511,9 +511,9 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
scope.launch { shipFlightEventLoop() }
|
||||
scope.launch { warpEventLoop() }
|
||||
|
||||
//if (server.channels.connections.size > 1) {
|
||||
// enqueueWarp(WarpAction.Player(server.channels.connections.first().uuid!!))
|
||||
//}
|
||||
if (server.channels.connections.size > 1) {
|
||||
enqueueWarp(WarpAction.Player(server.channels.connections.first().uuid!!))
|
||||
}
|
||||
}
|
||||
}.exceptionally {
|
||||
LOGGER.error("Error while initializing shipworld for $this", it)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
@ -536,7 +535,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
val unloadable = world.entityIndex
|
||||
.query(
|
||||
aabbd,
|
||||
filter = Predicate { it.isApplicableForUnloading && !it.isRemote && aabbd.isInside(it.position) },
|
||||
filter = Predicate { it.isPersistent && !it.isRemote && aabbd.isInside(it.position) },
|
||||
withEdges = false)
|
||||
|
||||
world.storage.saveCells(pos, copyCells())
|
||||
|
@ -57,6 +57,48 @@ class SBPattern private constructor(
|
||||
return hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Different from regular resolve that this always returns non-null string,
|
||||
* even if we didn't replace all tags (not replaced tags appear as `<tags>` or as [defaultValue],
|
||||
* depending on [replaceWithDefault])
|
||||
*/
|
||||
fun resolveOrSkip(values: (String) -> String?, replaceWithDefault: Boolean = false, defaultValue: String = ""): String {
|
||||
if (namesSet.isEmpty()) {
|
||||
return raw
|
||||
} else if (pieces.size == 1) {
|
||||
val resolve = pieces[0].resolve(values, this::getParam)
|
||||
?: if (replaceWithDefault) {
|
||||
return defaultValue
|
||||
} else {
|
||||
return raw
|
||||
}
|
||||
|
||||
return resolve
|
||||
}
|
||||
|
||||
val buffer = ArrayList<String>(pieces.size)
|
||||
|
||||
for (piece in pieces) {
|
||||
var resolve = piece.resolve(values, this::getParam)
|
||||
|
||||
if (resolve == null) {
|
||||
if (replaceWithDefault) {
|
||||
resolve = defaultValue
|
||||
} else {
|
||||
resolve = "<${piece.name!!}>"
|
||||
}
|
||||
}
|
||||
|
||||
buffer.add(resolve)
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for (piece in buffer) count += piece.length
|
||||
val builder = StringBuilder(count)
|
||||
for (piece in buffer) builder.append(piece)
|
||||
return String(builder)
|
||||
}
|
||||
|
||||
fun resolve(values: (String) -> String?): String? {
|
||||
if (namesSet.isEmpty()) {
|
||||
return raw
|
||||
@ -146,7 +188,7 @@ class SBPattern private constructor(
|
||||
check(!(name != null && contents != null)) { "Both name and contents are not null" }
|
||||
}
|
||||
|
||||
fun resolve(map0: (String) -> String?, map1: (String) -> String?): String? {
|
||||
inline fun resolve(map0: (String) -> String?, map1: (String) -> String?): String? {
|
||||
return contents ?: map0.invoke(name!!) ?: map1.invoke(name!!)
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ private fun toBytes(accept: ByteConsumer, value: Float) {
|
||||
toBytes(accept, value.toBits())
|
||||
}
|
||||
|
||||
fun staticRandom32(vararg values: Any): Int {
|
||||
fun staticRandom32(vararg values: Any?): Int {
|
||||
val digest = XXHash32(2938728349.toInt())
|
||||
|
||||
for (value in values) {
|
||||
@ -68,6 +68,7 @@ fun staticRandom32(vararg values: Any): Int {
|
||||
is Long -> toBytes(digest::update, value)
|
||||
is Double -> toBytes(digest::update, value)
|
||||
is Float -> toBytes(digest::update, value)
|
||||
null -> {} // do nothing?
|
||||
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
@ -75,20 +76,25 @@ fun staticRandom32(vararg values: Any): Int {
|
||||
return digest.digestAsInt()
|
||||
}
|
||||
|
||||
fun staticRandomFloat(vararg values: Any): Float {
|
||||
fun staticRandomFloat(vararg values: Any?): Float {
|
||||
return staticRandom32(*values).ushr(8) * 5.9604645E-8f
|
||||
}
|
||||
|
||||
fun staticRandomDouble(vararg values: Any): Double {
|
||||
fun staticRandomDouble(vararg values: Any?): Double {
|
||||
return staticRandom64(*values).ushr(11) * 1.1102230246251565E-16
|
||||
}
|
||||
|
||||
fun staticRandomInt(origin: Int, bound: Int, vararg values: Any): Int {
|
||||
fun staticRandomInt(origin: Int, bound: Int, vararg values: Any?): Int {
|
||||
val rand = staticRandomDouble(*values)
|
||||
return origin + ((bound - origin) * rand).toInt()
|
||||
}
|
||||
|
||||
fun staticRandom64(vararg values: Any): Long {
|
||||
fun staticRandomLong(origin: Long, bound: Long, vararg values: Any?): Long {
|
||||
val rand = staticRandomDouble(*values)
|
||||
return origin + ((bound - origin) * rand).toLong()
|
||||
}
|
||||
|
||||
fun staticRandom64(vararg values: Any?): Long {
|
||||
val digest = XXHash64(1997293021376312589L)
|
||||
|
||||
for (value in values) {
|
||||
@ -101,6 +107,7 @@ fun staticRandom64(vararg values: Any): Long {
|
||||
is Long -> toBytes(digest::update, value)
|
||||
is Double -> toBytes(digest::update, value)
|
||||
is Float -> toBytes(digest::update, value)
|
||||
null -> {} // do nothing?
|
||||
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class Direction(val normal: Vector2d, override val jsonName: String) : IStringSerializable {
|
||||
LEFT(Vector2d.NEGATIVE_X, "left") {
|
||||
enum class Direction(val normal: Vector2d, override val jsonName: String, val luaValue: Long) : IStringSerializable {
|
||||
LEFT(Vector2d.NEGATIVE_X, "left", -1L) {
|
||||
override val opposite: Direction
|
||||
get() = RIGHT
|
||||
},
|
||||
RIGHT(Vector2d.POSITIVE_X, "right") {
|
||||
RIGHT(Vector2d.POSITIVE_X, "right", 1L) {
|
||||
override val opposite: Direction
|
||||
get() = LEFT
|
||||
};
|
||||
|
@ -110,7 +110,7 @@ sealed class TileModification {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!allowEntityOverlap && world.entityIndex.any(rect, Predicate { it is DynamicEntity && it.movement.computeCollisionAABB().intersect(rect) })) {
|
||||
if (!allowEntityOverlap && world.entityIndex.any(rect, Predicate { it.collisionArea.intersect(rect) })) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
@ -247,6 +246,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
var ticks = 0L
|
||||
private set
|
||||
|
||||
abstract val eventLoop: BlockableEventLoop
|
||||
|
||||
open fun broadcast(packet: IPacket) {
|
||||
|
||||
}
|
||||
@ -312,8 +313,6 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
|
||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||
|
||||
abstract val eventLoop: BlockableEventLoop
|
||||
|
||||
fun entitiesAtTile(pos: Vector2i, filter: Predicate<TileEntity> = Predicate { true }): List<TileEntity> {
|
||||
return entityIndex.query(
|
||||
AABBi(pos, pos + Vector2i.POSITIVE_XY).toDoubleAABB(),
|
||||
|
@ -10,10 +10,12 @@ import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.defs.InteractAction
|
||||
import ru.dbotthepony.kstarbound.defs.InteractRequest
|
||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.DamageTeam
|
||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
@ -83,9 +85,11 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
||||
* Returning false will also stop entity from being saved to disk, and render entity orphaned
|
||||
* when chunk containing it will get unloaded
|
||||
*/
|
||||
open val isApplicableForUnloading: Boolean
|
||||
open val isPersistent: Boolean
|
||||
get() = true
|
||||
|
||||
val team = networkedData(EntityDamageTeam(), EntityDamageTeam.CODEC, EntityDamageTeam.LEGACY_CODEC)
|
||||
|
||||
enum class RemovalReason(val removal: Boolean, val dying: Boolean, val remote: Boolean) {
|
||||
UNLOADED(false, false, false), // Being saved to disk
|
||||
REMOVED(true, false, false), // Got removed from world
|
||||
@ -111,6 +115,9 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
||||
*/
|
||||
abstract val metaBoundingBox: AABB
|
||||
|
||||
open val collisionArea: AABB
|
||||
get() = NEVER
|
||||
|
||||
open fun onNetworkUpdate() {
|
||||
|
||||
}
|
||||
@ -189,5 +196,6 @@ abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable<Abstr
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val NEVER = AABB(Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY), Vector2d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
@ -9,15 +11,20 @@ import ru.dbotthepony.kommons.gson.getArray
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.map
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.matrix.Matrix3d
|
||||
import ru.dbotthepony.kommons.matrix.Matrix3f
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2f
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimatedPartsDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleFactory
|
||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||
import ru.dbotthepony.kstarbound.json.mergeJson
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.math.PeriodicFunction
|
||||
@ -39,8 +46,10 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import kotlin.NoSuchElementException
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
@ -183,6 +192,7 @@ class Animator() {
|
||||
|
||||
private var activeStateChanged = false
|
||||
private var frameChanged = false
|
||||
private var firstTime = true
|
||||
|
||||
var activeState: AnimatedPartsDefinition.StateType.State? = null
|
||||
set(value) {
|
||||
@ -216,9 +226,14 @@ class Animator() {
|
||||
fun set(state: String, alwaysStart: Boolean): Boolean {
|
||||
val getState = states[state] ?: return false
|
||||
|
||||
if (activeState != getState || alwaysStart) {
|
||||
// allow to reset active state if calling for first time
|
||||
// without this client and server will disagree what state is active
|
||||
// if there default state present
|
||||
if (activeState != getState || alwaysStart || firstTime) {
|
||||
activeState = getState
|
||||
timer = 0.0
|
||||
startedEvent.trigger()
|
||||
firstTime = true
|
||||
return true
|
||||
}
|
||||
|
||||
@ -227,8 +242,11 @@ class Animator() {
|
||||
|
||||
init {
|
||||
if (default in states) {
|
||||
noPropagate = true
|
||||
activeState = states[default]!!
|
||||
stateIndex.accept(activeState!!.index.toLong())
|
||||
startedEvent.trigger()
|
||||
noPropagate = false
|
||||
}
|
||||
|
||||
stateIndex.addListener(Consumer {
|
||||
@ -284,12 +302,74 @@ class Animator() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finishAnimations() {
|
||||
while (true) {
|
||||
val active = activeState ?: break
|
||||
|
||||
if (active.mode == AnimatedPartsDefinition.AnimationMode.END) {
|
||||
timer = active.cycle
|
||||
} else if (active.mode == AnimatedPartsDefinition.AnimationMode.TRANSITION) {
|
||||
activeState = states[active.transition]!! // validity of 'transition' is checked during json load
|
||||
this.activeState = activeState
|
||||
timer = 0.0
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Part(config: AnimatedPartsDefinition.Part) {
|
||||
val partProperties = config.properties
|
||||
private inner class Part(private val config: AnimatedPartsDefinition.Part) {
|
||||
var activePartDirty = true
|
||||
val partStates = config.partStates
|
||||
var properties: JsonObject = config.properties.deepCopy()
|
||||
private set
|
||||
|
||||
var activeState: AnimatedPartsDefinition.Part.State? = null
|
||||
private set
|
||||
|
||||
fun freshen() {
|
||||
if (!activePartDirty)
|
||||
return
|
||||
|
||||
activePartDirty = false
|
||||
|
||||
// First reset all the active part information assuming that no state type
|
||||
// x state match exists.
|
||||
activeState = null
|
||||
properties = config.properties.deepCopy()
|
||||
|
||||
// Then go through each of the state types and states and look for a part
|
||||
// state match in order of priority.
|
||||
|
||||
// honestly, i have no idea what next code does
|
||||
for ((stateTypeName, stateType) in stateTypes) {
|
||||
// Skip disabled state types
|
||||
if (!stateType.enabled)
|
||||
continue
|
||||
|
||||
val partStateType = config.partStates[stateTypeName] ?: continue
|
||||
val stateName = stateType.activeState?.name ?: continue
|
||||
val partState = partStateType[stateName] ?: continue
|
||||
|
||||
activeState = partState
|
||||
val frame = stateType.frame
|
||||
|
||||
// Then set the part state data, as well as any part state frame data if
|
||||
// the current frame is within the list size.
|
||||
mergeJson(properties, partState.properties)
|
||||
|
||||
for ((key, props) in partState.frameProperties) {
|
||||
if (props.size() > frame) {
|
||||
properties[key] = props.get(frame).deepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
// Each part can only have one state type x state match, so we are done.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RotationGroup {
|
||||
@ -342,6 +422,15 @@ class Animator() {
|
||||
xShear = atan2(value[1, 0].toDouble(), value[0, 0].toDouble())
|
||||
yShear = atan2(value[0, 1].toDouble(), value[1, 1].toDouble())
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
xTranslation = 0.0
|
||||
yTranslation = 0.0
|
||||
xScale = 1.0
|
||||
yScale = 1.0
|
||||
xShear = 0.0
|
||||
yShear = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
private class ParticleEmitter {
|
||||
@ -376,7 +465,7 @@ class Animator() {
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
var processingDirectives by networkedString().also { elements.add(it) }
|
||||
var zoom by networkedFloat().also { elements.add(it) }
|
||||
var zoom by networkedFloat(1.0).also { elements.add(it) }
|
||||
var isFlipped by networkedBoolean().also { elements.add(it) }
|
||||
var flippedRelativeCenterLine by networkedFloat().also { elements.add(it) }
|
||||
var animationRate by networkedFloat(1.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
@ -518,13 +607,7 @@ class Animator() {
|
||||
}
|
||||
|
||||
fun setPartTag(partName: String, tagKey: String, tagValue: String) {
|
||||
var tags = partTags[partName]
|
||||
|
||||
if (tags == null) {
|
||||
tags = NetworkedMap(InternedStringCodec, InternedStringCodec)
|
||||
partTags[partName] = tags
|
||||
}
|
||||
|
||||
val tags = partTags[partName] ?: throw IllegalArgumentException("Unknown part $partName!")
|
||||
tags[tagKey] = tagValue
|
||||
}
|
||||
|
||||
@ -574,15 +657,280 @@ class Animator() {
|
||||
}
|
||||
|
||||
fun setActiveState(type: String, state: String, alwaysStart: Boolean = false): Boolean {
|
||||
val getType = stateTypes[type] ?: return false
|
||||
val getState = getType.states[state] ?: return false
|
||||
return stateTypes[type]?.set(state, alwaysStart) ?: return false
|
||||
}
|
||||
|
||||
if (getType.activeState != getState || alwaysStart) {
|
||||
getType.timer = 0.0
|
||||
return true
|
||||
fun animationState(type: String): String {
|
||||
val get = stateTypes[type] ?: throw NoSuchElementException("No such animation part $type")
|
||||
return get.activeState?.name ?: "" // original engine parity...............
|
||||
// it will return empty string if state type has no active state (
|
||||
}
|
||||
|
||||
fun stateProperty(type: String, path: String): JsonElement {
|
||||
val get = stateTypes[type] ?: throw NoSuchElementException("No such animation part $type")
|
||||
return get.activeState?.properties?.get(path) ?: JsonNull.INSTANCE // очень и очень грустно
|
||||
}
|
||||
|
||||
fun stateProperty(type: String, path: JsonPath): JsonElement {
|
||||
val get = stateTypes[type] ?: throw NoSuchElementException("No such animation part $type")
|
||||
val props = get.activeState?.properties ?: return JsonNull.INSTANCE
|
||||
return path.find(props) ?: JsonNull.INSTANCE // очень и очень грустно
|
||||
}
|
||||
|
||||
fun rotateGroup(group: String, targetAngle: Double, immediate: Boolean = false) {
|
||||
val get = rotationGroups[group] ?: throw NoSuchElementException("No such rotation group $group")
|
||||
get.targetAngle.accept(targetAngle)
|
||||
|
||||
if (immediate) {
|
||||
get.currentAngle = targetAngle
|
||||
get.immediateEvent.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
fun currentRotationAngle(group: String): Double {
|
||||
return rotationGroups[group]?.currentAngle ?: throw NoSuchElementException("No such rotation group $group")
|
||||
}
|
||||
|
||||
fun targetRotationAngle(group: String): Double {
|
||||
return rotationGroups[group]?.targetAngle?.get() ?: throw NoSuchElementException("No such rotation group $group")
|
||||
}
|
||||
|
||||
fun hasRotationGroup(group: String): Boolean {
|
||||
return group in rotationGroups
|
||||
}
|
||||
|
||||
fun rotationGroups(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(rotationGroups.keys)
|
||||
}
|
||||
|
||||
fun transformationGroups(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(transformationGroups.keys)
|
||||
}
|
||||
|
||||
fun hasTransformationGroup(group: String): Boolean {
|
||||
return group in transformationGroups
|
||||
}
|
||||
|
||||
fun translateTransformGroup(group: String, translation: Vector2f) {
|
||||
val get = transformationGroups[group] ?: throw NoSuchElementException("No such transformation group $group")
|
||||
get.setAffineTransform(Matrix3f.rowMajor(r02 = translation.x, r12 = translation.y).mul(get.affineTransform()))
|
||||
}
|
||||
|
||||
fun rotateTransformGroup(group: String, rotation: Float, center: Vector2f = Vector2f.ZERO) {
|
||||
val get = transformationGroups[group] ?: throw NoSuchElementException("No such transformation group $group")
|
||||
val sin = sin(rotation)
|
||||
val cos = cos(rotation)
|
||||
|
||||
val matrix = Matrix3f.rowMajor(
|
||||
cos, -sin, center.x - cos * center.x + sin * center.y,
|
||||
sin, cos, center.y - sin * center.y - cos * center.x
|
||||
)
|
||||
|
||||
get.setAffineTransform(matrix.mul(get.affineTransform()))
|
||||
}
|
||||
|
||||
fun scaleTransformationGroup(group: String, scale: Vector2f, center: Vector2f = Vector2f.ZERO) {
|
||||
val get = transformationGroups[group] ?: throw NoSuchElementException("No such transformation group $group")
|
||||
|
||||
val matrix = Matrix3f.rowMajor(
|
||||
scale.x, 0f, center.x - center.x * scale.x,
|
||||
0f, scale.y, center.y - center.y * scale.y
|
||||
)
|
||||
|
||||
get.setAffineTransform(matrix.mul(get.affineTransform()))
|
||||
}
|
||||
|
||||
fun transformTransformationGroup(
|
||||
group: String,
|
||||
r00: Float,
|
||||
r01: Float,
|
||||
r02: Float,
|
||||
r10: Float,
|
||||
r11: Float,
|
||||
r12: Float,
|
||||
) {
|
||||
val get = transformationGroups[group] ?: throw NoSuchElementException("No such transformation group $group")
|
||||
val matrix = Matrix3f.rowMajor(r00, r01, r02, r10, r11, r12)
|
||||
get.setAffineTransform(matrix.mul(get.affineTransform()))
|
||||
}
|
||||
|
||||
fun resetTransformationGroup(group: String) {
|
||||
val get = transformationGroups[group] ?: throw NoSuchElementException("No such transformation group $group")
|
||||
get.reset()
|
||||
}
|
||||
|
||||
fun hasParticleEmitter(emitter: String): Boolean {
|
||||
return emitter in particleEmitters
|
||||
}
|
||||
|
||||
fun particleEmitters(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(particleEmitters.keys)
|
||||
}
|
||||
|
||||
fun setParticleEmitterActive(emitter: String, state: Boolean = true) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
get.active = state
|
||||
}
|
||||
|
||||
fun setParticleEmitterEmissionRate(emitter: String, rate: Double) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
get.emissionRate = rate
|
||||
}
|
||||
|
||||
fun setParticleEmitterBurstCount(emitter: String, count: Int) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
require(count >= 0) { "Negative burst count: $count" }
|
||||
get.burstCount = count
|
||||
}
|
||||
|
||||
fun setParticleEmitterOffsetRegion(emitter: String, region: AABB) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
get.offsetRegion = KOptional(region)
|
||||
}
|
||||
|
||||
fun setParticleEmitterOffsetRegion(emitter: String) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
get.offsetRegion = KOptional()
|
||||
}
|
||||
|
||||
fun burstParticleEmitter(emitter: String) {
|
||||
val get = particleEmitters[emitter] ?: throw NoSuchElementException("No such particle emitter $emitter")
|
||||
get.burstEvent.trigger()
|
||||
}
|
||||
|
||||
fun lights(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(lights.keys)
|
||||
}
|
||||
|
||||
fun hasLight(light: String): Boolean {
|
||||
return light in lights
|
||||
}
|
||||
|
||||
fun setLightActive(light: String, state: Boolean = true) {
|
||||
val get = lights[light] ?: throw NoSuchElementException("No such light source $light")
|
||||
get.active = state
|
||||
}
|
||||
|
||||
fun setLightPosition(light: String, position: Vector2d) {
|
||||
val get = lights[light] ?: throw NoSuchElementException("No such light source $light")
|
||||
get.xPosition = position.x
|
||||
get.yPosition = position.y
|
||||
}
|
||||
|
||||
fun setLightPosition(light: String, x: Double, y: Double) {
|
||||
val get = lights[light] ?: throw NoSuchElementException("No such light source $light")
|
||||
get.xPosition = x
|
||||
get.yPosition = y
|
||||
}
|
||||
|
||||
fun setLightColor(light: String, color: RGBAColor) {
|
||||
val get = lights[light] ?: throw NoSuchElementException("No such light source $light")
|
||||
get.color = color
|
||||
}
|
||||
|
||||
fun setLightPointAngle(light: String, angle: Double) {
|
||||
val get = lights[light] ?: throw NoSuchElementException("No such light source $light")
|
||||
get.pointAngle = angle
|
||||
}
|
||||
|
||||
fun sounds(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(sounds.keys)
|
||||
}
|
||||
|
||||
fun hasSound(sound: String): Boolean {
|
||||
return sound in sounds
|
||||
}
|
||||
|
||||
fun setSoundPool(sound: String, pool: Collection<String>) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
|
||||
get.soundPool.clear()
|
||||
get.soundPool.addAll(pool)
|
||||
}
|
||||
|
||||
fun setSoundPosition(sound: String, position: Vector2d) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
|
||||
get.xPosition = position.x
|
||||
get.yPosition = position.y
|
||||
}
|
||||
|
||||
fun setSoundPosition(sound: String, x: Double, y: Double) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
|
||||
get.xPosition = x
|
||||
get.yPosition = y
|
||||
}
|
||||
|
||||
fun playSound(sound: String, loops: Int = 0) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
require(loops >= 0) { "Negative amount of loops: $loops" }
|
||||
get.loops = loops
|
||||
get.signals.push(SoundSignal.PLAY)
|
||||
}
|
||||
|
||||
fun setSoundVolume(sound: String, volume: Double, rampTime: Double = 0.0) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
get.volumeTarget = volume
|
||||
get.volumeRampTime = rampTime
|
||||
}
|
||||
|
||||
fun setSoundPitch(sound: String, pitch: Double, rampTime: Double = 0.0) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
get.pitchMultiplierTarget = pitch
|
||||
get.pitchMultiplierRampTime = rampTime
|
||||
}
|
||||
|
||||
fun stopAllSounds(sound: String, rampTime: Double = 0.0) {
|
||||
val get = sounds[sound] ?: throw NoSuchElementException("No such sound source $sound")
|
||||
get.volumeRampTime = rampTime
|
||||
get.signals.push(SoundSignal.STOP_ALL)
|
||||
}
|
||||
|
||||
fun effects(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(effects.keys)
|
||||
}
|
||||
|
||||
fun hasEffect(effect: String): Boolean {
|
||||
return effect in effects
|
||||
}
|
||||
|
||||
fun setEffectActive(effect: String, state: Boolean = true) {
|
||||
val get = effects[effect] ?: throw NoSuchElementException("No such effect source $effect")
|
||||
get.enabled.accept(state)
|
||||
}
|
||||
|
||||
fun parts(): Collection<String> {
|
||||
return Collections.unmodifiableCollection(parts.keys)
|
||||
}
|
||||
|
||||
fun partPoint(part: String, property: String): Vector2d? {
|
||||
val get = parts[part] ?: throw NoSuchElementException("No such animation part $part")
|
||||
get.freshen()
|
||||
val lookup = get.properties[property] ?: return null
|
||||
return vectors.fromJsonTree(lookup)
|
||||
}
|
||||
|
||||
fun partPoly(part: String, property: String): Poly? {
|
||||
val get = parts[part] ?: throw NoSuchElementException("No such animation part $part")
|
||||
get.freshen()
|
||||
val lookup = get.properties[property] ?: return null
|
||||
return polies.fromJsonTree(lookup)
|
||||
}
|
||||
|
||||
fun partProperty(part: String, property: String): JsonElement {
|
||||
val get = parts[part] ?: throw NoSuchElementException("No such animation part $part")
|
||||
get.freshen()
|
||||
return get.properties[property] ?: JsonNull.INSTANCE
|
||||
}
|
||||
|
||||
fun partTransformation(part: String): Matrix3d {
|
||||
val get = parts[part] ?: throw NoSuchElementException("No such animation part $part")
|
||||
val result = Matrix3d.rowMajor()
|
||||
|
||||
val active = get.activeState ?: return result
|
||||
// TODO
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: Dynamic target
|
||||
@ -618,6 +966,8 @@ class Animator() {
|
||||
}
|
||||
}
|
||||
|
||||
parts.values.forEach { it.activePartDirty = true }
|
||||
|
||||
for (rotationGroup in rotationGroups.values) {
|
||||
rotationGroup.tick(delta)
|
||||
}
|
||||
@ -631,6 +981,14 @@ class Animator() {
|
||||
}
|
||||
}
|
||||
|
||||
fun finishAnimations() {
|
||||
for (state in stateTypes.values) {
|
||||
state.finishAnimations()
|
||||
}
|
||||
|
||||
parts.values.forEach { it.activePartDirty = true }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// lame
|
||||
fun load(path: String): Animator {
|
||||
@ -649,5 +1007,7 @@ class Animator() {
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||
private val vectors by lazy { Starbound.gson.getAdapter(Vector2d::class.java) }
|
||||
private val polies by lazy { Starbound.gson.getAdapter(Poly::class.java) }
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) {
|
||||
|
||||
abstract val movement: MovementController
|
||||
|
||||
override val collisionArea: AABB
|
||||
get() = movement.computeCollisionAABB()
|
||||
|
||||
override fun onNetworkUpdate() {
|
||||
super.onNetworkUpdate()
|
||||
movement.updateFixtures()
|
||||
|
@ -82,7 +82,11 @@ class PlayerEntity() : HumanoidActorEntity("/") {
|
||||
private var xAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125))
|
||||
private var yAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125).also { it.interpolator = Interpolator.Linear })
|
||||
var humanoidData by networkGroup.upstream.add(networkedData(HumanoidData(), HumanoidData.CODEC, HumanoidData.LEGACY_CODEC))
|
||||
var teamState by networkGroup.upstream.add(networkedData(EntityDamageTeam(), EntityDamageTeam.CODEC, EntityDamageTeam.LEGACY_CODEC))
|
||||
|
||||
init {
|
||||
networkGroup.upstream.add(team)
|
||||
}
|
||||
|
||||
val landed = networkGroup.upstream.add(networkedEventCounter())
|
||||
var chatMessage by networkGroup.upstream.add(networkedString())
|
||||
val newChatMessage = networkGroup.upstream.add(networkedEventCounter())
|
||||
@ -106,7 +110,7 @@ class PlayerEntity() : HumanoidActorEntity("/") {
|
||||
override val aimPosition: Vector2d
|
||||
get() = Vector2d(xAimPosition, yAimPosition)
|
||||
|
||||
override val isApplicableForUnloading: Boolean
|
||||
override val isPersistent: Boolean
|
||||
get() = false
|
||||
|
||||
var uuid: UUID by Delegates.notNull()
|
||||
|
@ -83,11 +83,11 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
|
||||
private val currentMaterialSpaces = HashSet<Pair<Vector2i, Registry.Ref<TileDefinition>>>()
|
||||
private val currentRoots = HashSet<Vector2i>()
|
||||
|
||||
protected open fun markSpacesDirty() {
|
||||
open fun markSpacesDirty() {
|
||||
needToUpdateSpaces = true
|
||||
}
|
||||
|
||||
protected open fun markRootsDirty() {
|
||||
open fun markRootsDirty() {
|
||||
needToUpdateRoots = true
|
||||
}
|
||||
|
||||
@ -147,6 +147,8 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
|
||||
}
|
||||
|
||||
protected fun updateMaterialSpaces(desired: Collection<Pair<Vector2i, Registry.Ref<TileDefinition>>>): Boolean {
|
||||
check(world.isServer) { "Invalid realm" }
|
||||
|
||||
val toRemove = ArrayList<Pair<Vector2i, Registry.Ref<TileDefinition>>>()
|
||||
val toPlace = ArrayList<Pair<Vector2i, Registry.Ref<TileDefinition>>>()
|
||||
|
||||
@ -219,7 +221,7 @@ abstract class TileEntity(path: String) : AbstractEntity(path) {
|
||||
override fun tick(delta: Double) {
|
||||
super.tick(delta)
|
||||
|
||||
if (needToUpdateSpaces) {
|
||||
if (world.isServer && needToUpdateSpaces) {
|
||||
updateMaterialSpacesNow()
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
@ -59,6 +61,17 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedPointer
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.lua.LuaMessageHandler
|
||||
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideWorldBindings
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideWorldObjectBindings
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.get
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.lua.toJson
|
||||
import ru.dbotthepony.kstarbound.util.asStringOrNull
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||
@ -78,6 +91,8 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
isInteractive = data.get("interactive", false)
|
||||
tilePosition = data.get("tilePosition", vectors)
|
||||
|
||||
lua.globals["storage"] = lua.from(data.get("scriptStorage") { JsonObject() })
|
||||
|
||||
uniqueID.accept(KOptional.ofNullable(data["uniqueId"]?.asStringOrNull))
|
||||
|
||||
loadParameters(data.get("parameters") { JsonObject() })
|
||||
@ -97,6 +112,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
into["orientationIndex"] = orientationIndex
|
||||
into["interactive"] = isInteractive
|
||||
|
||||
val scriptStorage = lua.globals["storage"]
|
||||
|
||||
if (scriptStorage != null && scriptStorage is Table) {
|
||||
into["scriptStorage"] = scriptStorage.toJson()
|
||||
}
|
||||
|
||||
uniqueID.get().ifPresent {
|
||||
into["uniqueId"] = it
|
||||
}
|
||||
@ -243,6 +264,14 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
networkGroup.upstream.add(animator.networkGroup)
|
||||
}
|
||||
|
||||
val lua = LuaEnvironment()
|
||||
val luaUpdate = LuaUpdateComponent(lua)
|
||||
val luaMessageHandler = LuaMessageHandler(lua)
|
||||
|
||||
init {
|
||||
lua.globals["storage"] = lua.newTable()
|
||||
}
|
||||
|
||||
val unbreakable by LazyData {
|
||||
lookupProperty(JsonPath("unbreakable")) { JsonPrimitive(false) }.asBoolean
|
||||
}
|
||||
@ -280,10 +309,20 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
//
|
||||
var color: TileColor by Property(JsonPath("color"), TileColor.DEFAULT)
|
||||
|
||||
var animationParts: ImmutableMap<String, SpriteReference> by Property(JsonPath("animationParts"))
|
||||
var imagePosition: Vector2i by Property(JsonPath("imagePosition"), Vector2i.ZERO)
|
||||
var animationPosition: Vector2i by Property(JsonPath("animationPosition"), Vector2i.ZERO)
|
||||
|
||||
// ????????
|
||||
val volumeBoundingBox: AABB get() {
|
||||
val orientation = orientation
|
||||
|
||||
if (orientation == null) {
|
||||
return AABB(position, position + Vector2d.POSITIVE_XY)
|
||||
} else {
|
||||
return AABB(orientation.boundingBox.mins.toDoubleVector(), orientation.boundingBox.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
|
||||
}
|
||||
}
|
||||
|
||||
private val drawablesCache = LazyData {
|
||||
orientation?.drawables?.map { it.with(::getRenderParam) } ?: listOf()
|
||||
}
|
||||
@ -336,6 +375,11 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
}
|
||||
}
|
||||
|
||||
override fun markSpacesDirty() {
|
||||
super.markSpacesDirty()
|
||||
materialSpaces0.invalidate()
|
||||
}
|
||||
|
||||
override fun onJoinWorld(world: World<*, *>) {
|
||||
super.onJoinWorld(world)
|
||||
|
||||
@ -352,13 +396,32 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
networkedMaterialSpaces.addAll(orientation.materialSpaces)
|
||||
}
|
||||
|
||||
setImageKey("color", lookupProperty(JsonPath("color")) { JsonPrimitive("default") }.asString)
|
||||
if (isRemote) {
|
||||
|
||||
for ((k, v) in lookupProperty(JsonPath("animationParts")) { JsonObject() }.asJsonObject.entrySet()) {
|
||||
animator.setPartTag(k, "partImage", v.asString)
|
||||
} else {
|
||||
setImageKey("color", lookupProperty(JsonPath("color")) { JsonPrimitive("default") }.asString)
|
||||
|
||||
for ((k, v) in lookupProperty(JsonPath("animationParts")) { JsonObject() }.asJsonObject.entrySet()) {
|
||||
animator.setPartTag(k, "partImage", v.asString)
|
||||
}
|
||||
|
||||
updateMaterialSpacesNow()
|
||||
|
||||
provideWorldBindings(world, lua)
|
||||
provideWorldObjectBindings(this, lua)
|
||||
provideEntityBindings(this, lua)
|
||||
provideAnimatorBindings(animator, lua)
|
||||
lua.attach(config.value.scripts)
|
||||
lua.init()
|
||||
}
|
||||
|
||||
updateMaterialSpacesNow()
|
||||
// as original code puts it:
|
||||
// Don't animate the initial state when first spawned IF you're dumb, which by default
|
||||
// you would be, and don't know how to use transition and static states properly. Someday
|
||||
// I'll be brave and delete shit garbage entirely and we'll see what breaks.
|
||||
if (lookupProperty(JsonPath("forceFinishAnimationsInInit")) { JsonPrimitive(true) }.asBoolean) {
|
||||
animator.finishAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRenderParam(key: String): String {
|
||||
@ -400,6 +463,14 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
drawablesCache.invalidate()
|
||||
}
|
||||
|
||||
fun callBreak(smash: Boolean = false) {
|
||||
|
||||
}
|
||||
|
||||
fun addChatMessage(message: String, config: JsonElement, portrait: String? = null) {
|
||||
|
||||
}
|
||||
|
||||
override fun tick(delta: Double) {
|
||||
super.tick(delta)
|
||||
|
||||
@ -420,6 +491,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
setImageKey("frame", frame.toString())
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
luaUpdate.update()
|
||||
} catch (err: Throwable) {
|
||||
LogManager.getLogger().error("Error running update", err)
|
||||
}
|
||||
}
|
||||
|
||||
if (world.isServer && !unbreakable) {
|
||||
|
@ -24,6 +24,7 @@ import ru.dbotthepony.kommons.io.readVector2f
|
||||
import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kommons.matrix.Matrix3d
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.math.Line2d
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
@ -213,6 +214,10 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
|
||||
return Vector2d(min, max)
|
||||
}
|
||||
|
||||
fun transform(transformation: Matrix3d): Poly {
|
||||
return Poly(vertices.map { it * transformation })
|
||||
}
|
||||
|
||||
fun windingNumber(point: IStruct2d): Int {
|
||||
val (x, y) = point
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user