diff --git a/build.gradle.kts b/build.gradle.kts index 9ff0428d..29f42e96 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ repositories { mavenCentral() maven { - url = uri("https://maven.dbotthepony.ru") + url = uri("https://maven.dbotthepony.ru/") } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 823758fd..a5839a76 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -31,8 +31,8 @@ const val METRES_IN_STARBOUND_UNITf = 0.5f const val PIXELS_IN_STARBOUND_UNIT = 8.0 const val PIXELS_IN_STARBOUND_UNITf = 8.0f -class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) -class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) +// class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) +// class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) object Starbound : IVFS { private val LOGGER = LogManager.getLogger() @@ -40,10 +40,12 @@ object Starbound : IVFS { private val tiles = HashMap() private val projectiles = HashMap() private val parallax = HashMap() + private val functions = HashMap() val tilesAccess: Map = Collections.unmodifiableMap(tiles) val projectilesAccess: Map = Collections.unmodifiableMap(projectiles) val parallaxAccess: Map = Collections.unmodifiableMap(parallax) + val functionsAccess: Map = Collections.unmodifiableMap(functions) val gson: Gson = GsonBuilder() .enableComplexMapKeySerialization() @@ -65,6 +67,7 @@ object Starbound : IVFS { .also(SkyParameters::registerGson) .also(DungeonWorldDef::registerGson) .also(Parallax::registerGson) + .also(JsonFunction::registerGson) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) @@ -149,6 +152,7 @@ object Starbound : IVFS { } } + loadStage(callback, this::loadFunctions, "functions") loadStage(callback, this::loadTileMaterials, "materials") loadStage(callback, this::loadProjectiles, "projectiles") loadStage(callback, this::loadParallax, "parallax definitions") @@ -229,7 +233,8 @@ object Starbound : IVFS { check(tiles[tileDef.materialName] == null) { "Already has material with ID ${tileDef.materialName} loaded!" } tiles[tileDef.materialName] = tileDef } catch (err: Throwable) { - throw TileDefLoadingException("Loading tile file $listedFile", err) + //throw TileDefLoadingException("Loading tile file $listedFile", err) + LOGGER.error("Loading tile file $listedFile", err) } } } @@ -238,18 +243,35 @@ object Starbound : IVFS { private fun loadProjectiles(callback: (String) -> Unit) { for (fs in fileSystems) { - for (listedFile in fs.listAllFiles("projectiles")) { - if (listedFile.endsWith(".projectile")) { - try { - callback("Loading $listedFile") + for (listedFile in fs.listAllFilesWithExtension("projectile")) { + try { + callback("Loading $listedFile") - val def = gson.fromJson(getReader(listedFile), ConfigurableProjectile::class.java).configure(getPathFolder(listedFile)) - check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } - projectiles[def.projectileName] = def - } catch(err: Throwable) { - //throw ProjectileDefLoadingException("Loading projectile file $listedFile", err) - LOGGER.error("Loading projectile file $listedFile", err) + val def = gson.fromJson(getReader(listedFile), ConfigurableProjectile::class.java).configure(getPathFolder(listedFile)) + check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } + projectiles[def.projectileName] = def + } catch(err: Throwable) { + //throw ProjectileDefLoadingException("Loading projectile file $listedFile", err) + LOGGER.error("Loading projectile file $listedFile", err) + } + } + } + } + + private fun loadFunctions(callback: (String) -> Unit) { + for (fs in fileSystems) { + for (listedFile in fs.listAllFilesWithExtension("functions")) { + try { + callback("Loading $listedFile") + + val readObject = loadJson(listedFile) as JsonObject + + for (key in readObject.keySet()) { + val def = gson.fromJson(readObject[key], JsonFunction::class.java) + functions[key] = def } + } catch(err: Throwable) { + LOGGER.error("Loading function file $listedFile", err) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IVFS.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IVFS.kt index 5a03f851..4d26e9a7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IVFS.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IVFS.kt @@ -53,6 +53,19 @@ interface IVFS { return ArrayList(a.size + b.size).also { it.addAll(a); it.addAll(b) } } + fun listAllFilesWithExtension(extension: String): Collection { + val listing = ArrayList() + val ext = ".$extension" + + for (listedFile in listAllFiles("")) { + if (listedFile.endsWith(ext)) { + listing.add(listedFile) + } + } + + return listing + } + fun listAllFiles(path: String): Collection { val lists = mutableListOf>() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt new file mode 100644 index 00000000..96aaf2a9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFunction.kt @@ -0,0 +1,93 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.ImmutableList +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter +import ru.dbotthepony.kstarbound.io.IStringSerializable +import ru.dbotthepony.kstarbound.io.Vector2dTypeAdapter +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +enum class JsonFunctionInterpolation(vararg aliases: String) : IStringSerializable { + LINEAR; + + private val aliases: Array = 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) + } + + companion object { + val ADAPTER: TypeAdapter = CustomEnumTypeAdapter(values()).nullSafe() + } +} + +enum class JsonFunctionConstraint(vararg aliases: String) : IStringSerializable { + CLAMP; + + private val aliases: Array = 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) + } + + companion object { + val ADAPTER: TypeAdapter = CustomEnumTypeAdapter(values()).nullSafe() + } +} + +class JsonFunction( + val interpolation: JsonFunctionInterpolation, + val constraint: JsonFunctionConstraint, + val ranges: List +) { + companion object : TypeAdapter() { + override fun write(out: JsonWriter, value: JsonFunction) { + TODO("Not yet implemented") + } + + override fun read(reader: JsonReader): JsonFunction { + reader.beginArray() + + val interpolation = JsonFunctionInterpolation.ADAPTER.read(reader) + val constraint = JsonFunctionConstraint.ADAPTER.read(reader) + + val ranges = ArrayList() + + while (reader.peek() != JsonToken.END_ARRAY) { + ranges.add(Vector2dTypeAdapter.read(reader)) + } + + reader.endArray() + + return JsonFunction(interpolation, constraint, ImmutableList.copyOf(ranges)) + } + + fun registerGson(gsonBuilder: GsonBuilder) { + gsonBuilder.registerTypeAdapter(JsonFunctionConstraint::class.java, JsonFunctionConstraint.ADAPTER) + gsonBuilder.registerTypeAdapter(JsonFunctionInterpolation::class.java, JsonFunctionInterpolation.ADAPTER) + gsonBuilder.registerTypeAdapter(JsonFunction::class.java, this) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt index 8d120de0..d56d8840 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt @@ -1,10 +1,13 @@ package ru.dbotthepony.kstarbound.io +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import ru.dbotthepony.kstarbound.api.IVFS import java.io.* import java.nio.ByteBuffer import java.nio.channels.Channels import java.util.* +import kotlin.collections.ArrayList import kotlin.collections.HashMap private fun readHeader(reader: RandomAccessFile, required: Int) { @@ -124,6 +127,8 @@ class StarboundPakDirectory(val name: String, val parent: StarboundPakDirectory? class StarboundPak(val path: File, callback: (finished: Boolean, status: String) -> Unit = { _, _ -> }) : Closeable, IVFS { val reader = RandomAccessFile(path, "r") + private val filesByExtension = Object2ObjectArrayMap>() + private val filesByExtensionPath = Object2ObjectArrayMap>() init { readHeader(reader, 0x53) // S @@ -184,6 +189,13 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) indexNodes[read.name] = read root.resolve(read.directoryHiearchy).writeFile(read) + + val last = read.name.substringAfterLast('/').substringAfterLast('.', "") + + if (last != "") { + filesByExtension.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read) + filesByExtensionPath.computeIfAbsent(last, Object2ObjectFunction { ArrayList() }).add(read.name) + } } catch (err: Throwable) { throw IOException("Reading index node at $i", err) } @@ -212,4 +224,8 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) override fun close() { reader.close() } + + override fun listAllFilesWithExtension(extension: String): Collection { + return filesByExtensionPath[extension]?.let(Collections::unmodifiableList) ?: listOf() + } }