KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemRegistry.kt

160 lines
4.8 KiB
Kotlin

package ru.dbotthepony.kstarbound.item
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.IStarboundFile
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.item.ItemType
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.JsonPatch
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Future
object ItemRegistry {
class Entry(
val name: String,
val type: ItemType,
val json: JsonObject,
val isEmpty: Boolean,
val friendlyName: String,
val itemTags: ImmutableSet<String>,
val itemAgingScripts: ImmutableSet<AssetPath>,
val file: IStarboundFile?
) {
val isNotEmpty: Boolean
get() = !isEmpty
val directory = file?.computeDirectory() ?: "/"
}
private val LOGGER = LogManager.getLogger()
private val entries = HashMap<String, Entry>()
private val tasks = ConcurrentLinkedQueue<Runnable>()
val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null)
init {
entries[""] = AIR
}
operator fun get(name: String): Entry {
return entries[name] ?: AIR
}
@JsonFactory
data class ReadData(
val itemName: String,
val shortdescription: String = "...",
val itemTags: ImmutableSet<String> = ImmutableSet.of(),
val itemAgingScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
)
@JsonFactory
data class ReadDataObj(
val shortdescription: String = "...",
val itemTags: ImmutableSet<String> = ImmutableSet.of(),
val itemAgingScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
)
private fun addObjectItem(obj: Registry.Entry<ObjectDefinition>) {
if (obj.key in entries) {
LOGGER.error("Refusing to overwrite item ${obj.key} with generated item for object (old def originate from ${entries[obj.key]!!.file}; new from ${obj.file})")
return
}
val config = obj.jsonObject.deepCopy()
if ("inventoryIcon" !in config) {
config["inventoryIcon"] = Globals.itemParameters.missingIcon.fullPath
LOGGER.warn("inventoryIcon is missing for object item ${obj.key}, using default ${Globals.itemParameters.missingIcon.fullPath}")
}
config["itemName"] = obj.key
if ("tooltipKind" !in config)
config["tooltipKind"] = "object"
// this is required for Lua call to "get item config"
config["printable"] = obj.value.printable
// Don't inherit object scripts. this is kind of a crappy solution to prevent
// ObjectItems (which are firable and therefore scripted) from trying to
// execute scripts intended for objects
config.remove("scripts")
val readData = Starbound.gson.fromJson(config, ReadDataObj::class.java)
entries[obj.key] = Entry(
name = obj.key,
type = ItemType.OBJECT,
json = config,
isEmpty = false,
friendlyName = readData.shortdescription,
itemTags = readData.itemTags,
itemAgingScripts = readData.itemAgingScripts,
file = obj.file,
)
}
fun finishLoad() {
tasks.forEach { it.run() }
tasks.clear()
for (obj in Registries.worldObjects.keys.values) {
if (obj.value.hasObjectItem) {
addObjectItem(obj)
}
}
}
fun load(fileTree: Map<String, Collection<IStarboundFile>>, patches: Map<String, Collection<IStarboundFile>>): Collection<Future<*>> {
val futures = ArrayList<Future<*>>()
val data by lazy { Starbound.gson.getAdapter(ReadData::class.java) }
for (type in ItemType.entries) {
val files = fileTree[type.extension ?: continue] ?: continue
for (file in files) {
futures.add(Starbound.EXECUTOR.submit {
try {
val read = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(file.jsonReader()), patches[file.computeFullPath()]) as JsonObject
val readData = data.fromJsonTree(read)
tasks.add {
if (readData.itemName in entries) {
LOGGER.warn("Overwriting item definition at ${readData.itemName} (old def originate from ${entries[readData.itemName]!!.file}; new from $file)")
}
entries[readData.itemName] = Entry(
name = readData.itemName,
type = type,
json = read,
isEmpty = false,
friendlyName = readData.shortdescription,
itemTags = readData.itemTags,
itemAgingScripts = readData.itemAgingScripts,
file = file,
)
}
} catch (err: Throwable) {
LOGGER.error("Reading item definition $file", err)
}
})
}
}
return futures
}
}