diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt index 83dfb253..9260c45d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt @@ -50,11 +50,14 @@ fun String.sintern(): String = Starbound.STRINGS.intern(this) inline fun Gson.fromJson(reader: JsonReader): T? = fromJson(reader, T::class.java) -fun Collection.batch(executor: ForkJoinPool, batchSize: Int = 16, mapper: (IStarboundFile) -> KOptional>): Stream> { +/** + * guarantees even distribution of tasks while also preserving encountered order of elements + */ +fun Collection.batch(executor: ForkJoinPool, batchSize: Int = 16, mapper: (IStarboundFile) -> KOptional): Stream { require(batchSize >= 1) { "Invalid batch size: $batchSize" } if (batchSize == 1 || size <= batchSize) { - val tasks = ArrayList>>>() + val tasks = ArrayList>>() for (listedFile in this) { tasks.add(executor.submit(Callable { mapper.invoke(listedFile) })) @@ -63,7 +66,7 @@ fun Collection.batch(executor: ForkJoinPool, batchSize return tasks.stream().map { it.join() }.filter { it.isPresent }.map { it.value } } - val batches = ArrayList>>>>() + val batches = ArrayList>>>() var batch = ArrayList(batchSize) for (listedFile in this) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 9a9ce81c..7d379e4e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -11,7 +11,6 @@ import ru.dbotthepony.kstarbound.player.Avatar import ru.dbotthepony.kstarbound.player.QuestDescriptor import ru.dbotthepony.kstarbound.player.QuestInstance import ru.dbotthepony.kstarbound.util.JVMTimeSource -import ru.dbotthepony.kstarbound.world.api.AbstractCell import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kstarbound.io.json.VersionedJson import ru.dbotthepony.kstarbound.io.readVarInt @@ -118,7 +117,7 @@ fun main() { val rand = Random() for (i in 0 .. 128) { - val item = ItemEntity(client.world!!, Registries.items.values.random().value) + val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value) item.position = Vector2d(225.0 - i, 785.0) item.spawn() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt deleted file mode 100644 index 8d4810d2..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt +++ /dev/null @@ -1,185 +0,0 @@ -package ru.dbotthepony.kstarbound - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import com.google.gson.internal.bind.JsonTreeReader -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.api.IStarboundFile -import ru.dbotthepony.kstarbound.lua.LuaState -import ru.dbotthepony.kstarbound.util.AssetPathStack -import ru.dbotthepony.kstarbound.util.set -import ru.dbotthepony.kstarbound.util.traverseJsonPath -import java.util.* -import kotlin.reflect.KClass - -inline fun ObjectRegistry(name: String, noinline key: ((T) -> String)? = null, noinline intKey: ((T) -> Int)? = null): ObjectRegistry { - return ObjectRegistry(T::class, name, key, intKey) -} - -fun mergeJsonElements(source: JsonObject, destination: JsonObject): JsonObject { - for ((k, v) in source.entrySet()) { - if (!destination.has(k)) { - destination[k] = v.deepCopy() - } else { - mergeJsonElements(v, destination[k]) - } - } - - return destination -} - -fun mergeJsonElements(source: JsonArray, destination: JsonArray): JsonArray { - for ((i, v) in source.withIndex()) { - if (i >= destination.size()) { - destination.add(v.deepCopy()) - } else { - destination[i] = mergeJsonElements(v, destination[i]) - } - } - - return destination -} - -fun mergeJsonElements(source: JsonElement, destination: JsonElement): JsonElement { - if (destination is JsonPrimitive) { - return destination - } - - if (destination is JsonObject && source is JsonObject) { - return mergeJsonElements(source, destination) - } - - if (destination is JsonArray && source is JsonArray) { - return mergeJsonElements(source, destination) - } - - return destination -} - -class RegistryObject( - /** - * Объект реестра - */ - val value: T, - /** - * Оригинальный JSON объекта без каких либо изменений - */ - val json: JsonElement, - /** - * Файл, откуда данный объект был загружен - */ - val file: IStarboundFile, -) { - val jsonObject get() = json as JsonObject - - fun push(lua: LuaState) { - lua.push(toJson()) - } - - fun push(lua: LuaState.ArgStack) { - lua.push(toJson()) - } - - /** - * Возвращает полную (обработанную) структуру [JsonObject] объекта [value] - * - * Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны - * в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт. - */ - fun toJson(): JsonElement { - return mergeJsonElements(json, Starbound.gson.toJsonTree(value)) - } - - fun traverseJsonPath(path: String): JsonElement? { - return traverseJsonPath(path, mergeJsonElements(json, Starbound.gson.toJsonTree(value))) - } - - override fun equals(other: Any?): Boolean { - return other === this || other is RegistryObject<*> && other.value == value && other.json == json - } - - private var computedHash = false - private var hash = 0 - - override fun hashCode(): Int { - if (!computedHash) { - hash = value.hashCode().rotateRight(13) xor json.hashCode() - computedHash = true - } - - return hash - } - - override fun toString(): String { - return "RegistryObject[$value from $file]" - } -} - -class ObjectRegistry(val clazz: KClass, val name: String, val key: ((T) -> String)? = null, val intKey: ((T) -> Int)? = null) { - val objects = Object2ObjectOpenHashMap>() - val intObjects = Int2ObjectOpenHashMap>() - - val values get() = objects.values - - operator fun get(index: String) = objects[index] - operator fun get(index: Int): RegistryObject? = intObjects[index] - operator fun contains(index: String) = index in objects - operator fun contains(index: Int) = index in intObjects - - fun clear() { - objects.clear() - intObjects.clear() - } - - fun add(file: IStarboundFile): Boolean { - return AssetPathStack(file.computeDirectory()) { - val elem = Starbound.gson.fromJson(file.reader(), JsonElement::class.java) - val value = Starbound.gson.fromJson(JsonTreeReader(elem), clazz.java) - add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper")) - } - } - - fun add(value: RegistryObject): Boolean { - return add(value, this.key?.invoke(value.value) ?: throw UnsupportedOperationException("No key mapper")) - } - - fun add(value: T, json: JsonElement, file: IStarboundFile): Boolean { - return add(RegistryObject(value, json, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper")) - } - - fun add(value: T, json: JsonElement, file: IStarboundFile, key: String): Boolean { - return add(RegistryObject(value, json, file), key) - } - - private val lock = Any() - - private fun add(value: RegistryObject, key: String): Boolean { - synchronized(lock) { - val existing = objects.put(key, value) - - if (existing != null) { - LOGGER.warn("Registry $name already has object with key $key! Overwriting. (old originated from ${existing.file}, new originate from ${value.file}).") - } - - if (this.intKey == null) - return existing != null - - val intKey = this.intKey.invoke(value.value) - val intExisting = intObjects.put(intKey, value) - - if (intExisting != null) { - 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 - } - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt index 565cbaf3..43f1a16a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt @@ -1,63 +1,66 @@ package ru.dbotthepony.kstarbound -import com.google.gson.Gson import com.google.gson.JsonElement -import com.google.gson.JsonObject -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 ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition import ru.dbotthepony.kstarbound.util.KOptional -import java.util.Collections -import java.util.LinkedList -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService +import ru.dbotthepony.kstarbound.util.ParallelPerform +import java.util.* import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask -import java.util.concurrent.Future +import java.util.concurrent.locks.ReentrantLock +import kotlin.collections.ArrayList +import kotlin.concurrent.withLock object RecipeRegistry { private val LOGGER = LogManager.getLogger() - private val recipesInternal = ArrayList>() - private val group2recipesInternal = Object2ObjectOpenHashMap>>() - private val group2recipesBacking = Object2ObjectOpenHashMap>>() - private val output2recipesInternal = Object2ObjectOpenHashMap>>() - private val output2recipesBacking = Object2ObjectOpenHashMap>>() - private val input2recipesInternal = Object2ObjectOpenHashMap>>() - private val input2recipesBacking = Object2ObjectOpenHashMap>>() + data class Entry(val value: RecipeDefinition, val json: JsonElement, val file: IStarboundFile) - val recipes: List> = Collections.unmodifiableList(recipesInternal) - val group2recipes: Map>> = Collections.unmodifiableMap(group2recipesBacking) - val output2recipes: Map>> = Collections.unmodifiableMap(output2recipesBacking) - val input2recipes: Map>> = Collections.unmodifiableMap(input2recipesBacking) + private val recipesInternal = ArrayList() + private val group2recipesInternal = Object2ObjectOpenHashMap>() + private val group2recipesBacking = Object2ObjectOpenHashMap>() + private val output2recipesInternal = Object2ObjectOpenHashMap>() + private val output2recipesBacking = Object2ObjectOpenHashMap>() + private val input2recipesInternal = Object2ObjectOpenHashMap>() + private val input2recipesBacking = Object2ObjectOpenHashMap>() - fun add(recipe: RegistryObject) { - val value = recipe.value - recipesInternal.add(recipe) + val recipes: List = Collections.unmodifiableList(recipesInternal) + val group2recipes: Map> = Collections.unmodifiableMap(group2recipesBacking) + val output2recipes: Map> = Collections.unmodifiableMap(output2recipesBacking) + val input2recipes: Map> = Collections.unmodifiableMap(input2recipesBacking) - for (group in value.groups) { - group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> - LinkedList>().also { - group2recipesBacking[p as String] = Collections.unmodifiableList(it) - } - }).add(recipe) - } + private val lock = ReentrantLock() - output2recipesInternal.computeIfAbsent(value.output.item.name, Object2ObjectFunction { p -> - LinkedList>().also { - output2recipesBacking[p as String] = Collections.unmodifiableList(it) + fun add(recipe: Entry) { + lock.withLock { + val value = recipe.value + recipesInternal.add(recipe) + + for (group in value.groups) { + group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> + ArrayList(1).also { + group2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) } - }).add(recipe) - for (input in value.input) { - input2recipesInternal.computeIfAbsent(input.item.name, Object2ObjectFunction { p -> - LinkedList>().also { - input2recipesBacking[p as String] = Collections.unmodifiableList(it) + output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p -> + ArrayList(1).also { + output2recipesBacking[p as String] = Collections.unmodifiableList(it) } }).add(recipe) + + for (input in value.input) { + input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p -> + ArrayList(1).also { + input2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) + } } } @@ -77,12 +80,12 @@ object RecipeRegistry { line.text = ("Loading $listedFile") val json = elements.read(listedFile.jsonReader()) val value = recipes.fromJsonTree(json) - line.elements.incrementAndGet() - KOptional(RegistryObject(value, json, listedFile)) + KOptional.of(Entry(value, json, listedFile)) } catch (err: Throwable) { LOGGER.error("Loading recipe definition file $listedFile", err) - line.elements.incrementAndGet() KOptional.empty() + } finally { + line.elements.incrementAndGet() } }.forEach { add(it) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt index 2243044b..2788eeb3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt @@ -36,33 +36,64 @@ import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.KOptional -import java.util.concurrent.Callable -import java.util.concurrent.ExecutorService +import ru.dbotthepony.kstarbound.util.ParallelPerform import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask -import java.util.concurrent.Future object Registries { private val LOGGER = LogManager.getLogger() - val tiles = ObjectRegistry("tiles", TileDefinition::materialName, TileDefinition::materialId) - val tileModifiers = ObjectRegistry("tile modifiers", MaterialModifier::modName, MaterialModifier::modId) - val liquid = ObjectRegistry("liquid", LiquidDefinition::name, LiquidDefinition::liquidId) - val species = ObjectRegistry("species", Species::kind) - val statusEffects = ObjectRegistry("status effects", StatusEffectDefinition::name) - val particles = ObjectRegistry("particles", ParticleDefinition::kind) - val items = ObjectRegistry("items", IItemDefinition::itemName) - val questTemplates = ObjectRegistry("quest templates", QuestTemplate::id) - val techs = ObjectRegistry("techs", TechDefinition::name) - val jsonFunctions = ObjectRegistry("json functions") - val json2Functions = ObjectRegistry("json 2functions") - val npcTypes = ObjectRegistry("npc types", NpcTypeDefinition::type) - val projectiles = ObjectRegistry("projectiles", ProjectileDefinition::projectileName) - val tenants = ObjectRegistry("tenants", TenantDefinition::name) - val treasurePools = ObjectRegistry("treasure pools", TreasurePoolDefinition::name) - val monsterSkills = ObjectRegistry("monster skills", MonsterSkillDefinition::name) - val monsterTypes = ObjectRegistry("monster types", MonsterTypeDefinition::type) - val worldObjects = ObjectRegistry("objects", ObjectDefinition::objectName) + val tiles = Registry("tiles") + val tileModifiers = Registry("tile modifiers") + val liquid = Registry("liquid") + val species = Registry("species") + val statusEffects = Registry("status effects") + val particles = Registry("particles") + val items = Registry("items") + val questTemplates = Registry("quest templates") + val techs = Registry("techs") + val jsonFunctions = Registry("json functions") + val json2Functions = Registry("json 2functions") + val npcTypes = Registry("npc types") + val projectiles = Registry("projectiles") + val tenants = Registry("tenants") + val treasurePools = Registry("treasure pools") + val monsterSkills = Registry("monster skills") + val monsterTypes = Registry("monster types") + val worldObjects = Registry("objects") + + private fun key(mapper: (T) -> String): (T) -> Pair { + return { mapper.invoke(it) to null } + } + + private fun key(mapper: (T) -> String, mapperInt: (T) -> Int): (T) -> Pair { + return { mapper.invoke(it) to mapperInt.invoke(it) } + } + + fun validate(): Boolean { + var any = false + + any = !tiles.validate() || any + any = !tileModifiers.validate() || any + any = !liquid.validate() || any + any = !species.validate() || any + any = !statusEffects.validate() || any + any = !particles.validate() || any + any = !items.validate() || any + any = !questTemplates.validate() || any + any = !techs.validate() || any + any = !jsonFunctions.validate() || any + any = !json2Functions.validate() || any + any = !npcTypes.validate() || any + any = !projectiles.validate() || any + any = !tenants.validate() || any + any = !treasurePools.validate() || any + any = !monsterSkills.validate() || any + any = !monsterTypes.validate() || any + any = !worldObjects.validate() || any + + return !any + } private fun loadStage( log: ILoadingLog, @@ -81,8 +112,9 @@ object Registries { private inline fun loadStage( log: ILoadingLog, executor: ForkJoinPool, - registry: ObjectRegistry, + registry: Registry, files: List, + noinline keyProvider: (T) -> Pair, name: String = registry.name ) { val adapter = Starbound.gson.getAdapter(T::class.java) @@ -95,19 +127,29 @@ object Registries { try { it.text = "Loading $listedFile" - val result = AssetPathStack(listedFile.computeDirectory()) { + AssetPathStack(listedFile.computeDirectory()) { val elem = elementAdapter.read(listedFile.jsonReader()) - RegistryObject(adapter.fromJsonTree(elem), elem, listedFile) - } + val read = adapter.fromJsonTree(elem) + val keys = keyProvider(read); - it.elements.incrementAndGet() - KOptional(result) + KOptional.of { + try { + if (keys.second != null) + registry.add(keys.first, keys.second!!, read, elem, listedFile) + else + registry.add(keys.first, read, elem, listedFile) + } catch (err: Throwable) { + LOGGER.error("Loading ${registry.name} definition file $listedFile", err); + } + } + } } catch (err: Throwable) { - LOGGER.error("Loading ${registry.name} definition file $listedFile", err) - it.elements.incrementAndGet() + LOGGER.error("Loading ${registry.name} definition file $listedFile", err); KOptional.empty() + } finally { + it.elements.incrementAndGet() } - }.forEach { registry.add(it) } + }.forEach { it.invoke() } }, name) } @@ -120,20 +162,20 @@ object Registries { tasks.add(executor.submit { loadStage(log, { loadJson2Functions(it, fileTree["2functions"] ?: listOf()) }, "json 2functions") }) tasks.add(executor.submit { loadStage(log, { loadTreasurePools(it, fileTree["treasurepools"] ?: listOf()) }, "treasure pools") }) - tasks.add(executor.submit { loadStage(log, executor, tiles, fileTree["material"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, tileModifiers, fileTree["matmod"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, liquid, fileTree["liquid"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, worldObjects, fileTree["object"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, statusEffects, fileTree["statuseffect"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, species, fileTree["species"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, particles, fileTree["particle"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, questTemplates, fileTree["questtemplate"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, techs, fileTree["tech"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, npcTypes, fileTree["npctype"] ?: listOf()) }) - // tasks.add(executor.submit { loadStage(log, executor, projectiles, ext2files["projectile"] ?: listOf()) }) - // tasks.add(executor.submit { loadStage(log, executor, tenants, ext2files["tenant"] ?: listOf()) }) - tasks.add(executor.submit { loadStage(log, executor, monsterSkills, fileTree["monsterskill"] ?: listOf()) }) - // tasks.add(executor.submit { loadStage(log, _monsterTypes, ext2files["monstertype"] ?: listOf()) }) + tasks.add(executor.submit { loadStage(log, executor, tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)) }) + tasks.add(executor.submit { loadStage(log, executor, tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId)) }) + tasks.add(executor.submit { loadStage(log, executor, liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)) }) + + tasks.add(executor.submit { loadStage(log, executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)) }) + tasks.add(executor.submit { loadStage(log, executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)) }) + tasks.add(executor.submit { loadStage(log, executor, species, fileTree["species"] ?: listOf(), key(Species::kind)) }) + tasks.add(executor.submit { loadStage(log, executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)) }) + tasks.add(executor.submit { loadStage(log, executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)) }) + tasks.add(executor.submit { loadStage(log, executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)) }) + tasks.add(executor.submit { loadStage(log, executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)) }) + // tasks.add(executor.submit { loadStage(log, executor, projectiles, ext2files["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)) }) + // tasks.add(executor.submit { loadStage(log, executor, tenants, ext2files["tenant"] ?: listOf(), key(TenantDefinition::name)) }) + tasks.add(executor.submit { loadStage(log, executor, monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)) }) return tasks } @@ -164,19 +206,18 @@ object Registries { line.maxElements = fileList.size val time = System.nanoTime() - fileList.batch(executor) { listedFile -> + ParallelPerform(fileList.spliterator(), { listedFile -> try { line.text = "Loading $listedFile" val json = objects.read(listedFile.jsonReader()) val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) } - line.elements.incrementAndGet() - KOptional(RegistryObject(def, json, listedFile)) + items.add(def.itemName, def, json, listedFile) } catch (err: Throwable) { LOGGER.error("Loading item definition file $listedFile", err) + } finally { line.elements.incrementAndGet() - KOptional.empty() } - }.forEach { items.add(it) } + }).fork().join() line.text = "Loaded items '$ext' in ${((System.nanoTime() - time) / 1_000_000.0).toLong()}ms" }) @@ -196,7 +237,7 @@ object Registries { try { line.text = ("Loading $k from $listedFile") val fn = Starbound.gson.fromJson(JsonTreeReader(v), JsonFunction::class.java) - jsonFunctions.add(fn, v, listedFile, k) + jsonFunctions.add(k, fn, v, listedFile) } catch (err: Exception) { LOGGER.error("Loading json function definition $k from file $listedFile", err) } @@ -222,7 +263,7 @@ object Registries { try { line.text = ("Loading $k from $listedFile") val fn = Starbound.gson.fromJson(JsonTreeReader(v), Json2Function::class.java) - json2Functions.add(fn, v, listedFile, k) + json2Functions.add(k, fn, v, listedFile) } catch (err: Throwable) { LOGGER.error("Loading json 2function definition $k from file $listedFile", err) } @@ -249,7 +290,7 @@ object Registries { line.text = ("Loading $k from $listedFile") val result = Starbound.gson.fromJson(JsonTreeReader(v), TreasurePoolDefinition::class.java) result.name = k - treasurePools.add(result, v, listedFile) + treasurePools.add(result.name, result, v, listedFile) } catch (err: Throwable) { LOGGER.error("Loading treasure pool definition $k from file $listedFile", err) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt new file mode 100644 index 00000000..dd2f14dd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt @@ -0,0 +1,350 @@ +package ru.dbotthepony.kstarbound + +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +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 it.unimi.dsi.fastutil.ints.Int2ObjectFunction +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectMap +import it.unimi.dsi.fastutil.objects.Object2ObjectMaps +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kstarbound.api.IStarboundFile +import ru.dbotthepony.kstarbound.io.json.consumeNull +import ru.dbotthepony.kstarbound.util.Either +import java.lang.reflect.ParameterizedType +import java.util.concurrent.locks.ReentrantLock +import java.util.function.Supplier +import kotlin.collections.contains +import kotlin.collections.set +import kotlin.concurrent.withLock +import ru.dbotthepony.kstarbound.util.traverseJsonPath + +inline fun Registry.adapter(): TypeAdapterFactory { + return object : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + val subtype = type.type as? ParameterizedType ?: return null + if (subtype.actualTypeArguments.size != 1 || subtype.actualTypeArguments[0] != S::class.java) return null + + return when (type.rawType) { + Registry.Entry::class.java -> { + object : TypeAdapter>() { + override fun write(out: JsonWriter, value: Registry.Entry?) { + if (value != null) { + out.value(value.key) + } else { + out.nullValue() + } + } + + override fun read(`in`: JsonReader): Registry.Entry? { + if (`in`.consumeNull()) { + return null + } else if (`in`.peek() == JsonToken.STRING) { + return this@adapter[`in`.nextString()] + } else if (`in`.peek() == JsonToken.NUMBER) { + return this@adapter[`in`.nextInt()] + } else { + throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}") + } + } + } + } + + Registry.Ref::class.java -> { + object : TypeAdapter>() { + override fun write(out: JsonWriter, value: Registry.Ref?) { + if (value != null) { + value.key.map(out::value, out::value) + } else { + out.nullValue() + } + } + + override fun read(`in`: JsonReader): Registry.Ref? { + if (`in`.consumeNull()) { + return null + } else if (`in`.peek() == JsonToken.STRING) { + return this@adapter.ref(`in`.nextString()) + } else if (`in`.peek() == JsonToken.NUMBER) { + return this@adapter.ref(`in`.nextInt()) + } else { + throw JsonSyntaxException("Expected registry key or registry id, got ${`in`.peek()}") + } + } + } + } + + else -> null + } as TypeAdapter? + } + } +} + +class Registry(val name: String) { + private val keysInternal = Object2ObjectOpenHashMap() + private val idsInternal = Int2ObjectOpenHashMap() + private val keyRefs = Object2ObjectOpenHashMap() + private val idRefs = Int2ObjectOpenHashMap() + + private val lock = ReentrantLock() + + val keys: Object2ObjectMap> = Object2ObjectMaps.unmodifiable(keysInternal) + val ids: Int2ObjectMap> = Int2ObjectMaps.unmodifiable(idsInternal) + + sealed class Ref : Supplier?> { + abstract val key: Either + abstract val entry: Entry? + abstract val registry: Registry + + val isPresent: Boolean + get() = value != null + + val value: T? + get() = entry?.value + + fun traverseJsonPath(path: String): JsonElement? { + return traverseJsonPath(path, entry?.json ?: return null) + } + + final override fun get(): Entry? { + return entry + } + } + + sealed class Entry : Supplier { + abstract val key: String + abstract val id: Int? + abstract val value: T + abstract val json: JsonElement + abstract val file: IStarboundFile? + abstract val registry: Registry + abstract val isBuiltin: Boolean + + fun traverseJsonPath(path: String): JsonElement? { + return traverseJsonPath(path, json) + } + + final override fun get(): T { + return value + } + + val jsonObject: JsonObject + get() = json as JsonObject + } + + private inner class Impl(override val key: String, override var value: T, override var id: Int? = null) : Entry() { + override var json: JsonElement = JsonNull.INSTANCE + override var file: IStarboundFile? = null + override var isBuiltin: Boolean = false + + override fun equals(other: Any?): Boolean { + return this === other + } + + override fun hashCode(): Int { + return key.hashCode() + } + + override fun toString(): String { + return "Registry.Entry[key=$key, id=$id, registry=$name]" + } + + override val registry: Registry + get() = this@Registry + } + + private inner class RefImpl(override val key: Either) : Ref() { + override var entry: Entry? = null + + override fun equals(other: Any?): Boolean { + return this === other || other is Registry<*>.RefImpl && other.key == key && other.registry == registry + } + + override fun hashCode(): Int { + return key.hashCode() + } + + override fun toString(): String { + return "Registry.Ref[key=$key, bound=${entry != null}, registry=$name]" + } + + override val registry: Registry + get() = this@Registry + } + + operator fun get(index: String): Entry? = lock.withLock { keysInternal[index] } + operator fun get(index: Int): Entry? = lock.withLock { idsInternal[index] } + + fun ref(index: String): Ref = lock.withLock { + keyRefs.computeIfAbsent(index, Object2ObjectFunction { + val ref = RefImpl(Either.left(it as String)) + ref.entry = keysInternal[it] + ref + }) + } + + fun ref(index: Int): Ref = lock.withLock { + idRefs.computeIfAbsent(index, Int2ObjectFunction { + val ref = RefImpl(Either.right(it)) + ref.entry = idsInternal[it] + ref + }) + } + + operator fun contains(index: String) = lock.withLock { index in keysInternal } + operator fun contains(index: Int) = lock.withLock { index in idsInternal } + + fun validate(): Boolean { + var any = true + + keyRefs.values.forEach { + if (!it.isPresent) { + LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems") + any = false + } + } + + idRefs.values.forEach { + if (!it.isPresent) { + LOGGER.warn("Registry '$name' reference with ID '${it.key.right()}' is not bound to value, expect problems") + any = false + } + } + + return any + } + + fun add(key: String, value: T, json: JsonElement, file: IStarboundFile): Entry { + lock.withLock { + if (key in keysInternal) { + LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: ""})") + } + + val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) }) + + check(!entry.isBuiltin) { "Trying to redefine builtin entry" } + + entry.id?.let { + idsInternal.remove(it) + idRefs[it]?.entry = null + } + + entry.id = null + entry.value = value + entry.json = json + entry.file = file + + keyRefs[key]?.entry = entry + + return entry + } + } + + fun add(key: String, id: Int, value: T, json: JsonElement, file: IStarboundFile): Entry { + lock.withLock { + if (key in keysInternal) { + LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: ""})") + } + + if (id in idsInternal) { + LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from $file; old def originate from ${idsInternal[id]?.file ?: ""})") + } + + val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) }) + + check(!entry.isBuiltin) { "Trying to redefine builtin entry" } + + entry.id?.let { + idsInternal.remove(it) + idRefs[it]?.entry = null + } + + entry.id = id + entry.value = value + entry.json = json + entry.file = file + + keyRefs[key]?.entry = entry + idRefs[id]?.entry = entry + idsInternal[id] = entry + + return entry + } + } + + fun add(key: String, value: T, isBuiltin: Boolean = false): Entry { + lock.withLock { + if (key in keysInternal) { + LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from ; old def originate from ${keysInternal[key]?.file ?: ""})") + } + + val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) }) + + check(!entry.isBuiltin || isBuiltin) { "Trying to redefine builtin entry" } + + entry.id?.let { + idsInternal.remove(it) + idRefs[it]?.entry = null + } + + entry.id = null + entry.value = value + entry.json = JsonNull.INSTANCE + entry.file = null + entry.isBuiltin = isBuiltin + + keyRefs[key]?.entry = entry + + return entry + } + } + + fun add(key: String, id: Int, value: T, isBuiltin: Boolean = false): Entry { + lock.withLock { + if (key in keysInternal) { + LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from ; old def originate from ${keysInternal[key]?.file ?: ""})") + } + + if (id in idsInternal) { + LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from ; old def originate from ${idsInternal[id]?.file ?: ""})") + } + + val entry = keysInternal.computeIfAbsent(key, Object2ObjectFunction { Impl(key, value) }) + + check(!entry.isBuiltin || isBuiltin) { "Trying to redefine builtin entry" } + + entry.id?.let { + idsInternal.remove(it) + idRefs[it]?.entry = null + } + + entry.id = id + entry.value = value + entry.json = JsonNull.INSTANCE + entry.file = null + entry.isBuiltin = isBuiltin + + keyRefs[key]?.entry = entry + idRefs[id]?.entry = entry + idsInternal[id] = entry + + return entry + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 483c8418..0968550d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -194,40 +194,30 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(Poly.Companion) - registerTypeAdapterFactory(with(RegistryReferenceFactory()) { - add(Registries.tiles) - add(Registries.tileModifiers) - add(Registries.liquid) - add(Registries.items) - add(Registries.species) - add(Registries.statusEffects) - add(Registries.particles) - add(Registries.questTemplates) - add(Registries.techs) - add(Registries.jsonFunctions) - add(Registries.json2Functions) - add(Registries.npcTypes) - add(Registries.projectiles) - add(Registries.tenants) - add(Registries.treasurePools) - add(Registries.monsterSkills) - add(Registries.monsterTypes) - add(Registries.worldObjects) - }) + registerTypeAdapterFactory(Registries.tiles.adapter()) + registerTypeAdapterFactory(Registries.tileModifiers.adapter()) + registerTypeAdapterFactory(Registries.liquid.adapter()) + registerTypeAdapterFactory(Registries.items.adapter()) + registerTypeAdapterFactory(Registries.species.adapter()) + registerTypeAdapterFactory(Registries.statusEffects.adapter()) + registerTypeAdapterFactory(Registries.particles.adapter()) + registerTypeAdapterFactory(Registries.questTemplates.adapter()) + registerTypeAdapterFactory(Registries.techs.adapter()) + registerTypeAdapterFactory(Registries.jsonFunctions.adapter()) + registerTypeAdapterFactory(Registries.json2Functions.adapter()) + registerTypeAdapterFactory(Registries.npcTypes.adapter()) + registerTypeAdapterFactory(Registries.projectiles.adapter()) + registerTypeAdapterFactory(Registries.tenants.adapter()) + registerTypeAdapterFactory(Registries.treasurePools.adapter()) + registerTypeAdapterFactory(Registries.monsterSkills.adapter()) + registerTypeAdapterFactory(Registries.monsterTypes.adapter()) + registerTypeAdapterFactory(Registries.worldObjects.adapter()) registerTypeAdapter(LongRangeAdapter) create() } - init { - val f = NonExistingFile("/metamaterials.config") - - for (material in BuiltinMetaMaterials.MATERIALS) { - Registries.tiles.add(material, JsonNull.INSTANCE, f) - } - } - fun item(name: String): ItemStack { return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY) } @@ -411,7 +401,7 @@ object Starbound : ISBFileLocator { state.setTableFunction("recipesForItem", this) { args -> args.lua.push(JsonArray().also { a -> - RecipeRegistry.output2recipes[args.getString()]?.stream()?.map { it.toJson() }?.forEach { + RecipeRegistry.output2recipes[args.getString()]?.stream()?.map { it.json }?.forEach { a.add(it) } }) @@ -449,7 +439,7 @@ object Starbound : ISBFileLocator { args.push() } else { args.push(JsonObject().also { - it["directory"] = item.item!!.file.computeDirectory() + it["directory"] = item.item?.file?.computeDirectory()?.let(::JsonPrimitive) ?: JsonNull.INSTANCE it["config"] = item.item!!.json it["parameters"] = item.parameters }) @@ -481,7 +471,7 @@ object Starbound : ISBFileLocator { state.setTableFunction("tenantConfig", this) { args -> // Json root.tenantConfig(String tenantName) val name = args.getString() - Registries.tenants[name]?.push(args) ?: throw NoSuchElementException("No such tenant $name") + args.push(Registries.tenants[name] ?: throw NoSuchElementException("No such tenant $name")) 1 } @@ -496,11 +486,11 @@ object Starbound : ISBFileLocator { } } - args.push(Registries.tenants.values + args.push(Registries.tenants.keys.values .stream() .filter { it.value.test(actualTags) } .sorted { a, b -> b.value.compareTo(a.value) } - .map { it.toJson() } + .map { it.json } .collect(JsonArrayCollector)) 1 @@ -517,7 +507,7 @@ object Starbound : ISBFileLocator { liquid = Registries.liquid[id]?.value ?: throw NoSuchElementException("No such liquid with ID $id") } - args.lua.pushStrings(liquid.statusEffects.stream().map { it.value?.value?.name }.filterNotNull().toList()) + args.lua.pushStrings(liquid.statusEffects.stream().map { it.value?.name }.filterNotNull().toList()) 1 } @@ -711,7 +701,7 @@ object Starbound : ISBFileLocator { state.setTableFunction("techConfig", this) { args -> val name = args.getString() val tech = Registries.techs[name] ?: throw NoSuchElementException("No such tech $name") - tech.push(args) + args.push(tech) 1 } @@ -898,6 +888,8 @@ object Starbound : ISBFileLocator { if (!parallel) pool.shutdown() + Registries.validate() + initializing = false initialized = true log.line("Finished loading in ${System.currentTimeMillis() - time}ms") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt index 5f766386..537cb8d2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/RenderLayer.kt @@ -71,9 +71,9 @@ enum class RenderLayer { fun tileLayer(isBackground: Boolean, isModifier: Boolean, tile: AbstractTileState): Point { if (isModifier) { - return tileLayer(isBackground, true, tile.modifier?.renderParameters?.zLevel ?: 0L, tile.modifier?.modId?.toLong() ?: 0L, tile.modifierHueShift) + return tileLayer(isBackground, true, tile.modifier?.value?.renderParameters?.zLevel ?: 0L, tile.modifier?.value?.modId?.toLong() ?: 0L, tile.modifierHueShift) } else { - return tileLayer(isBackground, false, tile.material.renderParameters.zLevel, tile.material.materialId.toLong(), tile.hueShift) + return tileLayer(isBackground, false, tile.material.value.renderParameters.zLevel, tile.material.value.materialId.toLong(), tile.hueShift) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 90ba78e7..124b9977 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -95,13 +95,13 @@ class TileRenderers(val client: StarboundClient) { private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean { - return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift + return otherTile?.material?.value == definition && thisTile?.hueShift == otherTile.hueShift } } private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester { override fun test(thisTile: AbstractTileState?, otherTile: AbstractTileState?): Boolean { - return otherTile?.modifier == definition + return otherTile?.modifier?.value == definition } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt index a39ae0ee..6f3ae8c6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.LongArraySet import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ReferenceArraySet +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh @@ -97,9 +98,9 @@ class ClientWorld( val tile = view.getTile(x, y) ?: continue val material = tile.material - if (!material.isMeta) { + if (!material.value.isMeta) { client.tileRenderers - .getMaterialRenderer(material.materialName) + .getMaterialRenderer(material.key) .tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground) } @@ -107,7 +108,7 @@ class ClientWorld( if (modifier != null) { client.tileRenderers - .getModifierRenderer(modifier.modName) + .getModifierRenderer(modifier.key) .tesselate(tile, view, meshes, Vector2i(x, y), isBackground = isBackground, isModifier = true) } } @@ -134,11 +135,11 @@ class ClientWorld( liquidIsDirty = false liquidMesh.clear() - val liquidTypes = ReferenceArraySet() + val liquidTypes = ReferenceArraySet>() for (x in 0 until renderRegionWidth) { for (y in 0 until renderRegionHeight) { - view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) } + view.getCell(x, y).liquid.def?.let { liquidTypes.add(it) } } } @@ -151,7 +152,7 @@ class ClientWorld( for (y in 0 until renderRegionHeight) { val state = view.getCell(x, y) - if (state?.liquid?.def === type) { + if (state.liquid.def == type) { builder.vertex(x.toFloat(), y.toFloat()) builder.vertex(x.toFloat() + 1f, y.toFloat()) builder.vertex(x.toFloat() + 1f, y.toFloat() + 1f) @@ -160,7 +161,7 @@ class ClientWorld( } } - liquidMesh.add(Mesh(builder) to type.color) + liquidMesh.add(Mesh(builder) to type.value.color) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt index 1700aa6c..3e368ce1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/ItemReference.kt @@ -9,6 +9,7 @@ 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.Registry import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -19,12 +20,16 @@ import ru.dbotthepony.kstarbound.util.ItemStack * Прототип [ItemStack] в JSON файлах */ data class ItemReference( - val item: RegistryReference, + val item: Registry.Ref, val count: Long = 1, val parameters: JsonObject = JsonObject() ) { + init { + require(item.key.isLeft) { "Can't reference item by ID" } + } + fun makeStack(): ItemStack { - return ItemStack(item.value ?: return ItemStack.EMPTY, count, parameters) + return ItemStack(item.entry ?: return ItemStack.EMPTY, count, parameters) } class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { @@ -33,7 +38,7 @@ data class ItemReference( return object : TypeAdapter() { private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = false), gson, stringInterner) private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(storesJson = false, asList = true), gson, stringInterner) - private val references = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, IItemDefinition::class.java)) as TypeAdapter> + private val references = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, IItemDefinition::class.java)) as TypeAdapter> override fun write(out: JsonWriter, value: ItemReference?) { if (value == null) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt deleted file mode 100644 index 5736a269..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt +++ /dev/null @@ -1,100 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.gson.Gson -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 it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.ObjectRegistry -import ru.dbotthepony.kstarbound.RegistryObject -import ru.dbotthepony.kstarbound.io.json.consumeNull -import java.lang.reflect.ParameterizedType -import java.util.function.Supplier - -class RegistryReferenceFactory : TypeAdapterFactory { - private val types = Reference2ObjectArrayMap, Pair<(String) -> RegistryObject?, String>>() - private var isLenient = false - - fun lenient(): RegistryReferenceFactory { - isLenient = true - return this - } - - fun add(clazz: Class, resolver: (String) -> RegistryObject?, name: String): RegistryReferenceFactory { - check(types.put(clazz, (resolver as (String) -> RegistryObject?) to name) == null) { "Already has resolver for class $clazz!" } - return this - } - - fun add(registry: ObjectRegistry): RegistryReferenceFactory { - return add(registry.clazz.java, registry::get, registry.name) - } - - inline fun add(noinline resolver: (String) -> RegistryObject?, name: String) = add(T::class.java, resolver, name) - - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType == RegistryReference::class.java) { - val ptype = type.type as? ParameterizedType ?: return null - val registryType = ptype.actualTypeArguments[0] - val resolver = types[registryType] ?: return if (isLenient) null else throw NoSuchElementException("Can't deserialize registry reference with type $registryType!") - return RegistryReferenceTypeAdapter(resolver.first, gson.getAdapter(String::class.java), resolver.second) as TypeAdapter - } - - return null - } -} - -class RegistryReferenceTypeAdapter(val resolver: (String) -> RegistryObject?, val strings: TypeAdapter, val name: String) : TypeAdapter>() { - override fun write(out: JsonWriter, value: RegistryReference?) { - if (value == null) - out.nullValue() - else - strings.write(out, value.name) - } - - override fun read(`in`: JsonReader): RegistryReference? { - if (`in`.consumeNull()) - return null - - if (`in`.peek() == JsonToken.STRING) { - return RegistryReference(strings.read(`in`)!!, resolver, name) - } - - throw JsonSyntaxException("Expecting string for registry reference, ${`in`.peek()} given, near ${`in`.path}") - } -} - -data class RegistryReference(val name: String, val resolver: (String) -> RegistryObject?, val registryName: String) : Supplier?>, () -> RegistryObject?, Lazy?> { - private val lazy = lazy { - val result = resolver.invoke(name) - - if (result == null) { - LOGGER.error("No such object '$name' in registry '$registryName'! Expect stuff being broken!") - } - - result - } - - override fun get(): RegistryObject? { - return lazy.value - } - - override val value: RegistryObject? - get() = lazy.value - - override fun isInitialized(): Boolean { - return lazy.isInitialized() - } - - override fun invoke(): RegistryObject? { - return lazy.value - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt index 8b968eeb..61f0d7cf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList @@ -25,7 +26,7 @@ data class Species( val undyColor: ImmutableList, val hairColor: ImmutableList, val genders: ImmutableList, - val statusEffects: ImmutableSet> = ImmutableSet.of(), + val statusEffects: ImmutableSet> = ImmutableSet.of(), ) { @JsonFactory data class Tooltip(val title: String, val subTitle: String, val description: String) @@ -37,8 +38,8 @@ data class Species( val characterImage: SpriteReference, val hairGroup: String? = null, val hair: ImmutableSet, - val shirt: ImmutableSet>, - val pants: ImmutableSet>, + val shirt: ImmutableSet>, + val pants: ImmutableSet>, val facialHairGroup: String? = null, val facialHair: ImmutableSet = ImmutableSet.of(), val facialMaskGroup: String? = null, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/TreasurePoolDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/TreasurePoolDefinition.kt index 2dfbd7e0..0b952276 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/TreasurePoolDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/TreasurePoolDefinition.kt @@ -12,8 +12,8 @@ import com.google.gson.internal.bind.JsonTreeReader import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.ItemReference -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.json.stream import ru.dbotthepony.kstarbound.util.Either @@ -49,7 +49,7 @@ class TreasurePoolDefinition(pieces: List) { data class Piece( val level: Double, val pool: ImmutableList = ImmutableList.of(), - val fill: ImmutableList>> = ImmutableList.of(), + val fill: ImmutableList>> = ImmutableList.of(), val poolRounds: IPoolRounds = OneRound, // TODO: что оно делает? // оно точно не запрещает ему появляться несколько раз за одну генерацию treasure pool @@ -69,7 +69,7 @@ class TreasurePoolDefinition(pieces: List) { val stack = it.makeStack() if (stack.isNotEmpty) result.add(stack) }, { - it.value?.value?.evaluate(random, actualLevel) + it.value?.evaluate(random, actualLevel) }) } @@ -82,7 +82,7 @@ class TreasurePoolDefinition(pieces: List) { val stack = it.makeStack() if (stack.isNotEmpty) result.add(stack) }, { - it.value?.value?.evaluate(random, actualLevel) + it.value?.evaluate(random, actualLevel) }) break @@ -145,7 +145,7 @@ class TreasurePoolDefinition(pieces: List) { data class PoolEntry( val weight: Double, - val treasure: Either> + val treasure: Either> ) { init { require(weight > 0.0) { "Invalid pool entry weight: $weight" } @@ -157,7 +157,7 @@ class TreasurePoolDefinition(pieces: List) { if (type.rawType === TreasurePoolDefinition::class.java) { return object : TypeAdapter() { private val itemAdapter = gson.getAdapter(ItemReference::class.java) - private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter> + private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter> private val objReader = gson.getAdapter(JsonObject::class.java) override fun write(out: JsonWriter, value: TreasurePoolDefinition?) { @@ -184,7 +184,7 @@ class TreasurePoolDefinition(pieces: List) { val things = objReader.read(`in`) val pool = ImmutableList.Builder() - val fill = ImmutableList.Builder>>() + val fill = ImmutableList.Builder>>() var poolRounds: IPoolRounds = OneRound val allowDuplication = things["allowDuplication"]?.asBoolean ?: false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt index 1c293b3a..ed181369 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/api/IItemDefinition.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.kstarbound.defs.item.api +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.IThingWithDescription -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon import ru.dbotthepony.kstarbound.defs.item.ItemRarity import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition @@ -46,7 +46,7 @@ interface IItemDefinition : IThingWithDescription { /** * При подборе предмета мгновенно заставляет игрока изучить эти рецепты крафта */ - val learnBlueprintsOnPickup: List> + val learnBlueprintsOnPickup: List> /** * Максимальное количество предмета в стопке diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt index fb9d7198..9368cc78 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/impl/ItemDefinition.kt @@ -1,12 +1,11 @@ package ru.dbotthepony.kstarbound.defs.item.impl import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.IThingWithDescription -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.ThingDescription import ru.dbotthepony.kstarbound.defs.item.IInventoryIcon import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition -import ru.dbotthepony.kstarbound.defs.item.InventoryIcon import ru.dbotthepony.kstarbound.defs.item.ItemRarity import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat @@ -22,7 +21,7 @@ data class ItemDefinition( override val category: String? = null, override val inventoryIcon: ImmutableList? = null, override val itemTags: ImmutableList = ImmutableList.of(), - override val learnBlueprintsOnPickup: ImmutableList> = ImmutableList.of(), + override val learnBlueprintsOnPickup: ImmutableList> = ImmutableList.of(), override val maxStack: Long = 9999L, override val eventCategory: String? = null, override val consumeOnPickup: Boolean = false, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt index 7429dba8..7cef7bb0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/monster/MonsterTypeDefinition.kt @@ -3,11 +3,11 @@ package ru.dbotthepony.kstarbound.defs.monster import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.IScriptable import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.player.PlayerMovementParameters -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -24,7 +24,7 @@ data class MonsterTypeDefinition( val animation: AssetReference, // [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ], // "dropPools" : [ "smallRobotTreasure" ], - val dropPools: Either>>, ImmutableList>>, + val dropPools: Either>>, ImmutableList>>, val baseParameters: BaseParameters ) : IThingWithDescription by desc { @JsonFactory diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt index 172d22b3..3ae0defa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt @@ -11,14 +11,11 @@ import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetPath -import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.defs.ItemReference import ru.dbotthepony.kstarbound.defs.JsonReference -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.StatModifier -import ru.dbotthepony.kstarbound.defs.TouchDamage -import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -44,10 +41,10 @@ data class ObjectDefinition( val hasObjectItem: Boolean = true, val scannable: Boolean = true, val retainObjectParametersInItem: Boolean = false, - val breakDropPool: RegistryReference? = null, + val breakDropPool: Registry.Ref? = null, // null - not specified, empty list - always drop nothing val breakDropOptions: ImmutableList>? = null, - val smashDropPool: RegistryReference? = null, + val smashDropPool: Registry.Ref? = null, val smashDropOptions: ImmutableList> = ImmutableList.of(), //val animation: AssetReference? = null, val animation: AssetPath? = null, @@ -97,10 +94,10 @@ data class ObjectDefinition( val hasObjectItem: Boolean = true, val scannable: Boolean = true, val retainObjectParametersInItem: Boolean = false, - val breakDropPool: RegistryReference? = null, + val breakDropPool: Registry.Ref? = null, // null - not specified, empty list - always drop nothing val breakDropOptions: ImmutableList>? = null, - val smashDropPool: RegistryReference? = null, + val smashDropPool: Registry.Ref? = null, val smashDropOptions: ImmutableList> = ImmutableList.of(), //val animation: AssetReference? = null, val animation: AssetPath? = null, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt index 601fb53d..22af88c8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt @@ -210,8 +210,8 @@ data class ObjectOrientation( val builder = ImmutableList.Builder>() when (val collisionType = obj.get("collisionType", "none").lowercase()) { - "solid" -> collisionSpaces.forEach { builder.add(it to BuiltinMetaMaterials.OBJECT_SOLID.materialName) } - "platform" -> collisionSpaces.forEach { if (it.y == boundingBox.maxs.y) builder.add(it to BuiltinMetaMaterials.OBJECT_PLATFORM.materialName) } + "solid" -> collisionSpaces.forEach { builder.add(it to BuiltinMetaMaterials.OBJECT_SOLID.key) } + "platform" -> collisionSpaces.forEach { if (it.y == boundingBox.maxs.y) builder.add(it to BuiltinMetaMaterials.OBJECT_PLATFORM.key) } "none" -> {} else -> throw JsonSyntaxException("Unknown collision type $collisionType") } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/particle/ParticleCreator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/particle/ParticleCreator.kt index be01fd50..516abf61 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/particle/ParticleCreator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/particle/ParticleCreator.kt @@ -1,13 +1,13 @@ package ru.dbotthepony.kstarbound.defs.particle -import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.util.Either @JsonFactory data class ParticleCreator( val count: Int = 1, - val particle: Either, IParticleConfig>, + val particle: Either, IParticleConfig>, //override val offset: Vector2d? = null, //override val position: Vector2d? = null, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/BlueprintLearnList.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/BlueprintLearnList.kt index 8e141d5e..c3fbb882 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/BlueprintLearnList.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/BlueprintLearnList.kt @@ -10,7 +10,7 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap -import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -18,7 +18,7 @@ class BlueprintLearnList private constructor(private val tiers: Int2ObjectArrayM constructor(tiers: Map>) : this(Int2ObjectArrayMap>().also { for ((k, v) in tiers.entries) it.put(k, ImmutableList.copyOf(v)) }) @JsonFactory - data class Entry(val item: RegistryReference) + data class Entry(val item: Registry.Ref) operator fun get(tier: Int): List { return tiers.getOrDefault(tier, ImmutableList.of()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt index d6fdd86d..769ea0c6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt @@ -2,9 +2,9 @@ package ru.dbotthepony.kstarbound.defs.player import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetPath import ru.dbotthepony.kstarbound.defs.IScriptable -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory @@ -13,8 +13,8 @@ data class DeploymentConfig( override val scripts: ImmutableList, override val scriptDelta: Int, - val starterMechSet: ImmutableMap>, - val speciesStarterMechBody: ImmutableMap>, + val starterMechSet: ImmutableMap>, + val speciesStarterMechBody: ImmutableMap>, val enemyDetectRadius: Double, val enemyDetectTypeNames: ImmutableList, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt index 9490f255..bab74ab6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt @@ -4,8 +4,8 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet import com.google.gson.JsonObject +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetReference -import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.Species import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition @@ -22,14 +22,14 @@ data class PlayerDefinition( val blueprintAlreadyKnown: SBPattern, val collectableUnlock: SBPattern, - val species: ImmutableSet>, + val species: ImmutableSet>, val nametagColor: RGBAColor, val ageItemsEvery: Int, - val defaultItems: ImmutableSet>, + val defaultItems: ImmutableSet>, val defaultBlueprints: BlueprintLearnList, - val defaultCodexes: ImmutableMap>>, + val defaultCodexes: ImmutableMap>>, val metaBoundBox: AABB, val movementParameters: PlayerMovementParameters, val zeroGMovementParameters: PlayerMovementParameters, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt index a07ca8aa..ceec4a6e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/BuiltinMetaMaterials.kt @@ -1,12 +1,14 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.Registries +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetReference import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.defs.ThingDescription object BuiltinMetaMaterials { - private fun make(id: Int, name: String, collisionType: CollisionType) = TileDefinition( + private fun make(id: Int, name: String, collisionType: CollisionType) = Registries.tiles.add(name, id, TileDefinition( materialId = id, materialName = "metamaterial:$name", descriptionData = ThingDescription.EMPTY, @@ -15,7 +17,7 @@ object BuiltinMetaMaterials { renderParameters = RenderParameters.META, isMeta = true, collisionKind = collisionType - ) + )) /** * air @@ -38,7 +40,7 @@ object BuiltinMetaMaterials { val OBJECT_SOLID = make(65500, "objectsolid", CollisionType.BLOCK) val OBJECT_PLATFORM = make(65501, "objectplatform", CollisionType.PLATFORM) - val MATERIALS: ImmutableList = ImmutableList.of( + val MATERIALS: ImmutableList> = ImmutableList.of( EMPTY, NULL, STRUCTURE, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/LiquidDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/LiquidDefinition.kt index 49c8bf6b..b883deb9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/LiquidDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/LiquidDefinition.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.kstarbound.defs.tile import com.google.common.collect.ImmutableList -import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.StatusEffectDefinition import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kvector.vector.RGBAColor @@ -14,7 +14,7 @@ data class LiquidDefinition( val tickDelta: Int = 1, val color: RGBAColor, val itemDrop: String? = null, - val statusEffects: ImmutableList> = ImmutableList.of(), + val statusEffects: ImmutableList> = ImmutableList.of(), val interactions: ImmutableList = ImmutableList.of(), val texture: String, val bottomLightMix: RGBAColor, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt index 5a55188d..28c374b6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt @@ -17,7 +17,7 @@ import jnr.ffi.Pointer import org.apache.logging.log4j.LogManager import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil -import ru.dbotthepony.kstarbound.RegistryObject +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter import ru.dbotthepony.kvector.api.IStruct2i @@ -595,8 +595,8 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter fun push(value: Boolean) = this@LuaState.push(value) fun push(value: String?) = this@LuaState.push(value) fun push(value: JsonElement?) = this@LuaState.push(value) - fun push(value: RegistryObject<*>?) = this@LuaState.push(value) - fun pushFull(value: RegistryObject<*>?) = this@LuaState.pushFull(value) + fun push(value: Registry.Entry<*>?) = this@LuaState.push(value) + fun pushFull(value: Registry.Entry<*>?) = this@LuaState.pushFull(value) } /** @@ -902,7 +902,8 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter this.setTableValue(table) } - fun setTableValue(key: String, value: String) { + fun setTableValue(key: String, value: String?) { + value ?: return val table = this.stackTop this.push(key) this.push(value) @@ -1058,20 +1059,20 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter } } - fun push(value: RegistryObject<*>?) { + fun push(value: Registry.Entry<*>?) { if (value == null) push() else - push(value.toJson()) + push(value.json) } - fun pushFull(value: RegistryObject<*>?) { + fun pushFull(value: Registry.Entry<*>?) { if (value == null) push() else { pushTable(hashSize = 2) - setTableValue("path", value.file.computeFullPath()) - setTableValue("config", value.toJson()) + setTableValue("path", value.file?.computeFullPath()) + setTableValue("config", value.json) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/player/Avatar.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/player/Avatar.kt index b53398c2..c636697d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/player/Avatar.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/player/Avatar.kt @@ -7,7 +7,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import ru.dbotthepony.kstarbound.Registries -import ru.dbotthepony.kstarbound.RegistryObject +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.player.TechDefinition import ru.dbotthepony.kstarbound.lua.LuaState @@ -48,9 +48,9 @@ class Avatar(val uniqueId: UUID) { var cursorItem = ItemStack.EMPTY - private val availableTechs = ObjectOpenHashSet>() - private val enabledTechs = ObjectOpenHashSet>() - private val equippedTechs = Object2ObjectOpenHashMap>() + private val availableTechs = ObjectOpenHashSet>() + private val enabledTechs = ObjectOpenHashSet>() + private val equippedTechs = Object2ObjectOpenHashMap>() private val knownBlueprints = ObjectOpenHashSet() // С подписью NEW diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt index 2c5b3435..f2be38b0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt @@ -51,6 +51,22 @@ class Either private constructor(val left: KOptional, val right: KOptio return orElse.invoke() } + override fun equals(other: Any?): Boolean { + return other === this || other is Either<*, *> && other.left == left && other.right == right + } + + override fun hashCode(): Int { + return left.hashCode() * 31 + right.hashCode() + } + + override fun toString(): String { + if (isLeft) { + return "Either.left[${left.value}]" + } else { + return "Either.right[${right.value}]" + } + } + companion object { @JvmStatic fun left(value: L): Either { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ItemStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ItemStack.kt index d4a4e3a4..141e9851 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ItemStack.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ItemStack.kt @@ -5,17 +5,16 @@ import com.google.gson.JsonPrimitive import com.google.gson.TypeAdapter import com.google.gson.internal.bind.TypeAdapters import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter -import ru.dbotthepony.kstarbound.RegistryObject +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition import ru.dbotthepony.kstarbound.io.json.consumeNull -class ItemStack private constructor(item: RegistryObject?, count: Long, val parameters: JsonObject, marker: Unit) { - constructor(item: RegistryObject, count: Long = 1L, parameters: JsonObject = JsonObject()) : this(item, count, parameters, Unit) +class ItemStack private constructor(item: Registry.Entry?, count: Long, val parameters: JsonObject, marker: Unit) { + constructor(item: Registry.Entry, count: Long = 1L, parameters: JsonObject = JsonObject()) : this(item, count, parameters, Unit) - var item: RegistryObject? = item + var item: Registry.Entry? = item private set var size = count diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt index 3df464e6..9c42ae6e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt @@ -67,7 +67,7 @@ class KOptional private constructor(private val _value: T, val isPresent: Boo } override fun hashCode(): Int { - return _value.hashCode() + return _value.hashCode() + 43839429 } override fun toString(): String { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt index 5228934f..31ac6433 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/LightCalculator.kt @@ -64,7 +64,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int) val parent = this@LightCalculator.parent.getCell(x, y) ?: return@lazy 0f val lightBlockStrength: Float - if (parent.foreground.material.renderParameters.lightTransparent) { + if (parent.foreground.material.value.renderParameters.lightTransparent) { lightBlockStrength = 0f } else { lightBlockStrength = 1f diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt index 6e4b050e..f6094e80 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt @@ -47,7 +47,7 @@ fun interface TileRayFilter { } val NeverFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.CONTINUE } -val NonEmptyFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.of(!state.foreground.material.collisionKind.isEmpty) } +val NonEmptyFilter = TileRayFilter { state, fraction, x, y, normal, borderX, borderY -> RayFilterResult.of(!state.foreground.material.value.collisionKind.isEmpty) } fun ICellAccess.castRay(startPos: Vector2d, direction: Vector2d, length: Double, filter: TileRayFilter) = castRay(startPos, startPos + direction * length, filter) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 5490c17a..4c7f38cb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -226,7 +226,7 @@ abstract class World, ChunkType : Chunk? abstract val level: Float abstract val pressure: Float abstract val isInfinite: Boolean diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt index e2114c44..85ba8f17 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt @@ -1,13 +1,14 @@ package ru.dbotthepony.kstarbound.world.api +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import java.io.DataInputStream sealed class AbstractTileState { - abstract val material: TileDefinition - abstract val modifier: MaterialModifier? + abstract val material: Registry.Entry + abstract val modifier: Registry.Entry? abstract val color: TileColor abstract val hueShift: Float abstract val modifierHueShift: Float diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableLiquidState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableLiquidState.kt index 0a73cfb7..d7a1f3c1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableLiquidState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableLiquidState.kt @@ -1,9 +1,10 @@ package ru.dbotthepony.kstarbound.world.api +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition data class ImmutableLiquidState( - override val def: LiquidDefinition? = null, + override val def: Registry.Entry? = null, override val level: Float = 0f, override val pressure: Float = 0f, override val isInfinite: Boolean = false, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt index d5328f77..c7b2d302 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ImmutableTileState.kt @@ -1,12 +1,13 @@ package ru.dbotthepony.kstarbound.world.api +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition data class ImmutableTileState( - override var material: TileDefinition = BuiltinMetaMaterials.NULL, - override var modifier: MaterialModifier? = null, + override var material: Registry.Entry = BuiltinMetaMaterials.NULL, + override var modifier: Registry.Entry? = null, override var color: TileColor = TileColor.DEFAULT, override var hueShift: Float = 0f, override var modifierHueShift: Float = 0f, diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableLiquidState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableLiquidState.kt index f6add68e..0158b666 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableLiquidState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableLiquidState.kt @@ -1,17 +1,18 @@ package ru.dbotthepony.kstarbound.world.api import ru.dbotthepony.kstarbound.Registries +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import java.io.DataInputStream data class MutableLiquidState( - override var def: LiquidDefinition? = null, + override var def: Registry.Entry? = null, override var level: Float = 0f, override var pressure: Float = 0f, override var isInfinite: Boolean = false, ) : AbstractLiquidState() { fun read(stream: DataInputStream): MutableLiquidState { - def = Registries.liquid[stream.readUnsignedByte()]?.value + def = Registries.liquid[stream.readUnsignedByte()] level = stream.readFloat() pressure = stream.readFloat() isInfinite = stream.readBoolean() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt index 7fdcbe3a..c65a47cc 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/MutableTileState.kt @@ -1,14 +1,15 @@ package ru.dbotthepony.kstarbound.world.api import ru.dbotthepony.kstarbound.Registries +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import java.io.DataInputStream data class MutableTileState( - override var material: TileDefinition = BuiltinMetaMaterials.NULL, - override var modifier: MaterialModifier? = null, + override var material: Registry.Entry = BuiltinMetaMaterials.NULL, + override var modifier: Registry.Entry? = null, override var color: TileColor = TileColor.DEFAULT, override var hueShift: Float = 0f, override var modifierHueShift: Float = 0f, @@ -49,10 +50,10 @@ data class MutableTileState( } fun read(stream: DataInputStream): MutableTileState { - material = Registries.tiles[stream.readUnsignedShort()]?.value ?: BuiltinMetaMaterials.EMPTY + material = Registries.tiles[stream.readUnsignedShort()] ?: BuiltinMetaMaterials.EMPTY setHueShift(stream.read()) color = TileColor.of(stream.read()) - modifier = Registries.tileModifiers[stream.readUnsignedShort()]?.value + modifier = Registries.tileModifiers[stream.readUnsignedShort()] setModHueShift(stream.read()) return this } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt index 5eb76346..4bc6abe0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt @@ -6,7 +6,7 @@ import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import ru.dbotthepony.kstarbound.Registries -import ru.dbotthepony.kstarbound.RegistryObject +import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.defs.Drawable @@ -26,9 +26,9 @@ import ru.dbotthepony.kvector.vector.Vector2i open class WorldObject( val world: World<*, *>, - val prototype: RegistryObject, + val prototype: Registry.Entry, val pos: Vector2i, -) : JsonDriven(prototype.file.computeDirectory()) { +) : JsonDriven(prototype.file?.computeDirectory() ?: "/") { constructor(world: World<*, *>, data: JsonObject) : this( world, Registries.worldObjects[data["name"]?.asString ?: throw IllegalArgumentException("Missing object name")] ?: throw IllegalArgumentException("No such object defined for '${data["name"]}'"),