diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Globals.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Globals.kt index 4810f546..69b2819d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Globals.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Globals.kt @@ -4,6 +4,8 @@ import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.await import kotlinx.coroutines.launch @@ -147,7 +149,7 @@ object Globals { LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!") } else { try { - AssetPathStack("/") { + AssetPathStack(path) { accept.set(adapter.value.fromJsonTree(file)) } } catch (err: Throwable) { @@ -161,6 +163,38 @@ object Globals { return Starbound.GLOBAL_SCOPE.launch { load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) }) }.asCompletableFuture() } + private inline fun mapAdapter(filename: String): Lazy>> { + val parent by lazy { Starbound.gson.getAdapter(T::class.java) } + + return lazy(LazyThreadSafetyMode.NONE) { + object : TypeAdapter>() { + override fun write(out: JsonWriter, value: ImmutableMap) { + TODO("Not yet implemented") + } + + override fun read(`in`: JsonReader): ImmutableMap { + `in`.beginObject() + + val builder = ImmutableMap.Builder() + + while (`in`.hasNext()) { + val name = `in`.nextName() + val element = Starbound.ELEMENTS_ADAPTER.read(`in`) + + try { + builder.put(name, parent.fromJsonTree(element)) + } catch (err: Throwable) { + LOGGER.error("Exception reading element at $name from $filename; it will not be loaded", err) + } + } + + `in`.endObject() + return builder.build() + } + } + } + } + fun load(): List> { val tasks = ArrayList>() @@ -186,10 +220,10 @@ object Globals { tasks.add(load("/plants/bushDamage.config", ::bushDamage)) tasks.add(load("/tiles/defaultDamage.config", ::tileDamage)) - tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture()) - tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture()) - tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture()) - tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture()) + tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture()) + tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture()) + tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture()) + tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture()) tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt index 0fec1891..70835ba8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt @@ -123,7 +123,7 @@ object Registries { try { val elem = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) - AssetPathStack(listedFile.computeDirectory()) { + AssetPathStack(listedFile.computeFullPath()) { val read = adapter.fromJsonTree(elem) val keys = keyProvider(read) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt index e7324c49..d3ee611a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt @@ -98,9 +98,13 @@ class AssetReference { } val value = json.thenApplyAsync({ j -> - AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) { - adapter.fromJsonTree(j) - } + if (j == null) { + null + } else { + AssetPathStack(fullPath.substringBefore(':').substringBefore('?')) { + adapter.fromJsonTree(j) + } + } }, Starbound.EXECUTOR) value.exceptionally { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PhysicsForceRegion.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PhysicsForceRegion.kt index b13d98f5..0055c04f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PhysicsForceRegion.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PhysicsForceRegion.kt @@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.io.writeStruct2d import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.json.builder.JsonFlat import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.network.syncher.legacyCodec @@ -38,6 +39,7 @@ sealed class PhysicsForceRegion { val xTargetVelocity: Double?, val yTargetVelocity: Double?, val controlForce: Double, + @JsonFlat override val filter: PhysicsCategoryFilter, override val enabled: Boolean = true ) : PhysicsForceRegion() { @@ -69,6 +71,7 @@ sealed class PhysicsForceRegion { val innerRadius: Double, val targetRadialVelocity: Double, val controlForce: Double, + @JsonFlat override val filter: PhysicsCategoryFilter, override val enabled: Boolean = true ) : PhysicsForceRegion() { @@ -101,6 +104,7 @@ sealed class PhysicsForceRegion { val gradient: Line2d, val baseTargetVelocity: Double, val baseControlForce: Double, + @JsonFlat override val filter: PhysicsCategoryFilter, override val enabled: Boolean = true ) : PhysicsForceRegion() { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt index e9f99176..0ba74e15 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonDefinition.kt @@ -72,10 +72,10 @@ data class DungeonDefinition( tiles.spewWarnings(name) } - private val directory = AssetPathStack.last() + private val file = AssetPathStack.lastFile() val actualParts: ImmutableList by lazy { - AssetPathStack(directory) { + AssetPathStack(file) { val build = parts.stream().map { Starbound.gson.fromJson(it, DungeonPart::class.java) }.collect(ImmutableList.toImmutableList()) build.forEach { it.bind(this) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt index ac6a4fab..5c9f9ddd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt @@ -39,6 +39,7 @@ import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.getObject import ru.dbotthepony.kstarbound.json.JsonPatch import ru.dbotthepony.kstarbound.math.vector.Vector2d +import ru.dbotthepony.kstarbound.util.AssetPathStack import java.io.BufferedInputStream import java.io.FileNotFoundException import java.lang.ref.Reference @@ -378,11 +379,27 @@ class Image private constructor( val file = Starbound.locate(it) if (!file.exists) { - throw FileNotFoundException("No such file $it") + val asset = AssetPathStack.lastFile() + + if (asset == "/") { + logger.error("No such file $it") + } else { + logger.error("No such file $it (referenced in $asset)") + } + + return@computeIfAbsent Optional.empty() } if (!file.isFile) { - throw FileNotFoundException("File $it is a directory") + val asset = AssetPathStack.lastFile() + + if (asset == "/") { + logger.error("$it is a directory") + } else { + logger.error("$it is a directory (referenced in $asset)") + } + + return@computeIfAbsent Optional.empty() } val getWidth = intArrayOf(0) @@ -400,7 +417,7 @@ class Image private constructor( MemoryUtil.memPutByte(buf + i, readBuf[i]) } - return@STBIReadCallbackI read + return@STBIReadCallbackI read.coerceAtLeast(0) }) val skipCallback = STBISkipCallback.create { _, n -> stream.skip(n.toLong()) } @@ -425,10 +442,18 @@ class Image private constructor( eofCallback.free() callback.free() - if (!status) - throw IllegalArgumentException("File $file is not an image or it is corrupted") + if (!status) { + val asset = AssetPathStack.lastFile() - Optional.of(Image(file, it, getWidth[0], getHeight[0], getConfig(it))) + if (asset == "/") { + logger.error("$file is not an image or it is corrupted") + } else { + logger.error("$file is not an image or it is corrupted (referenced in $asset)") + } + + Optional.empty() + } else + Optional.of(Image(file, it, getWidth[0], getHeight[0], getConfig(it))) } catch (err: Exception) { logger.error("Failed to load image at path $it", err) Optional.empty() 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 670e6d06..ae66dc38 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectDefinition.kt @@ -9,27 +9,23 @@ import com.google.gson.JsonElement import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonPrimitive -import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import kotlinx.coroutines.async import kotlinx.coroutines.future.asCompletableFuture -import kotlinx.coroutines.launch import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.defs.AssetPath -import ru.dbotthepony.kstarbound.defs.JsonReference import ru.dbotthepony.kstarbound.defs.actor.StatModifier import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kstarbound.json.listAdapter -import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.util.PeriodicFunction import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.get @@ -219,7 +215,7 @@ data class ObjectDefinition( val orientations = ImmutableList.Builder>() - val path = AssetPathStack.last() + val path = AssetPathStack.lastFile() for (v in ObjectOrientation.preprocess(read.getArray("orientations"))) { val future = Starbound.GLOBAL_SCOPE.async { this@Adapter.orientations.read(v as JsonObject, path) }.asCompletableFuture() @@ -275,7 +271,7 @@ data class ObjectDefinition( soundEffectRangeMultiplier = basic.soundEffectRangeMultiplier, price = basic.price, statusEffects = basic.statusEffects, - touchDamage = Starbound.loadJsonAsset(basic.touchDamage, AssetPathStack.last()), + touchDamage = Starbound.loadJsonAsset(basic.touchDamage, AssetPathStack.lastFolder()), minimumLiquidLevel = basic.minimumLiquidLevel, maximumLiquidLevel = basic.maximumLiquidLevel, liquidCheckInterval = basic.liquidCheckInterval, 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 71b57298..d8b24ecb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/ObjectOrientation.kt @@ -9,10 +9,7 @@ import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter -import com.google.gson.annotations.JsonAdapter import com.google.gson.reflect.TypeToken -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonWriter import kotlinx.coroutines.future.await import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.clear @@ -24,9 +21,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.render.RenderLayer import ru.dbotthepony.kstarbound.defs.Drawable -import ru.dbotthepony.kstarbound.defs.JsonReference import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials -import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.json.setAdapter import ru.dbotthepony.kommons.gson.contains @@ -37,11 +32,9 @@ import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile -import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.World -import java.util.concurrent.CompletableFuture import kotlin.math.PI data class ObjectOrientation( @@ -154,12 +147,12 @@ data class ObjectOrientation( private val spaces = gson.setAdapter() private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter>> - suspend fun read(obj: JsonObject, folder: String): ObjectOrientation { + suspend fun read(obj: JsonObject, file: String): ObjectOrientation { val drawables = ArrayList() val flipImages = obj.get("flipImages", false) val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object")) - AssetPathStack(folder) { + AssetPathStack(file) { if ("imageLayers" in obj) { for (value in obj["imageLayers"].asJsonArray) { var result = this.drawables.fromJsonTree(value) @@ -273,7 +266,7 @@ data class ObjectOrientation( val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) } - val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last()).await() + val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.lastFolder()).await() val emitters = ArrayList() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt index 385b267d..24ad1f54 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.kstarbound.util import ru.dbotthepony.kstarbound.Starbound -import java.io.File object AssetPathStack { private val _stack = object : ThreadLocal>() { @@ -10,20 +9,35 @@ object AssetPathStack { } } - private val stack: ArrayDeque get() = _stack.get() + private val _fileStack = object : ThreadLocal>() { + override fun initialValue(): ArrayDeque { + return ArrayDeque() + } + } + + private val folderStack: ArrayDeque get() = _stack.get() + private val fileStack: ArrayDeque get() = _fileStack.get() fun push(value: String) { - stack.addLast(value) + if (value.last() == '/') { + // already folder, we don't have filename + folderStack.addLast(value) + fileStack.addLast(value) + } else { + folderStack.addLast(value.substringBeforeLast('/') + "/") + fileStack.addLast(value) + } } - fun pushFilename(value: String) { - push(value.substringBefore(':').substringBeforeLast('/')) + fun lastFolder() = folderStack.lastOrNull() ?: "/" + fun lastFile() = fileStack.lastOrNull() ?: "/" + + fun pop() { + folderStack.removeLast() + fileStack.removeLast() } - fun last() = stack.lastOrNull() ?: "/" - fun pop() = stack.removeLast() - - inline fun block(path: String, block: (String) -> T): T { + inline operator fun invoke(path: String, block: (String) -> T): T { try { push(path) return block.invoke(path) @@ -32,23 +46,12 @@ object AssetPathStack { } } - inline operator fun invoke(path: String, block: (String) -> T) = block(path, block) - - private fun remap(a: String, b: String): String { - if (b.isEmpty()) - return a - if (b[0] == '/') - return b - - return Starbound.STRINGS.intern("$a/$b") - } - fun remap(path: String): String { - return remap(last(), path) + return relativeTo(lastFolder(), path) } fun remapSafe(path: String): String { - return remap(last(), path) + return relativeTo(lastFolder(), path) } fun relativeTo(base: String, path: String): String { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt index fc7d4b2d..5619ce69 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt @@ -8,7 +8,7 @@ import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.ObjectArrayList -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import org.apache.logging.log4j.LogManager import org.lwjgl.opengl.GL11.GL_LINES import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.math.RGBAColor @@ -30,6 +30,7 @@ import ru.dbotthepony.kstarbound.json.listAdapter import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.network.syncher.legacyCodec import ru.dbotthepony.kstarbound.network.syncher.nativeCodec +import ru.dbotthepony.kstarbound.util.AssetPathStack import java.io.DataInputStream import java.io.DataOutputStream import java.util.LinkedList @@ -417,6 +418,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: I } companion object : TypeAdapterFactory { + private val LOGGER = LogManager.getLogger() val CODEC = nativeCodec(::read, Poly::write) val LEGACY_CODEC = legacyCodec(::read, Poly::write) @@ -447,8 +449,13 @@ class Poly private constructor(val edges: ImmutableList, val vertices: I else { val list = list.read(`in`) - if (list.isEmpty()) + if (list.isEmpty()) { + // LOGGER.warn("Accepting empty poly from asset ${AssetPathStack.lastFile()} to match original engine behavior") return EMPTY + } else if (list.size == 1) { + LOGGER.warn("Accepting degenerate poly with 1 points from asset ${AssetPathStack.lastFile()} to match original engine behavior") + return Poly(listOf(list[0], list[0], list[0], list[0])) + } return Poly(list) }