Redesigned registry

This commit is contained in:
DBotThePony 2023-10-22 00:45:22 +07:00
parent 8cd84a4501
commit 949ed802ad
Signed by: DBot
GPG Key ID: DCC23B5715498507
40 changed files with 641 additions and 513 deletions

View File

@ -50,11 +50,14 @@ fun String.sintern(): String = Starbound.STRINGS.intern(this)
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)
fun <T : Any> Collection<IStarboundFile>.batch(executor: ForkJoinPool, batchSize: Int = 16, mapper: (IStarboundFile) -> KOptional<RegistryObject<T>>): Stream<RegistryObject<T>> {
/**
* guarantees even distribution of tasks while also preserving encountered order of elements
*/
fun <T> Collection<IStarboundFile>.batch(executor: ForkJoinPool, batchSize: Int = 16, mapper: (IStarboundFile) -> KOptional<T>): Stream<T> {
require(batchSize >= 1) { "Invalid batch size: $batchSize" }
if (batchSize == 1 || size <= batchSize) {
val tasks = ArrayList<ForkJoinTask<KOptional<RegistryObject<T>>>>()
val tasks = ArrayList<ForkJoinTask<KOptional<T>>>()
for (listedFile in this) {
tasks.add(executor.submit(Callable { mapper.invoke(listedFile) }))
@ -63,7 +66,7 @@ fun <T : Any> Collection<IStarboundFile>.batch(executor: ForkJoinPool, batchSize
return tasks.stream().map { it.join() }.filter { it.isPresent }.map { it.value }
}
val batches = ArrayList<ForkJoinTask<List<KOptional<RegistryObject<T>>>>>()
val batches = ArrayList<ForkJoinTask<List<KOptional<T>>>>()
var batch = ArrayList<IStarboundFile>(batchSize)
for (listedFile in this) {

View File

@ -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()

View File

@ -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 <reified T : Any> ObjectRegistry(name: String, noinline key: ((T) -> String)? = null, noinline intKey: ((T) -> Int)? = null): ObjectRegistry<T> {
return ObjectRegistry(T::class, name, key, intKey)
}
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<T : Any>(
/**
* Объект реестра
*/
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<T : Any>(val clazz: KClass<T>, val name: String, val key: ((T) -> String)? = null, val intKey: ((T) -> Int)? = null) {
val objects = Object2ObjectOpenHashMap<String, RegistryObject<T>>()
val intObjects = Int2ObjectOpenHashMap<RegistryObject<T>>()
val values get() = objects.values
operator fun get(index: String) = objects[index]
operator fun get(index: Int): RegistryObject<T>? = 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<T>(JsonTreeReader(elem), clazz.java)
add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
}
}
fun add(value: RegistryObject<T>): 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<T>, 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()
}
}

View File

@ -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<RegistryObject<RecipeDefinition>>()
private val group2recipesInternal = Object2ObjectOpenHashMap<String, LinkedList<RegistryObject<RecipeDefinition>>>()
private val group2recipesBacking = Object2ObjectOpenHashMap<String, List<RegistryObject<RecipeDefinition>>>()
private val output2recipesInternal = Object2ObjectOpenHashMap<String, LinkedList<RegistryObject<RecipeDefinition>>>()
private val output2recipesBacking = Object2ObjectOpenHashMap<String, List<RegistryObject<RecipeDefinition>>>()
private val input2recipesInternal = Object2ObjectOpenHashMap<String, LinkedList<RegistryObject<RecipeDefinition>>>()
private val input2recipesBacking = Object2ObjectOpenHashMap<String, List<RegistryObject<RecipeDefinition>>>()
data class Entry(val value: RecipeDefinition, val json: JsonElement, val file: IStarboundFile)
val recipes: List<RegistryObject<RecipeDefinition>> = Collections.unmodifiableList(recipesInternal)
val group2recipes: Map<String, List<RegistryObject<RecipeDefinition>>> = Collections.unmodifiableMap(group2recipesBacking)
val output2recipes: Map<String, List<RegistryObject<RecipeDefinition>>> = Collections.unmodifiableMap(output2recipesBacking)
val input2recipes: Map<String, List<RegistryObject<RecipeDefinition>>> = Collections.unmodifiableMap(input2recipesBacking)
private val recipesInternal = ArrayList<Entry>()
private val group2recipesInternal = Object2ObjectOpenHashMap<String, ArrayList<Entry>>()
private val group2recipesBacking = Object2ObjectOpenHashMap<String, List<Entry>>()
private val output2recipesInternal = Object2ObjectOpenHashMap<String, ArrayList<Entry>>()
private val output2recipesBacking = Object2ObjectOpenHashMap<String, List<Entry>>()
private val input2recipesInternal = Object2ObjectOpenHashMap<String, ArrayList<Entry>>()
private val input2recipesBacking = Object2ObjectOpenHashMap<String, List<Entry>>()
fun add(recipe: RegistryObject<RecipeDefinition>) {
val value = recipe.value
recipesInternal.add(recipe)
val recipes: List<Entry> = Collections.unmodifiableList(recipesInternal)
val group2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(group2recipesBacking)
val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking)
val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking)
for (group in value.groups) {
group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p ->
LinkedList<RegistryObject<RecipeDefinition>>().also {
group2recipesBacking[p as String] = Collections.unmodifiableList(it)
}
}).add(recipe)
}
private val lock = ReentrantLock()
output2recipesInternal.computeIfAbsent(value.output.item.name, Object2ObjectFunction { p ->
LinkedList<RegistryObject<RecipeDefinition>>().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<Entry>(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<RegistryObject<RecipeDefinition>>().also {
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p ->
ArrayList<Entry>(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<Entry>(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) }

View File

@ -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<JsonFunction>("json functions")
val json2Functions = ObjectRegistry<Json2Function>("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<TileDefinition>("tiles")
val tileModifiers = Registry<MaterialModifier>("tile modifiers")
val liquid = Registry<LiquidDefinition>("liquid")
val species = Registry<Species>("species")
val statusEffects = Registry<StatusEffectDefinition>("status effects")
val particles = Registry<ParticleDefinition>("particles")
val items = Registry<IItemDefinition>("items")
val questTemplates = Registry<QuestTemplate>("quest templates")
val techs = Registry<TechDefinition>("techs")
val jsonFunctions = Registry<JsonFunction>("json functions")
val json2Functions = Registry<Json2Function>("json 2functions")
val npcTypes = Registry<NpcTypeDefinition>("npc types")
val projectiles = Registry<ProjectileDefinition>("projectiles")
val tenants = Registry<TenantDefinition>("tenants")
val treasurePools = Registry<TreasurePoolDefinition>("treasure pools")
val monsterSkills = Registry<MonsterSkillDefinition>("monster skills")
val monsterTypes = Registry<MonsterTypeDefinition>("monster types")
val worldObjects = Registry<ObjectDefinition>("objects")
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, Int?> {
return { mapper.invoke(it) to null }
}
private fun <T> key(mapper: (T) -> String, mapperInt: (T) -> Int): (T) -> Pair<String, Int> {
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 <reified T : Any> loadStage(
log: ILoadingLog,
executor: ForkJoinPool,
registry: ObjectRegistry<T>,
registry: Registry<T>,
files: List<IStarboundFile>,
noinline keyProvider: (T) -> Pair<String, Int?>,
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<JsonFunction>(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<Json2Function>(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<TreasurePoolDefinition>(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)
}

View File

@ -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 <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
return object : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
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<Registry.Entry<S>>() {
override fun write(out: JsonWriter, value: Registry.Entry<S>?) {
if (value != null) {
out.value(value.key)
} else {
out.nullValue()
}
}
override fun read(`in`: JsonReader): Registry.Entry<S>? {
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<Registry.Ref<S>>() {
override fun write(out: JsonWriter, value: Registry.Ref<S>?) {
if (value != null) {
value.key.map(out::value, out::value)
} else {
out.nullValue()
}
}
override fun read(`in`: JsonReader): Registry.Ref<S>? {
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<T>?
}
}
}
class Registry<T : Any>(val name: String) {
private val keysInternal = Object2ObjectOpenHashMap<String, Impl>()
private val idsInternal = Int2ObjectOpenHashMap<Impl>()
private val keyRefs = Object2ObjectOpenHashMap<String, RefImpl>()
private val idRefs = Int2ObjectOpenHashMap<RefImpl>()
private val lock = ReentrantLock()
val keys: Object2ObjectMap<String, out Entry<T>> = Object2ObjectMaps.unmodifiable(keysInternal)
val ids: Int2ObjectMap<out Entry<T>> = Int2ObjectMaps.unmodifiable(idsInternal)
sealed class Ref<T : Any> : Supplier<Entry<T>?> {
abstract val key: Either<String, Int>
abstract val entry: Entry<T>?
abstract val registry: Registry<T>
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<T>? {
return entry
}
}
sealed class Entry<T : Any> : Supplier<T> {
abstract val key: String
abstract val id: Int?
abstract val value: T
abstract val json: JsonElement
abstract val file: IStarboundFile?
abstract val registry: Registry<T>
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<T>() {
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<T>
get() = this@Registry
}
private inner class RefImpl(override val key: Either<String, Int>) : Ref<T>() {
override var entry: Entry<T>? = 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<T>
get() = this@Registry
}
operator fun get(index: String): Entry<T>? = lock.withLock { keysInternal[index] }
operator fun get(index: Int): Entry<T>? = lock.withLock { idsInternal[index] }
fun ref(index: String): Ref<T> = lock.withLock {
keyRefs.computeIfAbsent(index, Object2ObjectFunction {
val ref = RefImpl(Either.left(it as String))
ref.entry = keysInternal[it]
ref
})
}
fun ref(index: Int): Ref<T> = 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<T> {
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 ?: "<code>"})")
}
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<T> {
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 ?: "<code>"})")
}
if (id in idsInternal) {
LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from $file; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
}
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<T> {
lock.withLock {
if (key in keysInternal) {
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
}
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<T> {
lock.withLock {
if (key in keysInternal) {
LOGGER.warn("Overwriting Registry entry at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
}
if (id in idsInternal) {
LOGGER.warn("Overwriting Registry entry with ID '$id' (new def originate from <code>; old def originate from ${idsInternal[id]?.file ?: "<code>"})")
}
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()
}
}

View File

@ -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")

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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<LiquidDefinition>()
val liquidTypes = ReferenceArraySet<Registry.Entry<LiquidDefinition>>()
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)
}
}

View File

@ -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<IItemDefinition>,
val item: Registry.Ref<IItemDefinition>,
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<String> = Interner { it }) : TypeAdapterFactory {
@ -33,7 +38,7 @@ data class ItemReference(
return object : TypeAdapter<ItemReference>() {
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<RegistryReference<IItemDefinition>>
private val references = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, IItemDefinition::class.java)) as TypeAdapter<Registry.Ref<IItemDefinition>>
override fun write(out: JsonWriter, value: ItemReference?) {
if (value == null)

View File

@ -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<Class<*>, Pair<(String) -> RegistryObject<Nothing>?, String>>()
private var isLenient = false
fun lenient(): RegistryReferenceFactory {
isLenient = true
return this
}
fun <T : Any> add(clazz: Class<T>, resolver: (String) -> RegistryObject<T>?, name: String): RegistryReferenceFactory {
check(types.put(clazz, (resolver as (String) -> RegistryObject<Nothing>?) to name) == null) { "Already has resolver for class $clazz!" }
return this
}
fun <T : Any> add(registry: ObjectRegistry<T>): RegistryReferenceFactory {
return add(registry.clazz.java, registry::get, registry.name)
}
inline fun <reified T: Any> add(noinline resolver: (String) -> RegistryObject<T>?, name: String) = add(T::class.java, resolver, name)
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
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<T>
}
return null
}
}
class RegistryReferenceTypeAdapter<T : Any>(val resolver: (String) -> RegistryObject<T>?, val strings: TypeAdapter<String>, val name: String) : TypeAdapter<RegistryReference<T>>() {
override fun write(out: JsonWriter, value: RegistryReference<T>?) {
if (value == null)
out.nullValue()
else
strings.write(out, value.name)
}
override fun read(`in`: JsonReader): RegistryReference<T>? {
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<T : Any>(val name: String, val resolver: (String) -> RegistryObject<T>?, val registryName: String) : Supplier<RegistryObject<T>?>, () -> RegistryObject<T>?, Lazy<RegistryObject<T>?> {
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<T>? {
return lazy.value
}
override val value: RegistryObject<T>?
get() = lazy.value
override fun isInitialized(): Boolean {
return lazy.isInitialized()
}
override fun invoke(): RegistryObject<T>? {
return lazy.value
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -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<ColorReplacements>,
val hairColor: ImmutableList<ColorReplacements>,
val genders: ImmutableList<Gender>,
val statusEffects: ImmutableSet<RegistryReference<StatusEffectDefinition>> = ImmutableSet.of(),
val statusEffects: ImmutableSet<Registry.Ref<StatusEffectDefinition>> = 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<String>,
val shirt: ImmutableSet<RegistryReference<IItemDefinition>>,
val pants: ImmutableSet<RegistryReference<IItemDefinition>>,
val shirt: ImmutableSet<Registry.Ref<IItemDefinition>>,
val pants: ImmutableSet<Registry.Ref<IItemDefinition>>,
val facialHairGroup: String? = null,
val facialHair: ImmutableSet<String> = ImmutableSet.of(),
val facialMaskGroup: String? = null,

View File

@ -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<Piece>) {
data class Piece(
val level: Double,
val pool: ImmutableList<PoolEntry> = ImmutableList.of(),
val fill: ImmutableList<Either<ItemReference, RegistryReference<TreasurePoolDefinition>>> = ImmutableList.of(),
val fill: ImmutableList<Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
val poolRounds: IPoolRounds = OneRound,
// TODO: что оно делает?
// оно точно не запрещает ему появляться несколько раз за одну генерацию treasure pool
@ -69,7 +69,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
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<Piece>) {
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<Piece>) {
data class PoolEntry(
val weight: Double,
val treasure: Either<ItemReference, RegistryReference<TreasurePoolDefinition>>
val treasure: Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>
) {
init {
require(weight > 0.0) { "Invalid pool entry weight: $weight" }
@ -157,7 +157,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
if (type.rawType === TreasurePoolDefinition::class.java) {
return object : TypeAdapter<TreasurePoolDefinition>() {
private val itemAdapter = gson.getAdapter(ItemReference::class.java)
private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(RegistryReference::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter<RegistryReference<TreasurePoolDefinition>>
private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter<Registry.Ref<TreasurePoolDefinition>>
private val objReader = gson.getAdapter(JsonObject::class.java)
override fun write(out: JsonWriter, value: TreasurePoolDefinition?) {
@ -184,7 +184,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
val things = objReader.read(`in`)
val pool = ImmutableList.Builder<PoolEntry>()
val fill = ImmutableList.Builder<Either<ItemReference, RegistryReference<TreasurePoolDefinition>>>()
val fill = ImmutableList.Builder<Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>>()
var poolRounds: IPoolRounds = OneRound
val allowDuplication = things["allowDuplication"]?.asBoolean ?: false

View File

@ -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<RegistryReference<IItemDefinition>>
val learnBlueprintsOnPickup: List<Registry.Ref<IItemDefinition>>
/**
* Максимальное количество предмета в стопке

View File

@ -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<IInventoryIcon>? = null,
override val itemTags: ImmutableList<String> = ImmutableList.of(),
override val learnBlueprintsOnPickup: ImmutableList<RegistryReference<IItemDefinition>> = ImmutableList.of(),
override val learnBlueprintsOnPickup: ImmutableList<Registry.Ref<IItemDefinition>> = ImmutableList.of(),
override val maxStack: Long = 9999L,
override val eventCategory: String? = null,
override val consumeOnPickup: Boolean = false,

View File

@ -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<AnimationDefinition>,
// [ { "default" : "poptopTreasure", "bow" : "poptopHunting" } ],
// "dropPools" : [ "smallRobotTreasure" ],
val dropPools: Either<ImmutableList<ImmutableMap<String, RegistryReference<TreasurePoolDefinition>>>, ImmutableList<RegistryReference<TreasurePoolDefinition>>>,
val dropPools: Either<ImmutableList<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>>, ImmutableList<Registry.Ref<TreasurePoolDefinition>>>,
val baseParameters: BaseParameters
) : IThingWithDescription by desc {
@JsonFactory

View File

@ -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<TreasurePoolDefinition>? = null,
val breakDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
// null - not specified, empty list - always drop nothing
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
val smashDropPool: RegistryReference<TreasurePoolDefinition>? = null,
val smashDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
//val animation: AssetReference<AnimationDefinition>? = 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<TreasurePoolDefinition>? = null,
val breakDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
// null - not specified, empty list - always drop nothing
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
val smashDropPool: RegistryReference<TreasurePoolDefinition>? = null,
val smashDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
//val animation: AssetReference<AnimationDefinition>? = null,
val animation: AssetPath? = null,

View File

@ -210,8 +210,8 @@ data class ObjectOrientation(
val builder = ImmutableList.Builder<Pair<Vector2i, String>>()
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")
}

View File

@ -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<RegistryReference<ParticleDefinition>, IParticleConfig>,
val particle: Either<Registry.Ref<ParticleDefinition>, IParticleConfig>,
//override val offset: Vector2d? = null,
//override val position: Vector2d? = null,

View File

@ -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<Int, List<Entry>>) : this(Int2ObjectArrayMap<ImmutableList<Entry>>().also { for ((k, v) in tiers.entries) it.put(k, ImmutableList.copyOf(v)) })
@JsonFactory
data class Entry(val item: RegistryReference<IItemDefinition>)
data class Entry(val item: Registry.Ref<IItemDefinition>)
operator fun get(tier: Int): List<Entry> {
return tiers.getOrDefault(tier, ImmutableList.of())

View File

@ -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<AssetPath>,
override val scriptDelta: Int,
val starterMechSet: ImmutableMap<String, RegistryReference<IItemDefinition>>,
val speciesStarterMechBody: ImmutableMap<String, RegistryReference<IItemDefinition>>,
val starterMechSet: ImmutableMap<String, Registry.Ref<IItemDefinition>>,
val speciesStarterMechBody: ImmutableMap<String, Registry.Ref<IItemDefinition>>,
val enemyDetectRadius: Double,
val enemyDetectTypeNames: ImmutableList<String>,

View File

@ -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<RegistryReference<Species>>,
val species: ImmutableSet<Registry.Ref<Species>>,
val nametagColor: RGBAColor,
val ageItemsEvery: Int,
val defaultItems: ImmutableSet<RegistryReference<IItemDefinition>>,
val defaultItems: ImmutableSet<Registry.Ref<IItemDefinition>>,
val defaultBlueprints: BlueprintLearnList,
val defaultCodexes: ImmutableMap<String, ImmutableList<RegistryReference<IItemDefinition>>>,
val defaultCodexes: ImmutableMap<String, ImmutableList<Registry.Ref<IItemDefinition>>>,
val metaBoundBox: AABB,
val movementParameters: PlayerMovementParameters,
val zeroGMovementParameters: PlayerMovementParameters,

View File

@ -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<TileDefinition> = ImmutableList.of(
val MATERIALS: ImmutableList<Registry.Entry<TileDefinition>> = ImmutableList.of(
EMPTY,
NULL,
STRUCTURE,

View File

@ -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<RegistryReference<StatusEffectDefinition>> = ImmutableList.of(),
val statusEffects: ImmutableList<Registry.Ref<StatusEffectDefinition>> = ImmutableList.of(),
val interactions: ImmutableList<Interaction> = ImmutableList.of(),
val texture: String,
val bottomLightMix: RGBAColor,

View File

@ -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)
}
}

View File

@ -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<RegistryObject<TechDefinition>>()
private val enabledTechs = ObjectOpenHashSet<RegistryObject<TechDefinition>>()
private val equippedTechs = Object2ObjectOpenHashMap<String, RegistryObject<TechDefinition>>()
private val availableTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
private val enabledTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
private val equippedTechs = Object2ObjectOpenHashMap<String, Registry.Entry<TechDefinition>>()
private val knownBlueprints = ObjectOpenHashSet<ItemStack>()
// С подписью NEW

View File

@ -51,6 +51,22 @@ class Either<L, R> private constructor(val left: KOptional<L>, 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 <L, R> left(value: L): Either<L, R> {

View File

@ -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<IItemDefinition>?, count: Long, val parameters: JsonObject, marker: Unit) {
constructor(item: RegistryObject<IItemDefinition>, count: Long = 1L, parameters: JsonObject = JsonObject()) : this(item, count, parameters, Unit)
class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, count: Long, val parameters: JsonObject, marker: Unit) {
constructor(item: Registry.Entry<IItemDefinition>, count: Long = 1L, parameters: JsonObject = JsonObject()) : this(item, count, parameters, Unit)
var item: RegistryObject<IItemDefinition>? = item
var item: Registry.Entry<IItemDefinition>? = item
private set
var size = count

View File

@ -67,7 +67,7 @@ class KOptional<T> private constructor(private val _value: T, val isPresent: Boo
}
override fun hashCode(): Int {
return _value.hashCode()
return _value.hashCode() + 43839429
}
override fun toString(): String {

View File

@ -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

View File

@ -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)

View File

@ -226,7 +226,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
result.add(CollisionPoly(
BLOCK_POLY + Vector2d(x.toDouble(), y.toDouble()),
cell.foreground.material.collisionKind,
cell.foreground.material.value.collisionKind,
//velocity = Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)
))
}

View File

@ -1,12 +1,11 @@
package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.util.HashTableInterner
import java.io.DataInputStream
sealed class AbstractLiquidState {
abstract val def: LiquidDefinition?
abstract val def: Registry.Entry<LiquidDefinition>?
abstract val level: Float
abstract val pressure: Float
abstract val isInfinite: Boolean

View File

@ -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<TileDefinition>
abstract val modifier: Registry.Entry<MaterialModifier>?
abstract val color: TileColor
abstract val hueShift: Float
abstract val modifierHueShift: Float

View File

@ -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<LiquidDefinition>? = null,
override val level: Float = 0f,
override val pressure: Float = 0f,
override val isInfinite: Boolean = false,

View File

@ -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<TileDefinition> = BuiltinMetaMaterials.NULL,
override var modifier: Registry.Entry<MaterialModifier>? = null,
override var color: TileColor = TileColor.DEFAULT,
override var hueShift: Float = 0f,
override var modifierHueShift: Float = 0f,

View File

@ -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<LiquidDefinition>? = 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()

View File

@ -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<TileDefinition> = BuiltinMetaMaterials.NULL,
override var modifier: Registry.Entry<MaterialModifier>? = 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
}

View File

@ -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<ObjectDefinition>,
val prototype: Registry.Entry<ObjectDefinition>,
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"]}'"),