Больше Lua API в root, 2function

This commit is contained in:
DBotThePony 2023-03-26 20:52:48 +07:00
parent b0e978b2d1
commit 12232fbd18
Signed by: DBot
GPG Key ID: DCC23B5715498507
13 changed files with 2037 additions and 88 deletions

View File

@ -18,7 +18,7 @@ import ru.dbotthepony.kstarbound.util.PathStack
import java.util.*
import kotlin.reflect.KClass
inline fun <reified T : Any> ObjectRegistry(name: String, noinline key: (T) -> String, noinline intKey: ((T) -> Int)? = null): ObjectRegistry<T> {
inline fun <reified T : Any> ObjectRegistry(name: String, noinline key: ((T) -> String)? = null, noinline intKey: ((T) -> Int)? = null): ObjectRegistry<T> {
return ObjectRegistry(T::class, name, key, intKey)
}
@ -48,7 +48,7 @@ class RegistryObject<T : Any>(val value: T, private val json: JsonObject, val fi
}
}
class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: (T) -> String, val intKey: ((T) -> Int)? = null) {
class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: ((T) -> String)? = null, val intKey: ((T) -> Int)? = null) {
private val objects = Object2ObjectOpenHashMap<String, RegistryObject<T>>()
private val intObjects = Int2ObjectOpenHashMap<RegistryObject<T>>()
@ -160,17 +160,19 @@ class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: (
return pathStack(file.computeDirectory()) {
val elem = gson.fromJson(file.reader(), JsonObject::class.java)
val value = gson.fromJson<T>(JsonTreeReader(elem), clazz.java)
add(RegistryObject(value, elem, file, gson, pathStack))
add(RegistryObject(value, elem, file, gson, pathStack), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
}
}
fun add(value: T, json: JsonObject, file: IStarboundFile, gson: Gson, pathStack: PathStack): Boolean {
return add(RegistryObject(value, json, file, gson, pathStack))
return add(RegistryObject(value, json, file, gson, pathStack), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
}
private fun add(value: RegistryObject<T>): Boolean {
val key = this.key.invoke(value.value)
fun add(value: T, json: JsonObject, file: IStarboundFile, gson: Gson, pathStack: PathStack, key: String): Boolean {
return add(RegistryObject(value, json, file, gson, pathStack), key)
}
private fun add(value: RegistryObject<T>, key: String): Boolean {
val existing = objects.put(key, value)
if (existing != null) {
@ -184,7 +186,7 @@ class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: (
val intExisting = intObjects.put(intKey, value)
if (intExisting != null) {
LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${this.key.invoke(intExisting.value)})! Overwriting. (old originated from ${intExisting.file}, new originate from ${value.file}).")
LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${ objects.entries.firstOrNull { it.value === intExisting }?.key })! Overwriting. (old originated from ${intExisting.file}, new originate from ${value.file}).")
}
return existing != null || intExisting != null

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound
import com.google.common.cache.CacheBuilder
import com.google.common.collect.Interner
import com.google.common.collect.Interners
import com.google.gson.*
@ -7,6 +8,7 @@ import com.google.gson.internal.bind.JsonTreeReader
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager
import org.lwjgl.stb.STBImage
import ru.dbotthepony.kstarbound.api.ISBFileLocator
import ru.dbotthepony.kstarbound.api.IStarboundFile
import ru.dbotthepony.kstarbound.api.NonExistingFile
@ -63,20 +65,23 @@ import ru.dbotthepony.kstarbound.util.PathStack
import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.util.traverseJsonPath
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.io.*
import java.text.DateFormat
import java.time.Duration
import java.util.*
import java.util.function.BiConsumer
import java.util.function.BinaryOperator
import java.util.function.Function
import java.util.function.Supplier
import java.util.stream.Collector
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
class Starbound : ISBFileLocator {
private val logger = LogManager.getLogger()
val pathStack = PathStack(Starbound.STRINGS)
val pathStack = PathStack(STRINGS)
private val _tiles = ObjectRegistry("tiles", TileDefinition::materialName, TileDefinition::materialId)
val tiles = _tiles.view
@ -108,6 +113,12 @@ class Starbound : ISBFileLocator {
private val _techs = ObjectRegistry("techs", TechDefinition::name)
val techs = _techs.view
private val _jsonFunctions = ObjectRegistry<JsonFunction>("json functions")
val jsonFunctions = _jsonFunctions.view
private val _json2Functions = ObjectRegistry<Json2Function>("json 2functions")
val json2Functions = _json2Functions.view
val gson: Gson = with(GsonBuilder()) {
serializeNulls()
setDateFormat(DateFormat.LONG)
@ -115,7 +126,12 @@ class Starbound : ISBFileLocator {
setPrettyPrinting()
registerTypeAdapter(InternedStringAdapter(STRINGS))
registerTypeAdapter(InternedJsonElementAdapter(STRINGS))
InternedJsonElementAdapter(STRINGS).also {
registerTypeAdapter(it)
registerTypeAdapter(it.arrays)
registerTypeAdapter(it.objects)
}
registerTypeAdapter(Nothing::class.java, NothingAdapter)
@ -160,6 +176,7 @@ class Starbound : ISBFileLocator {
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
registerTypeAdapter(JsonFunction.Companion)
registerTypeAdapterFactory(Json2Function.Companion)
// Общее
registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
@ -193,6 +210,12 @@ class Starbound : ISBFileLocator {
val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson)
private val imageCache = CacheBuilder.newBuilder()
.concurrencyLevel(8)
.softValues()
.expireAfterAccess(Duration.ofMinutes(10))
.build<String, ImageData>()
fun item(name: String): ItemDescriptor {
return ItemDescriptor(items[name] ?: return ItemDescriptor.EMPTY)
}
@ -271,11 +294,42 @@ class Starbound : ISBFileLocator {
}
val read = file.readToString()
it.load(read, chunkName = file.computeFullPath())
it.load(read, chunkName = "@" + file.computeFullPath())
it.call()
}
fun expose(state: LuaState) {
fun imageData(path: String): ImageData {
return imageCache.get(path) {
val file = locate(path)
if (!file.exists) {
throw FileNotFoundException("No such file $file")
}
if (!file.isFile) {
throw IllegalStateException("File $file is a directory")
}
val getWidth = intArrayOf(0)
val getHeight = intArrayOf(0)
val components = intArrayOf(0)
val data = STBImage.stbi_load_from_memory(
file.readDirect(),
getWidth, getHeight,
components, 0
) ?: throw IllegalArgumentException("File $file is not an image or it is corrupted")
ImageData(data, getWidth[0], getHeight[0], components[0])
}
}
fun imageSize(path: String): Vector2i {
val image = imageData(path)
return Vector2i(image.width, image.height)
}
fun pushLuaAPI(state: LuaState) {
state.pushWeak(this) { args ->
luaRequire(args.lua, args)
0
@ -286,18 +340,62 @@ class Starbound : ISBFileLocator {
state.pushTable()
state.storeGlobal("root")
state.loadGlobal("root")
val root = state.stackTop
state.push("assetJson")
state.pushWeak(this) {args ->
state.setTableFunction("assetJson", this) {args ->
args.lua.push(loadJsonAsset(args.getString()))
1
}
state.setTableValue(root)
state.setTableFunction("makeCurrentVersionedJson", this) {args ->
TODO("makeCurrentVersionedJson")
}
state.setTableFunction("loadVersionedJson", this) {args ->
TODO("loadVersionedJson")
}
state.setTableFunction("evalFunction", this) {args ->
val name = args.getString()
val fn = jsonFunctions[name] ?: throw NoSuchElementException("No such function $name")
args.push(fn.value.evaluate(args.getDouble()))
1
}
state.setTableFunction("evalFunction2", this) {args ->
val name = args.getString()
val fn = json2Functions[name] ?: throw NoSuchElementException("No such 2function $name")
args.push(fn.value.evaluate(args.getDouble(), args.getDouble()))
1
}
state.setTableFunction("imageSize", this) {args ->
val (width, height) = imageSize(args.getString())
with(args.lua) {
pushTable(hashSize = 2)
val table = stackTop
setTableValue("width", width)
setTableValue("height", height)
setTableValue("x", width)
setTableValue("y", height)
push(1)
push(width)
setTableValue(table)
push(2)
push(height)
setTableValue(table)
}
1
}
state.pop()
state.load(polyfill, "starbound.jar!/scripts/polyfill.lua")
state.load(polyfill, "@starbound.jar!/scripts/polyfill.lua")
state.call()
}
@ -488,7 +586,9 @@ class Starbound : ISBFileLocator {
callback(false, false, "Finished building file index in ${System.currentTimeMillis() - time}ms".also(logger::info))
loadStage(callback, this::loadItemDefinitions, "item definitions")
loadStage(callback, { loadItemDefinitions(it, ext2files) }, "item definitions")
loadStage(callback, { loadJsonFunctions(it, ext2files["functions"] ?: listOf()) }, "json functions")
loadStage(callback, { loadJson2Functions(it, ext2files["2functions"] ?: listOf()) }, "json 2functions")
loadStage(callback, _tiles, ext2files["material"] ?: listOf())
loadStage(callback, _tileModifiers, ext2files["matmod"] ?: listOf())
@ -539,26 +639,28 @@ class Starbound : ISBFileLocator {
}
}
private fun loadItemDefinitions(callback: (String) -> Unit) {
val files = linkedMapOf(
".item" to ItemPrototype::class.java,
".currency" to CurrencyItemPrototype::class.java,
".liqitem" to LiquidItemPrototype::class.java,
".matitem" to MaterialItemPrototype::class.java,
".flashlight" to FlashlightPrototype::class.java,
".harvestingtool" to HarvestingToolPrototype::class.java,
".head" to HeadArmorItemPrototype::class.java,
".chest" to ChestArmorItemPrototype::class.java,
".legs" to LegsArmorItemPrototype::class.java,
".back" to BackArmorItemPrototype::class.java,
private fun loadItemDefinitions(callback: (String) -> Unit, files: Map<String, Collection<IStarboundFile>>) {
val fileMap = mapOf(
"item" to ItemPrototype::class.java,
"currency" to CurrencyItemPrototype::class.java,
"liqitem" to LiquidItemPrototype::class.java,
"matitem" to MaterialItemPrototype::class.java,
"flashlight" to FlashlightPrototype::class.java,
"harvestingtool" to HarvestingToolPrototype::class.java,
"head" to HeadArmorItemPrototype::class.java,
"chest" to ChestArmorItemPrototype::class.java,
"legs" to LegsArmorItemPrototype::class.java,
"back" to BackArmorItemPrototype::class.java,
)
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.keys.any { f.name.endsWith(it) } }) {
for ((ext, clazz) in fileMap) {
val fileList = files[ext] ?: continue
for (listedFile in fileList) {
try {
callback("Loading $listedFile")
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), files.entries.first { listedFile.name.endsWith(it.key) }.value) }
val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), clazz) }
_items.add(def, json, listedFile, gson, pathStack)
} catch (err: Throwable) {
logger.error("Loading item definition file $listedFile", err)
@ -571,6 +673,46 @@ class Starbound : ISBFileLocator {
}
}
private fun loadJsonFunctions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
for (listedFile in files) {
try {
callback("Loading $listedFile")
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
for ((k, v) in json.entrySet()) {
val fn = gson.fromJson<JsonFunction>(JsonTreeReader(v), JsonFunction::class.java)
_jsonFunctions.add(fn, json, listedFile, gson, pathStack, k)
}
} catch (err: Throwable) {
logger.error("Loading json function definition file $listedFile", err)
}
if (terminateLoading) {
return
}
}
}
private fun loadJson2Functions(callback: (String) -> Unit, files: Collection<IStarboundFile>) {
for (listedFile in files) {
try {
callback("Loading $listedFile")
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
for ((k, v) in json.entrySet()) {
val fn = gson.fromJson<Json2Function>(JsonTreeReader(v), Json2Function::class.java)
_json2Functions.add(fn, json, listedFile, gson, pathStack, k)
}
} catch (err: Throwable) {
logger.error("Loading json 2function definition file $listedFile", err)
}
if (terminateLoading) {
return
}
}
}
companion object {
/**
* Глобальный [Interner] для [String]

View File

@ -175,7 +175,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
input.installCallback(window)
GLFW.glfwMakeContextCurrent(window)
gl = GLStateTracker(starbound)
gl = GLStateTracker(this)
GLFW.glfwSetFramebufferSizeCallback(window) { _, w, h ->
if (w == 0 || h == 0) {

View File

@ -5,6 +5,7 @@ import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.*
import org.lwjgl.opengl.GLCapabilities
import ru.dbotthepony.kstarbound.api.ISBFileLocator
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.freetype.FreeType
import ru.dbotthepony.kstarbound.client.freetype.InvalidArgumentException
import ru.dbotthepony.kstarbound.client.gl.shader.GLPrograms
@ -116,7 +117,7 @@ private class TexturesTracker(maxValue: Int) : ReadWriteProperty<GLStateTracker,
}
@Suppress("PropertyName", "unused")
class GLStateTracker(val locator: ISBFileLocator) {
class GLStateTracker(val client: StarboundClient) {
private fun isMe(state: GLStateTracker?) {
if (state != null && state != this) {
throw InvalidArgumentException("Provided object does not belong to $this state tracker (belongs to $state)")
@ -441,18 +442,18 @@ class GLStateTracker(val locator: ISBFileLocator) {
ensureSameThread()
if (missingTexture == null) {
missingTexture = newTexture(missingTexturePath).upload(locator.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
missingTexture = newTexture(missingTexturePath).upload(client.starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
return named2DTextures.computeIfAbsent(path) {
if (!locator.exists(path)) {
if (!client.starbound.exists(path)) {
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
missingTexture!!
} else {
newTexture(path).upload(locator.readDirect(path)).generateMips().also {
newTexture(path).upload(client.starbound.imageData(path)).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.gl
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.*
import org.lwjgl.stb.STBImage
import ru.dbotthepony.kstarbound.io.ImageData
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.io.File
import java.io.FileNotFoundException
@ -248,6 +249,22 @@ class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : A
return this
}
fun upload(data: ImageData): GLTexture2D {
state.ensureSameThread()
val bufferFormat = when (val numChannels = data.amountOfChannels) {
1 -> GL_R
2 -> GL_RG
3 -> GL_RGB
4 -> GL_RGBA
else -> throw IllegalArgumentException("Weird amount of channels in file: $numChannels")
}
upload(bufferFormat, data.width, data.height, bufferFormat, GL_UNSIGNED_BYTE, data.data)
return this
}
var isValid = true
private set

View File

@ -1,66 +1,118 @@
package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter
import ru.dbotthepony.kstarbound.io.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
import ru.dbotthepony.kvector.util.linearInterpolation
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
enum class JsonFunctionInterpolation(vararg aliases: String) : IStringSerializable {
LINEAR;
enum class JsonFunctionInterpolation {
LINEAR {
override fun interpolate(t: Double, a: Double, b: Double): Double {
return linearInterpolation(t, a, b)
}
};
private val aliases: Array<String> = Array(aliases.size) { aliases[it].uppercase() }
override fun match(name: String): Boolean {
for (alias in aliases)
if (name == alias)
return true
return name == this.name
}
override fun write(out: JsonWriter) {
out.value(this.name)
}
abstract fun interpolate(t: Double, a: Double, b: Double): Double
}
enum class JsonFunctionConstraint(vararg aliases: String) : IStringSerializable {
CLAMP;
enum class JsonFunctionConstraint {
CLAMP {
override fun process(x: Double, lower: Double, upper: Double): Double {
return x.coerceIn(lower, upper)
}
};
private val aliases: Array<String> = Array(aliases.size) { aliases[it].uppercase() }
abstract fun process(x: Double, lower: Double, upper: Double): Double
}
override fun match(name: String): Boolean {
for (alias in aliases)
if (name == alias)
return true
return name == this.name
data class DoubleRange(val start: Double, val end: Double) : Comparable<Double> {
init {
require(start <= end) { "start <= end: $start <= $end" }
}
override fun write(out: JsonWriter) {
out.value(this.name)
val length = end - start
operator fun contains(value: Double): Boolean {
return value in start .. end
}
fun determine(value: Double): Double {
if (value < start) {
return 0.0
} else if (value > end) {
return 1.0
} else {
return (value - start) / length
}
}
override fun compareTo(other: Double): Int {
if (other < start) {
return 1
} else if (other > end) {
return -1
} else {
return 0
}
}
}
class JsonFunction(
val interpolation: JsonFunctionInterpolation,
val constraint: JsonFunctionConstraint,
val ranges: List<Vector2d>
ranges: Collection<Vector2d>
) {
val ranges: ImmutableList<Vector2d> = ranges.stream().sorted { o1, o2 -> o1.x.compareTo(o2.x) }.collect(ImmutableList.toImmutableList())
val lowerBound = this.ranges.first().x
val upperBound = this.ranges.last().x
fun evaluate(input: Double): Double {
val x = constraint.process(input, lowerBound, upperBound)
when (interpolation) {
JsonFunctionInterpolation.LINEAR -> {
if (x < ranges[0].x) {
return ranges[0].y
}
for (i in 0 until ranges.size - 1) {
val a = ranges[i]
val b = ranges[i + 1]
if (x in a.x .. b.x) {
return linearInterpolation((x - a.x) / (b.x - a.x), a.y, b.y)
}
}
return ranges.last().y
}
}
}
companion object : TypeAdapter<JsonFunction>() {
val CONSTRAINT_ADAPTER = EnumAdapter(JsonFunctionConstraint::class)
val INTERPOLATION_ADAPTER = EnumAdapter(JsonFunctionInterpolation::class)
override fun write(out: JsonWriter, value: JsonFunction) {
override fun write(out: JsonWriter, value: JsonFunction?) {
TODO("Not yet implemented")
}
override fun read(reader: JsonReader): JsonFunction {
override fun read(reader: JsonReader): JsonFunction? {
if (reader.peek() == JsonToken.NULL)
return null
reader.beginArray()
val interpolation = INTERPOLATION_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing interpolation")
@ -78,3 +130,196 @@ class JsonFunction(
}
}
}
class Json2Function(
val interpolation: JsonFunctionInterpolation,
val constraint: JsonFunctionConstraint,
xRanges: List<Ranges>, // значения [a, b, c, d]
yRanges: List<Ranges>, // значения [a, [b, c, d]]
) {
init {
require(xRanges.isNotEmpty()) { "X ranges are empty" }
require(yRanges.isNotEmpty()) { "Y ranges are empty" }
}
val xRanges: ImmutableList<Ranges> = xRanges.stream().sorted { o1, o2 -> o1.edge.compareTo(o2.edge) }.collect(ImmutableList.toImmutableList())
val yRanges: ImmutableList<Ranges> = yRanges.stream().sorted { o1, o2 -> o1.edge.compareTo(o2.edge) }.collect(ImmutableList.toImmutableList())
data class Ranges(val interpolation: JsonFunctionInterpolation, val edge: Double, val ranges: ImmutableList<DoubleRange>) {
val step = 1.0 / ranges.size.toDouble()
fun map(value: Double): Double {
if (value <= ranges.first().start) {
return 0.0
} else if (value >= ranges.last().end) {
return 1.0
}
var i = 0.0
for (range in ranges) {
if (value in range) {
return i + range.determine(value) * step
} else {
i += step
}
}
return i
}
fun unmap(t: Double): Double {
if (t <= 0.0) {
return ranges.first().start
} else if (t >= 1.0) {
return ranges.last().end
}
val unmap = t / step
val range = ranges[unmap.toInt()]
return interpolation.interpolate(unmap % 1.0, range.start, range.end)
}
}
fun evaluate(x: Double, y: Double): Double {
var xT = 0.0
if (xRanges.size == 1) {
xT = xRanges[0].map(x)
} else {
var hit = false
for (i in 1 until xRanges.size) {
val a = xRanges[i - 1]
val b = xRanges[i]
if (x in a.edge .. b.edge) {
xT = interpolation.interpolate((x - a.edge) / (b.edge - a.edge), a.map(x), b.map(x))
hit = true
break
}
}
if (!hit) {
xT = xRanges.last().map(x)
}
}
if (yRanges.size == 1) {
return yRanges[0].unmap(xT)
} else {
for (i in 1 until yRanges.size) {
val a = yRanges[i - 1]
val b = yRanges[i]
if (y in a.edge .. b.edge) {
return interpolation.interpolate((y - a.edge) / (b.edge - a.edge), a.unmap(xT), b.unmap(xT))
}
}
return yRanges.last().unmap(xT)
}
}
companion object : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Json2Function::class.java) {
return object : TypeAdapter<Json2Function>() {
val elements = gson.getAdapter(JsonArray::class.java)
override fun write(out: JsonWriter, value: Json2Function?) {
TODO("Not yet implemented")
}
override fun read(reader: JsonReader): Json2Function? {
if (reader.peek() == JsonToken.NULL)
return null
reader.beginArray()
val interpolation = JsonFunction.INTERPOLATION_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing interpolation")
val constraint = JsonFunction.CONSTRAINT_ADAPTER.read(reader) ?: throw JsonSyntaxException("Missing constraint")
val xRanges = ArrayList<Ranges>()
val yRanges = ArrayList<Ranges>()
val elements = elements.read(reader)
for ((i, row) in elements.withIndex()) {
if (row !is JsonArray) {
throw JsonSyntaxException("Invalid function ranges at row $i")
}
if (row.size() < 2) {
throw JsonSyntaxException("Row at $i should have at least 2 elements")
}
val edge = row[0].asDouble
if (row[1] is JsonArray) {
val ySource = row[1].asJsonArray
if (ySource.size() < 2) {
throw JsonSyntaxException("Y ranges at row $i should have at least 2 elements")
}
val ranges = ImmutableList.Builder<DoubleRange>()
var prev = ySource[0].asDouble
for (elem in 1 until ySource.size()) {
val v = ySource[elem].asDouble
ranges.add(DoubleRange(prev, v))
prev = v
}
yRanges.add(Ranges(interpolation, edge, ranges.build()))
} else {
if (row.size() < 3) {
throw JsonSyntaxException("Row at $i should have at least 3 elements")
}
val ranges = ImmutableList.Builder<DoubleRange>()
var prev = row[1].asDouble
for (elem in 2 until row.size()) {
val v = row[elem].asDouble
ranges.add(DoubleRange(prev, v))
prev = v
}
xRanges.add(Ranges(interpolation, edge, ranges.build()))
}
}
reader.endArray()
if (xRanges.isEmpty()) {
throw JsonSyntaxException("No X ranges")
}
if (yRanges.isEmpty()) {
throw JsonSyntaxException("No Y ranges")
}
val size = xRanges.first().ranges.size
var broken = xRanges.firstOrNull { it.ranges.size != size }
if (broken != null) {
throw JsonSyntaxException("One or more of X ranges are having different size (found ${broken.ranges.size}, expected $size)")
}
broken = yRanges.firstOrNull { it.ranges.size != size }
if (broken != null) {
throw JsonSyntaxException("One or more of Y ranges are having different size (found ${broken.ranges.size}, expected $size)")
}
return Json2Function(interpolation, constraint, xRanges, yRanges)
}
} as TypeAdapter<T>
}
return null
}
}
}

View File

@ -0,0 +1,54 @@
package ru.dbotthepony.kstarbound.io
import org.lwjgl.stb.STBImage
import org.lwjgl.system.MemoryUtil.memAddress
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.lang.ref.Cleaner
import java.nio.ByteBuffer
private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") }
class ImageData(val data: ByteBuffer, val width: Int, val height: Int, val amountOfChannels: Int) {
init {
val address = memAddress(data)
cleaner.register(data) { STBImage.nstbi_image_free(address) }
check(width >= 0) { "Invalid width $width" }
check(height >= 0) { "Invalid height $height" }
check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" }
}
operator fun get(x: Int, y: Int): Int {
require(x in 0 until width) { "X out of bounds: $x" }
require(y in 0 until height) { "Y out of bounds: $y" }
val offset = y * width * amountOfChannels + x * amountOfChannels
when (amountOfChannels) {
4 -> return data[offset].toInt() or
data[offset + 1].toInt().shl(8) or
data[offset + 2].toInt().shl(16) or
data[offset + 3].toInt().shl(24)
3 -> return data[offset].toInt() or
data[offset + 1].toInt().shl(8) or
data[offset + 2].toInt().shl(16)
2 -> return data[offset].toInt() or
data[offset + 1].toInt().shl(8)
1 -> return data[offset].toInt()
else -> throw IllegalStateException()
}
}
fun worldSpaces(pos: Vector2d, spaceScan: Double, flip: Boolean): List<Vector2i> {
when (amountOfChannels) {
3 -> TODO()
4 -> TODO()
else -> throw IllegalStateException("Can not check world space taken by image with $amountOfChannels color channels")
}
}
}

View File

@ -24,24 +24,46 @@ class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAda
JsonToken.NUMBER -> JsonPrimitive(LazilyParsedNumber(`in`.nextString()))
JsonToken.BOOLEAN -> if (`in`.nextBoolean()) TRUE else FALSE
JsonToken.NULL -> JsonNull.INSTANCE
JsonToken.BEGIN_ARRAY -> {
val output = JsonArray()
`in`.beginArray()
while (`in`.hasNext()) { output.add(read(`in`)) }
`in`.endArray()
output
}
JsonToken.BEGIN_OBJECT -> {
val output = JsonObject()
`in`.beginObject()
while (`in`.hasNext()) { output.add(stringInterner.intern(`in`.nextName()), read(`in`)) }
`in`.endObject()
output
}
JsonToken.BEGIN_ARRAY -> arrays.read(`in`)
JsonToken.BEGIN_OBJECT -> objects.read(`in`)
else -> throw IllegalArgumentException(p.toString())
}
}
val objects = object : TypeAdapter<JsonObject>() {
override fun write(out: JsonWriter, value: JsonObject?) {
return TypeAdapters.JSON_ELEMENT.write(out, value)
}
override fun read(`in`: JsonReader): JsonObject? {
if (`in`.peek() == JsonToken.NULL)
return null
val output = JsonObject()
`in`.beginObject()
while (`in`.hasNext()) { output.add(stringInterner.intern(`in`.nextName()), this@InternedJsonElementAdapter.read(`in`)) }
`in`.endObject()
return output
}
}
val arrays = object : TypeAdapter<JsonArray>() {
override fun write(out: JsonWriter, value: JsonArray?) {
return TypeAdapters.JSON_ELEMENT.write(out, value)
}
override fun read(`in`: JsonReader): JsonArray? {
if (`in`.peek() == JsonToken.NULL)
return null
val output = JsonArray()
`in`.beginArray()
while (`in`.hasNext()) { output.add(this@InternedJsonElementAdapter.read(`in`)) }
`in`.endArray()
return output
}
}
companion object {
val TRUE = JsonPrimitive(true)
val FALSE = JsonPrimitive(false)

View File

@ -526,6 +526,15 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
if (position > this.top) return null
return this.getBoolOrNil(position)
}
fun push() = this@LuaState.push()
fun push(value: Int) = this@LuaState.push(value)
fun push(value: Long) = this@LuaState.push(value)
fun push(value: Double) = this@LuaState.push(value)
fun push(value: Float) = this@LuaState.push(value)
fun push(value: Boolean) = this@LuaState.push(value)
fun push(value: String?) = this@LuaState.push(value)
fun push(value: JsonElement?) = this@LuaState.push(value)
}
/**

View File

@ -13,7 +13,7 @@ private val messageHandlerLua by lazy { loadInternalScript("message_handler") }
private val configLua by lazy { loadInternalScript("config") }
fun LuaState.exposeConfig(config: JsonElement) {
load(configLua, "starbound.jar!/scripts/config.lua")
load(configLua, "@starbound.jar!/scripts/config.lua")
call()
loadGlobal("config")
@ -30,7 +30,7 @@ fun LuaState.exposeConfig(config: JsonElement) {
class MessageHandler(val lua: LuaState) {
init {
lua.load(messageHandlerLua, "starbound.jar!/scripts/message_handler.lua")
lua.load(messageHandlerLua, "@starbound.jar!/scripts/message_handler.lua")
lua.call()
lua.loadGlobal("message")

View File

@ -196,14 +196,14 @@ class Avatar(val starbound: Starbound, val uniqueId: UUID) {
* Triggers an immediate cleanup of the player's inventory, removing item stacks with 0 quantity. May rarely be required in special cases of making several sequential modifications to the player's inventory within a single tick.
*/
fun cleanupItems() {
TODO()
}
/**
* Adds the specified item to the player's inventory.
*/
fun giveItem(descriptor: ItemDescriptor) {
TODO()
}
/**
@ -297,7 +297,7 @@ class Avatar(val starbound: Starbound, val uniqueId: UUID) {
}
}
fun expose(lua: LuaState) {
fun pushLuaAPI(lua: LuaState) {
lua.load(playerLua)
lua.pushTable()

View File

@ -120,8 +120,8 @@ class QuestInstance(
lua.pop()
avatar.starbound.expose(lua)
avatar.expose(lua)
avatar.starbound.pushLuaAPI(lua)
avatar.pushLuaAPI(lua)
lua.exposeConfig(template.scriptConfig)
}
@ -170,7 +170,7 @@ class QuestInstance(
}
try {
lua.load(script.readToString(), template.script.fullPath)
lua.load(script.readToString(), "@" + template.script.fullPath)
} catch(err: Throwable) {
LOGGER.error("Error loading Lua code for quest ${descriptor.questId}", err)
return false

File diff suppressed because it is too large Load Diff