diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Constants.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Constants.kt new file mode 100644 index 00000000..edf0f9fc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Constants.kt @@ -0,0 +1,7 @@ +package ru.dbotthepony.kstarbound + +const val METRES_IN_STARBOUND_UNIT = 0.5 +const val METRES_IN_STARBOUND_UNITf = 0.5f + +const val PIXELS_IN_STARBOUND_UNIT = 8.0 +const val PIXELS_IN_STARBOUND_UNITf = 8.0f diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index e0165ecd..6f2a3f76 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -1,13 +1,10 @@ package ru.dbotthepony.kstarbound -import com.google.common.collect.ImmutableList -import com.google.gson.GsonBuilder import org.apache.logging.log4j.LogManager import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import ru.dbotthepony.kstarbound.client.StarboundClient -import ru.dbotthepony.kstarbound.io.* -import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory +import ru.dbotthepony.kstarbound.io.BTreeDB import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity @@ -16,11 +13,11 @@ import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File import java.util.zip.Inflater -import kotlin.random.Random private val LOGGER = LogManager.getLogger() fun main() { + val starbound = Starbound() LOGGER.info("Running LWJGL ${Version.getVersion()}") //Thread.sleep(6_000L) @@ -28,10 +25,10 @@ fun main() { val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world")) //val db = BTreeDB(File("world.world")) - val client = StarboundClient() + val client = StarboundClient(starbound) //Starbound.addFilePath(File("./unpacked_assets/")) - Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) + starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) /*for (folder in File("J:\\Steam\\steamapps\\workshop\\content\\211820").list()!!) { val f = File("J:\\Steam\\steamapps\\workshop\\content\\211820\\$folder\\contents.pak") @@ -43,17 +40,17 @@ fun main() { //Starbound.addPakPath(File("packed.pak")) - Starbound.initializeGame { finished, replaceStatus, status -> + starbound.initializeGame { finished, replaceStatus, status -> client.putDebugLog(status, replaceStatus) } client.onTermination { - Starbound.terminateLoading = true + starbound.terminateLoading = true } val ent = PlayerEntity(client.world!!) - Starbound.onInitialize { + starbound.onInitialize { var find = 0L var set = 0L var parse = 0L @@ -86,7 +83,7 @@ fun main() { for (y in 0 .. 31) { for (x in 0 .. 31) { val materialID = reader.readUnsignedShort() - val getMat = Starbound.TILE_BY_ID[materialID] + val getMat = starbound.tilesByID[materialID] if (getMat != null) { chunk.foreground[x, y].material = getMat @@ -99,7 +96,7 @@ fun main() { val colorVariant = reader.readUnsignedByte() val modifier = reader.readUnsignedShort() - val getModifier = Starbound.TILE_MODIFIER_BY_ID[modifier] + val getModifier = starbound.tileModifiersByID[modifier] chunk.foreground[x, y].color = colorVariant chunk.foreground[x, y].setHueShift(colorShift) @@ -113,7 +110,7 @@ fun main() { chunk.foreground[x, y].setModifierHueShift(modifierHueShift) val materialID2 = reader.readUnsignedShort() - val getMat2 = Starbound.TILE_BY_ID[materialID2] + val getMat2 = starbound.tilesByID[materialID2] if (getMat2 != null) { chunk.background[x, y].material = getMat2 @@ -127,7 +124,7 @@ fun main() { val colorVariant2 = reader.readUnsignedByte() val modifier2 = reader.readUnsignedShort() - val getModifier2 = Starbound.TILE_MODIFIER_BY_ID[modifier2] + val getModifier2 = starbound.tileModifiersByID[modifier2] if (getModifier2 != null && getMat2 != null) { chunk.background[x, y].modifier = getModifier2 @@ -151,7 +148,7 @@ fun main() { val indestructible = reader.readBoolean() val unknown = reader.readUnsignedByte() - val getLiquid = Starbound.LIQUID_BY_ID[liquid] + val getLiquid = starbound.liquidByID[liquid] if (getLiquid != null) { val state = chunk.setLiquid(x, y, getLiquid)!! @@ -177,7 +174,7 @@ fun main() { //client.world!!.parallax = Starbound.parallaxAccess["garden"] - val item = Starbound.ITEM.values.random() + val item = starbound.items.values.random() val rand = java.util.Random() for (i in 0 .. 10) { @@ -275,7 +272,7 @@ fun main() { } while (client.renderFrame()) { - Starbound.pollCallbacks() + starbound.pollCallbacks() //ent.think(client.frameRenderTime) //client.camera.pos.x = ent.position.x.toFloat() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt new file mode 100644 index 00000000..3edb13fa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/ObjectRegistry.kt @@ -0,0 +1,176 @@ +package ru.dbotthepony.kstarbound + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.ints.IntIterator +import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ObjectCollection +import it.unimi.dsi.fastutil.objects.ObjectIterator +import it.unimi.dsi.fastutil.objects.ObjectSet +import org.apache.logging.log4j.LogManager +import java.util.* + +class ObjectRegistry(val name: String, val key: (T) -> String, val intKey: ((T) -> Int)? = null) { + private val objects = Object2ObjectOpenHashMap() + private val intObjects = Int2ObjectOpenHashMap() + + private val origins = Object2ObjectOpenHashMap() + private val intOrigins = Int2ObjectOpenHashMap() + + val view: Map = Collections.unmodifiableMap(objects) + val intView = object : Int2ObjectMap { + override fun get(key: Int): T? = intObjects[key] + override fun containsKey(key: Int) = intObjects.containsKey(key) + override fun defaultReturnValue(rv: T) = throw UnsupportedOperationException() + override fun defaultReturnValue(): T = throw UnsupportedOperationException() + override fun containsValue(value: T) = intObjects.containsValue(value) + override fun isEmpty() = intObjects.isEmpty() + override fun putAll(from: Map) = throw UnsupportedOperationException() + + private val int2ObjectEntrySet: ObjectSet> = object : ObjectSet> { + override val size: Int + get() = intObjects.int2ObjectEntrySet().size + + override fun add(element: Int2ObjectMap.Entry) = throw UnsupportedOperationException() + override fun addAll(elements: Collection>) = throw UnsupportedOperationException() + override fun clear() = throw UnsupportedOperationException() + + override fun iterator(): ObjectIterator> { + return object : ObjectIterator> { + val iterator = intObjects.int2ObjectEntrySet().iterator() + override fun hasNext() = iterator.hasNext() + override fun remove() = throw UnsupportedOperationException() + override fun next() = iterator.next() + } + } + + override fun contains(element: Int2ObjectMap.Entry) = intObjects.int2ObjectEntrySet().contains(element) + override fun containsAll(elements: Collection>) = intObjects.int2ObjectEntrySet().containsAll(elements) + override fun isEmpty() = intObjects.int2ObjectEntrySet().isEmpty() + override fun remove(element: Int2ObjectMap.Entry) = throw UnsupportedOperationException() + override fun removeAll(elements: Collection>) = throw UnsupportedOperationException() + override fun retainAll(elements: Collection>) = throw UnsupportedOperationException() + } + + override fun int2ObjectEntrySet(): ObjectSet> { + return int2ObjectEntrySet + } + + override val keys: IntSet = object : IntSet { + override fun add(key: Int) = throw UnsupportedOperationException() + override fun addAll(c: IntCollection) = throw UnsupportedOperationException() + override fun addAll(elements: Collection) = throw UnsupportedOperationException() + override fun clear() = throw UnsupportedOperationException() + + override fun iterator(): IntIterator { + return object : IntIterator { + val iterator = intObjects.keys.iterator() + override fun hasNext() = iterator.hasNext() + override fun remove() = throw UnsupportedOperationException() + override fun nextInt() = iterator.nextInt() + } + } + + override fun remove(k: Int) = throw UnsupportedOperationException() + override fun removeAll(c: IntCollection) = throw UnsupportedOperationException() + override fun removeAll(elements: Collection) = throw UnsupportedOperationException() + override fun retainAll(c: IntCollection) = throw UnsupportedOperationException() + override fun retainAll(elements: Collection) = throw UnsupportedOperationException() + override fun contains(key: Int) = intObjects.keys.contains(key) + override fun containsAll(c: IntCollection) = intObjects.keys.containsAll(c) + override fun containsAll(elements: Collection) = intObjects.keys.containsAll(elements) + override fun isEmpty() = intObjects.keys.isEmpty() + override fun toArray(a: IntArray) = intObjects.keys.toArray(a) + override fun toIntArray() = intObjects.keys.toIntArray() + + override val size: Int + get() = intObjects.keys.size + } + + override val values: ObjectCollection = object : ObjectCollection { + override val size: Int + get() = intObjects.values.size + + override fun add(element: T) = throw UnsupportedOperationException() + override fun addAll(elements: Collection) = throw UnsupportedOperationException() + override fun clear() = throw UnsupportedOperationException() + + override fun iterator(): ObjectIterator { + return object : ObjectIterator { + val iterator = intObjects.values.iterator() + override fun hasNext() = iterator.hasNext() + override fun next(): T = iterator.next() + override fun remove() = throw UnsupportedOperationException() + } + } + + override fun contains(element: T) = intObjects.values.contains(element) + override fun containsAll(elements: Collection) = intObjects.values.containsAll(elements) + override fun isEmpty() = intObjects.values.isEmpty() + override fun remove(element: T) = throw UnsupportedOperationException() + override fun removeAll(elements: Collection) = throw UnsupportedOperationException() + override fun retainAll(elements: Collection) = throw UnsupportedOperationException() + } + + override val size: Int + get() = intObjects.size + } + + fun clearOrigins() { + origins.clear() + intOrigins.clear() + } + + fun clear() { + clearOrigins() + objects.clear() + intObjects.clear() + } + + fun add(value: T, origin: Any? = null): Boolean { + val key = this.key.invoke(value) + + val existing = objects.put(key, value) + + if (existing != null) { + val oldOrigin = origins[key] + + if (origin == null && oldOrigin == null) + LOGGER.warn("Registry $name already has object with key $key! Overwriting.") + else if (origin == null) + LOGGER.warn("Registry $name already has object with key $key! Overwriting. (old originated from $oldOrigin)") + else + LOGGER.warn("Registry $name already has object with key $key! Overwriting. (old originated from $oldOrigin, new originate from $origin).") + } + + if (origin == null) + origins.remove(key) + else + origins[key] = origin + + if (this.intKey == null) + return existing != null + + val intKey = this.intKey.invoke(value) + val intExisting = intObjects.put(intKey, value) + + if (intExisting != null) { + val oldOrigin = intOrigins[intKey] + + if (origin == null && oldOrigin == null) + LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${this.key.invoke(intExisting)})! Overwriting.") + else if (origin == null) + LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${this.key.invoke(intExisting)})! Overwriting. (old originated from $oldOrigin)") + else + LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${this.key.invoke(intExisting)})! Overwriting. (old originated from $oldOrigin, new originate from $origin).") + } + + return existing != null || intExisting != null + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 5d2b848f..8b398363 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -6,15 +6,16 @@ import com.google.gson.* import com.google.gson.internal.bind.TypeAdapters import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap 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.ISBFileLocator import ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.api.NonExistingFile import ru.dbotthepony.kstarbound.api.PhysicalFile import ru.dbotthepony.kstarbound.api.explore import ru.dbotthepony.kstarbound.defs.* +import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration import ru.dbotthepony.kstarbound.defs.image.ImageReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.item.BackArmorItemPrototype @@ -27,17 +28,31 @@ import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.ItemPrototype import ru.dbotthepony.kstarbound.defs.item.LegsArmorItemPrototype +import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect import ru.dbotthepony.kstarbound.defs.item.LiquidItemPrototype import ru.dbotthepony.kstarbound.defs.item.MaterialItemPrototype import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype +import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototypeLayer import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition +import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition -import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition +import ru.dbotthepony.kstarbound.defs.world.SkyColoring +import ru.dbotthepony.kstarbound.defs.world.SkyColoringManifold +import ru.dbotthepony.kstarbound.defs.world.SkyParameters +import ru.dbotthepony.kstarbound.defs.world.SkySatellite +import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.io.* +import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter +import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter +import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter +import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter +import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter +import ru.dbotthepony.kstarbound.io.json.Vector4dTypeAdapter +import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter @@ -46,6 +61,7 @@ import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.util.AssetPathStack +import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.WriteOnce import java.io.* import java.text.DateFormat @@ -57,147 +73,146 @@ import java.util.function.Supplier import java.util.stream.Collector import kotlin.collections.ArrayList -const val METRES_IN_STARBOUND_UNIT = 0.5 -const val METRES_IN_STARBOUND_UNITf = 0.5f +class Starbound : ISBFileLocator { + private val logger = LogManager.getLogger() -const val PIXELS_IN_STARBOUND_UNIT = 8.0 -const val PIXELS_IN_STARBOUND_UNITf = 8.0f + val stringInterner: Interner = Interners.newWeakInterner() + val pathStack = AssetPathStack(stringInterner) -// class TileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) -// class ProjectileDefLoadingException(message: String, cause: Throwable? = null) : IllegalStateException(message, cause) + private val _tiles = ObjectRegistry("tiles", TileDefinition::materialName, TileDefinition::materialId) + val tiles = _tiles.view + val tilesByID = _tiles.intView -fun String.sbIntern(): String = Starbound.STRING_INTERNER.intern(this) + private val _tileModifiers = ObjectRegistry("tile modifiers", MaterialModifier::modName, MaterialModifier::modId) + val tileModifiers = _tileModifiers.view + val tileModifiersByID = _tileModifiers.intView -object Starbound { - private val LOGGER = LogManager.getLogger() + private val _liquid = ObjectRegistry("liquid", LiquidDefinition::name, LiquidDefinition::liquidId) + val liquid = _liquid.view + val liquidByID = _liquid.intView - val STRING_INTERNER: Interner = Interners.newWeakInterner() - val pathStack = AssetPathStack(STRING_INTERNER) + private val _species = ObjectRegistry("species", Species::kind) + val species = _species.view - fun assetFolder(input: String): String { - return pathStack.remap(input) - } + private val _statusEffects = ObjectRegistry("status effects", StatusEffectDefinition::name) + val statusEffects = _statusEffects.view - private val tiles = Object2ObjectOpenHashMap() - private val tilesByMaterialID = Int2ObjectOpenHashMap() + private val _particles = ObjectRegistry("particles", ParticleDefinition::kind) + val particles = _particles.view - private val tileModifiers = Object2ObjectOpenHashMap() - private val tileModifiersByID = Int2ObjectOpenHashMap() + private val _items = ObjectRegistry("items", IItemDefinition::itemName) + val items = _items.view - private val liquid = Object2ObjectOpenHashMap() - private val liquidByID = Int2ObjectOpenHashMap() + val spriteRegistry: SpriteReference.Adapter - private val projectiles = Object2ObjectOpenHashMap() - private val parallax = Object2ObjectOpenHashMap() - private val functions = Object2ObjectOpenHashMap() - private val species = Object2ObjectOpenHashMap() - private val _statusEffects = Object2ObjectOpenHashMap() - private val _particles = Object2ObjectOpenHashMap() - - val particles: Map = Collections.unmodifiableMap(_particles) - val statusEffects: Map = Collections.unmodifiableMap(_statusEffects) - - private val items = Object2ObjectOpenHashMap() - - val LIQUID: Map = Collections.unmodifiableMap(liquid) - val LIQUID_BY_ID: Map = Collections.unmodifiableMap(liquidByID) - val TILE_MODIFIER: Map = Collections.unmodifiableMap(tileModifiers) - val TILE_MODIFIER_BY_ID: Map = Collections.unmodifiableMap(tileModifiersByID) - val TILE: Map = Collections.unmodifiableMap(tiles) - val TILE_BY_ID: Map = Collections.unmodifiableMap(tilesByMaterialID) - val PROJECTILE: Map = Collections.unmodifiableMap(projectiles) - val PARALLAX: Map = Collections.unmodifiableMap(parallax) - val FUNCTION: Map = Collections.unmodifiableMap(functions) - val ITEM: Map = Collections.unmodifiableMap(items) - - val STRING_ADAPTER: TypeAdapter = object : TypeAdapter() { - override fun write(out: JsonWriter, value: String?) { - if (value == null) - out.nullValue() - else - out.value(value) - } - - override fun read(`in`: JsonReader): String? { - return STRING_INTERNER.intern(TypeAdapters.STRING.read(`in`) ?: return null) - } - } - - val GSON: Gson = GsonBuilder() - .enableComplexMapKeySerialization() - .serializeNulls() - .setDateFormat(DateFormat.LONG) - .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) - .setPrettyPrinting() + val gson: Gson = with(GsonBuilder()) { + serializeNulls() + setDateFormat(DateFormat.LONG) + setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) + setPrettyPrinting() // чтоб строки всегда intern'ились - .registerTypeAdapter(STRING_ADAPTER) + registerTypeAdapter(object : TypeAdapter() { + override fun write(out: JsonWriter, value: String?) { + if (value == null) + out.nullValue() + else + out.value(value) + } - // Обработчик @JsonImplementation - .registerTypeAdapterFactory(JsonImplementationTypeFactory) - - // ImmutableList, ImmutableSet, ImmutableMap - .registerTypeAdapterFactory(ImmutableCollectionAdapterFactory) - - // ArrayList - .registerTypeAdapterFactory(ArrayListAdapterFactory) - - // все enum'ы без особых настроек - .registerTypeAdapterFactory(EnumAdapter.Companion) - - // автоматическое создание BuilderAdapter по @аннотациям - .registerTypeAdapterFactory(BuilderAdapter.Companion) - - // автоматическое создание FactoryAdapter по @аннотациям - .registerTypeAdapterFactory(FactoryAdapter.Companion) - - .registerTypeAdapterFactory(EitherTypeAdapter) - - .also(::addStarboundJsonAdapters) - - .registerTypeAdapterFactory(IItemDefinition.InventoryIcon.Factory(pathStack)) - .registerTypeAdapter(SpriteReference.Adapter(pathStack)) - .registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.Factory(pathStack)) - .registerTypeAdapterFactory(DirectAssetReferenceFactory(pathStack)) - .registerTypeAdapter(ImageReference.Adapter(pathStack)) - - .registerTypeAdapterFactory(AssetReferenceFactory(pathStack) { - val file = locate(it) - - if (file.exists && file.isFile) - file.reader() - else - null + override fun read(`in`: JsonReader): String? { + return stringInterner.intern(TypeAdapters.STRING.read(`in`) ?: return null) + } }) - .registerTypeAdapterFactory(RegistryReferenceFactory() - .add(tiles::get) - .add(tileModifiers::get) - .add(liquid::get) - .add(projectiles::get) - .add(parallax::get) - .add(functions::get) - .add(items::get) - .add(species::get) - .add(_statusEffects::get) - .add(_particles::get) - ) + // Обработчик @JsonImplementation + registerTypeAdapterFactory(JsonImplementationTypeFactory) + + // ImmutableList, ImmutableSet, ImmutableMap + registerTypeAdapterFactory(ImmutableCollectionAdapterFactory) + + // ArrayList + registerTypeAdapterFactory(ArrayListAdapterFactory) + + // все enum'ы без особых настроек + registerTypeAdapterFactory(EnumAdapter.Companion) + + // автоматическое создание BuilderAdapter по @аннотациям + registerTypeAdapterFactory(BuilderAdapter.Factory(stringInterner)) + + // автоматическое создание FactoryAdapter по @аннотациям + registerTypeAdapterFactory(FactoryAdapter.Factory(stringInterner)) + + registerTypeAdapterFactory(EitherTypeAdapter) + registerTypeAdapterFactory(SBPattern.Companion) + + registerTypeAdapter(ColorReplacements.Companion) + registerTypeAdapterFactory(BlueprintLearnList.Companion) + + registerTypeAdapter(ColorTypeAdapter.nullSafe()) + + // математические классы + registerTypeAdapter(AABBTypeAdapter) + registerTypeAdapter(AABBiTypeAdapter) + registerTypeAdapter(Vector2dTypeAdapter) + registerTypeAdapter(Vector2fTypeAdapter) + registerTypeAdapter(Vector2iTypeAdapter) + registerTypeAdapter(Vector4iTypeAdapter) + registerTypeAdapter(Vector4dTypeAdapter) + registerTypeAdapter(PolyTypeAdapter) + + // Параметры неба + registerTypeAdapterFactory(SkyParameters.ADAPTER) + registerTypeAdapter(SkyColoringManifold.ADAPTER) + registerTypeAdapterFactory(SkyColoring.ADAPTER) + registerTypeAdapterFactory(SkySatellite.ADAPTER) + registerTypeAdapterFactory(SkySatellite.LAYER_ADAPTER) + + // Данные о данжах + registerTypeAdapterFactory(DungeonWorldDef.ADAPTER) + + // Параллакс + registerTypeAdapterFactory(ParallaxPrototype.ADAPTER) + registerTypeAdapterFactory(ParallaxPrototypeLayer.ADAPTER) + registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER) + + // Функции + registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER) + registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER) + registerTypeAdapter(JsonFunction.Companion) + + // Общее + registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER) + registerTypeAdapter(MaterialReference.Companion) + registerTypeAdapterFactory(ThingDescription.Factory(stringInterner)) + + registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) + + spriteRegistry = SpriteReference.Adapter(pathStack, this@Starbound::atlasRegistry) + registerTypeAdapter(spriteRegistry) + registerTypeAdapterFactory(IItemDefinition.InventoryIcon.Factory(pathStack, spriteRegistry)) + + registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.Factory(pathStack, this@Starbound::atlasRegistry)) + registerTypeAdapterFactory(DirectAssetReferenceFactory(pathStack)) + registerTypeAdapter(ImageReference.Adapter(pathStack, this@Starbound::atlasRegistry)) + + registerTypeAdapterFactory(AssetReferenceFactory(pathStack, this@Starbound)) + + registerTypeAdapterFactory(with(RegistryReferenceFactory()) { + add(tiles::get) + add(tileModifiers::get) + add(liquid::get) + add(items::get) + add(species::get) + add(statusEffects::get) + add(particles::get) + }) .create() - - @Suppress("unchecked_cast") - fun getTypeAdapter(type: Class): TypeAdapter { - return when (type) { - Float::class.java -> TypeAdapters.FLOAT as TypeAdapter - Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter - String::class.java -> STRING_ADAPTER as TypeAdapter - Int::class.java -> TypeAdapters.INTEGER as TypeAdapter - Long::class.java -> TypeAdapters.LONG as TypeAdapter - Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter - else -> GSON.getAdapter(type) as TypeAdapter - } } + val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson) + var initializing = false private set var initialized = false @@ -217,7 +232,7 @@ object Starbound { fileSystems.add(pak.root) } - fun exists(path: String): Boolean { + override fun exists(path: String): Boolean { @Suppress("name_shadowing") var path = path @@ -234,7 +249,7 @@ object Starbound { return false } - fun locate(path: String): IStarboundFile { + override fun locate(path: String): IStarboundFile { @Suppress("name_shadowing") var path = path @@ -272,12 +287,6 @@ object Starbound { archivePaths.add(pak) } - fun loadJson(path: String): JsonElement { - return JsonParser.parseReader(locate(path).reader()) - } - - fun readDirect(path: String) = locate(path).readDirect() - fun getTileDefinition(name: String) = tiles[name] private val initCallbacks = ArrayList<() -> Unit>() @@ -294,7 +303,7 @@ object Starbound { val time = System.currentTimeMillis() callback(false, false, "Loading $name...") - LOGGER.info("Loading $name...") + logger.info("Loading $name...") loader { if (terminateLoading) { @@ -305,82 +314,40 @@ object Starbound { } callback(false, true, "Loaded $name in ${System.currentTimeMillis() - time}ms") - LOGGER.info("Loaded $name in ${System.currentTimeMillis() - time}ms") + logger.info("Loaded $name in ${System.currentTimeMillis() - time}ms") } - private fun loadStage( + private fun loadStage( callback: (Boolean, Boolean, String) -> Unit, clazz: Class, - getKey: (T) -> K, - put: (K, T) -> T?, + registry: ObjectRegistry, files: List, - name: String, ) { loadStage(callback, loader = { for (listedFile in files) { try { it("Loading $listedFile") - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), clazz) } - val key = getKey.invoke(def) - - if (put.invoke(key, def) != null) { - LOGGER.warn("Already had $name with name $key loaded! Older prototype was overwritten (loading $listedFile)") - } + val def = pathStack(listedFile.computeDirectory()) { gson.fromJson(listedFile.reader(), clazz) } + registry.add(def, listedFile) } catch (err: Throwable) { - LOGGER.error("Loading $name definition file $listedFile", err) + logger.error("Loading ${registry.name} definition file $listedFile", err) } if (terminateLoading) { break } } - }, name) - } - - private fun loadStage( - callback: (Boolean, Boolean, String) -> Unit, - clazz: Class, - getKey0: (T) -> K0, - put0: (K0, T) -> T?, - getKey1: (T) -> K1, - put1: (K1, T) -> T?, - files: List, - name: String, - ) { - loadStage(callback, loader = { - for (listedFile in files) { - try { - it("Loading $listedFile") - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), clazz) } - val key0 = getKey0.invoke(def) - val key1 = getKey1.invoke(def) - - if (put0.invoke(key0, def) != null) { - LOGGER.warn("Already had $name with name $key0 loaded! Older prototype was overwritten (loading $listedFile)") - } - - if (put1.invoke(key1, def) != null) { - LOGGER.warn("Already had $name with ID $key1 loaded! Older prototype was overwritten (loading $listedFile)") - } - } catch (err: Throwable) { - LOGGER.error("Loading $name definition file $listedFile", err) - } - - if (terminateLoading) { - break - } - } - }, name) + }, registry.name) } private fun doInitialize(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) { var time = System.currentTimeMillis() if (archivePaths.isNotEmpty()) { - callback(false, false, "Searching for pak archives...".also(LOGGER::info)) + callback(false, false, "Searching for pak archives...".also(logger::info)) for (path in archivePaths) { - callback(false, false, "Reading index of ${path}...".also(LOGGER::info)) + callback(false, false, "Reading index of ${path}...".also(logger::info)) addPak(StarboundPak(path) { _, status -> callback(false, true, "${path.parent}/${path.name}: $status") @@ -388,9 +355,9 @@ object Starbound { } } - callback(false, false, "Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) + callback(false, false, "Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(logger::info)) time = System.currentTimeMillis() - callback(false, false, "Building file index...".also(LOGGER::info)) + callback(false, false, "Building file index...".also(logger::info)) val ext2files = fileSystems.parallelStream() .flatMap { it.explore() } @@ -439,22 +406,19 @@ object Starbound { } }) - callback(false, false, "Finished building file index in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) + callback(false, false, "Finished building file index in ${System.currentTimeMillis() - time}ms".also(logger::info)) - loadStage(callback, this::loadFunctions, "functions") - //loadStage(callback, this::loadProjectiles, "projectiles") - loadStage(callback, this::loadParallax, "parallax definitions") loadStage(callback, this::loadItemDefinitions, "item definitions") - loadStage(callback, TileDefinition::class.java, TileDefinition::materialName, tiles::put, TileDefinition::materialId, tilesByMaterialID::put, ext2files["material"] ?: listOf(), "materials") - loadStage(callback, MaterialModifier::class.java, MaterialModifier::modName, tileModifiers::put, MaterialModifier::modId, tileModifiersByID::put, ext2files["matmod"] ?: listOf(), "material modifier definitions") - loadStage(callback, LiquidDefinition::class.java, LiquidDefinition::name, liquid::put, LiquidDefinition::liquidId, liquidByID::put, ext2files["liquid"] ?: listOf(), "liquid definitions") - loadStage(callback, StatusEffectDefinition::class.java, StatusEffectDefinition::name, _statusEffects::put, ext2files["statuseffect"] ?: listOf(), "status effects") - loadStage(callback, Species::class.java, Species::kind, species::put, ext2files["species"] ?: listOf(), "species") - loadStage(callback, ParticleDefinition::class.java, ParticleDefinition::kind, _particles::put, ext2files["particle"] ?: listOf(), "particles") + loadStage(callback, TileDefinition::class.java, _tiles, ext2files["material"] ?: listOf()) + loadStage(callback, MaterialModifier::class.java, _tileModifiers, ext2files["matmod"] ?: listOf()) + loadStage(callback, LiquidDefinition::class.java, _liquid, ext2files["liquid"] ?: listOf()) + loadStage(callback, StatusEffectDefinition::class.java, _statusEffects, ext2files["statuseffect"] ?: listOf()) + loadStage(callback, Species::class.java, _species, ext2files["species"] ?: listOf()) + loadStage(callback, ParticleDefinition::class.java, _particles, ext2files["particle"] ?: listOf()) pathStack.block("/") { - playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) + playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) } initializing = false @@ -493,68 +457,6 @@ object Starbound { } } - private fun loadProjectiles(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".projectile") }) { - try { - callback("Loading $listedFile") - - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(it) } - check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } - projectiles[def.projectileName] = def - } catch(err: Throwable) { - //throw ProjectileDefLoadingException("Loading projectile file $listedFile", err) - LOGGER.error("Loading projectile file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - - private fun loadFunctions(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".functions") }) { - try { - callback("Loading $listedFile") - - val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject - - for (key in readObject.keySet()) { - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(readObject[key], JsonFunction::class.java) } - functions[key] = def - } - } catch(err: Throwable) { - LOGGER.error("Loading function file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - - private fun loadParallax(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) { - try { - callback("Loading $listedFile") - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ParallaxPrototype::class.java) } - parallax[listedFile.name.substringBefore('.')] = def - } catch(err: Throwable) { - LOGGER.error("Loading parallax file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - private fun loadItemDefinitions(callback: (String) -> Unit) { val files = linkedMapOf( ".item" to ItemPrototype::class.java, @@ -573,13 +475,10 @@ object Starbound { for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.keys.any { f.name.endsWith(it) } }) { try { callback("Loading $listedFile") - val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value) } - - if (items.put(def.itemName, def.assemble()) != null) { - LOGGER.warn("Already has item with name ${def.itemName} loaded! Older prototype was overwritten (loading $listedFile)") - } + val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { gson.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value) } + _items.add(def, listedFile) } catch (err: Throwable) { - LOGGER.error("Loading item definition file $listedFile", err) + logger.error("Loading item definition file $listedFile", err) } if (terminateLoading) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt deleted file mode 100644 index f41979c1..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt +++ /dev/null @@ -1,94 +0,0 @@ -package ru.dbotthepony.kstarbound - -import com.google.gson.GsonBuilder -import ru.dbotthepony.kstarbound.defs.ColorReplacements -import ru.dbotthepony.kstarbound.defs.DamageType -import ru.dbotthepony.kstarbound.defs.JsonFunction -import ru.dbotthepony.kstarbound.defs.MaterialReference -import ru.dbotthepony.kstarbound.util.SBPattern -import ru.dbotthepony.kstarbound.defs.ThingDescription -import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration -import ru.dbotthepony.kstarbound.defs.image.SpriteReference -import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition -import ru.dbotthepony.kstarbound.defs.item.IItemDefinition -import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect -import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype -import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototypeLayer -import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList -import ru.dbotthepony.kstarbound.defs.projectile.ActionActions -import ru.dbotthepony.kstarbound.defs.projectile.ActionConfig -import ru.dbotthepony.kstarbound.defs.projectile.ActionLoop -import ru.dbotthepony.kstarbound.defs.projectile.ActionProjectile -import ru.dbotthepony.kstarbound.defs.projectile.ActionSound -import ru.dbotthepony.kstarbound.defs.world.SkyColoring -import ru.dbotthepony.kstarbound.defs.world.SkyColoringManifold -import ru.dbotthepony.kstarbound.defs.world.SkyParameters -import ru.dbotthepony.kstarbound.defs.world.SkySatellite -import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef -import ru.dbotthepony.kstarbound.io.ColorTypeAdapter -import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter -import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector4dTypeAdapter -import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter -import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter -import ru.dbotthepony.kstarbound.math.PolyTypeAdapter - -fun addStarboundJsonAdapters(builder: GsonBuilder) { - with(builder) { - registerTypeAdapterFactory(SBPattern.Companion) - - registerTypeAdapter(ColorReplacements.Companion) - registerTypeAdapterFactory(BlueprintLearnList.Companion) - - registerTypeAdapter(ColorTypeAdapter.nullSafe()) - - // математические классы - registerTypeAdapter(AABBTypeAdapter) - registerTypeAdapter(AABBiTypeAdapter) - registerTypeAdapter(Vector2dTypeAdapter) - registerTypeAdapter(Vector2fTypeAdapter) - registerTypeAdapter(Vector2iTypeAdapter) - registerTypeAdapter(Vector4iTypeAdapter) - registerTypeAdapter(Vector4dTypeAdapter) - registerTypeAdapter(PolyTypeAdapter) - - // Снаряды - registerTypeAdapterFactory(ActionConfig.ADAPTER) - registerTypeAdapterFactory(ActionProjectile.ADAPTER) - registerTypeAdapterFactory(ActionSound.ADAPTER) - registerTypeAdapterFactory(ActionLoop.ADAPTER) - registerTypeAdapterFactory(ActionActions.ADAPTER) - - // Параметры неба - registerTypeAdapterFactory(SkyParameters.ADAPTER) - registerTypeAdapter(SkyColoringManifold.ADAPTER) - registerTypeAdapterFactory(SkyColoring.ADAPTER) - registerTypeAdapterFactory(SkySatellite.ADAPTER) - registerTypeAdapterFactory(SkySatellite.LAYER_ADAPTER) - - // Данные о данжах - registerTypeAdapterFactory(DungeonWorldDef.ADAPTER) - - // Параллакс - registerTypeAdapterFactory(ParallaxPrototype.ADAPTER) - registerTypeAdapterFactory(ParallaxPrototypeLayer.ADAPTER) - registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER) - - // Функции - registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER) - registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER) - registerTypeAdapter(JsonFunction.Companion) - - // Общее - registerTypeAdapterFactory(AtlasConfiguration.Companion) - - registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER) - registerTypeAdapter(MaterialReference.Companion) - registerTypeAdapter(ThingDescription.Companion) - - registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt index 8322b1cc..99934c09 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt @@ -10,7 +10,42 @@ import java.io.* import java.nio.ByteBuffer import java.util.stream.Stream -interface IStarboundFile { +fun interface ISBFileLocator { + fun locate(path: String): IStarboundFile + fun exists(path: String): Boolean = locate(path).exists + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun read(path: String) = locate(path).read() + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun readDirect(path: String) = locate(path).readDirect() + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun open(path: String) = locate(path).open() + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun reader(path: String) = locate(path).reader() + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun readJson(path: String) = locate(path).readJson() +} + +interface IStarboundFile : ISBFileLocator { val exists: Boolean val isDirectory: Boolean @@ -60,7 +95,7 @@ interface IStarboundFile { return path } - fun locate(path: String): IStarboundFile { + override fun locate(path: String): IStarboundFile { @Suppress("name_shadowing") val path = path.trim() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 22c27b6d..bdf4c1be 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -58,13 +58,13 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() private val backgroundTilePrograms = HashMap() private val tileRenderersCache = HashMap() @@ -70,15 +71,15 @@ class TileRenderers(val state: GLStateTracker) { fun getTileRenderer(defName: String): TileRenderer { return tileRenderersCache.computeIfAbsent(defName) { - val def = Starbound.TILE[defName] // TODO: Пустой рендерер - return@computeIfAbsent TileRenderer(state, def!!) + val def = client.starbound.tiles[defName] // TODO: Пустой рендерер + return@computeIfAbsent TileRenderer(this, def!!) } } fun getModifierRenderer(defName: String): TileRenderer { return modifierRenderersCache.computeIfAbsent(defName) { - val def = Starbound.TILE_MODIFIER[defName] // TODO: Пустой рендерер - return@computeIfAbsent TileRenderer(state, def!!) + val def = client.starbound.tileModifiers[defName] // TODO: Пустой рендерер + return@computeIfAbsent TileRenderer(this, def!!) } } @@ -182,7 +183,8 @@ private class ModifierEqualityTester(val definition: MaterialModifier) : Equalit } } -class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { +class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) { + val state get() = renderers.state val texture = state.loadNamedTexture(def.renderParameters.texture.image).also { it.textureMagFilter = GL_NEAREST } @@ -193,8 +195,8 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { else -> throw IllegalStateException() } - val bakedProgramState = state.tileRenderers.foreground(texture) - val bakedBackgroundProgramState = state.tileRenderers.background(texture) + val bakedProgramState = renderers.foreground(texture) + val bakedBackgroundProgramState = renderers.background(texture) // private var notifiedDepth = false private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileChunk, builder: AbstractVertexBuilder<*>, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) { @@ -252,9 +254,9 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { for (renderPiece in matchPiece.pieces) { if (renderPiece.piece.texture != null) { val program = if (background) { - state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!)) + renderers.background(state.loadNamedTexture(renderPiece.piece.texture!!)) } else { - state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) + renderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!)) } tesselateAt(self, renderPiece.piece, getter, layers.computeIfAbsent(program, def.renderParameters.zLevel, ::vertexTextureBuilder), pos, renderPiece.offset, isModifier) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt index e5fc68a7..313a2c92 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/EntityRenderer.kt @@ -3,11 +3,7 @@ package ru.dbotthepony.kstarbound.client.render.entity import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import ru.dbotthepony.kstarbound.client.ClientChunk import ru.dbotthepony.kstarbound.client.gl.GLStateTracker -import ru.dbotthepony.kstarbound.client.render.entity.ItemRenderer -import ru.dbotthepony.kstarbound.client.render.entity.ProjectileRenderer import ru.dbotthepony.kstarbound.world.entities.Entity -import ru.dbotthepony.kstarbound.world.entities.ItemEntity -import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.vector.ndouble.Vector2d import java.io.Closeable @@ -50,14 +46,17 @@ open class EntityRenderer(val state: GLStateTracker, val entity: Entity, open va check(renderers.put(clazz, renderer as (state: GLStateTracker, entity: Entity, chunk: ClientChunk?) -> EntityRenderer) == null) { "Already has renderer for ${clazz.canonicalName}!" } } + inline fun registerRenderer(noinline renderer: (state: GLStateTracker, entity: T, chunk: ClientChunk?) -> EntityRenderer) { + registerRenderer(T::class.java, renderer) + } + fun getRender(state: GLStateTracker, entity: Entity, chunk: ClientChunk? = null): EntityRenderer { val factory = renderers[entity::class.java] ?: return EntityRenderer(state, entity, chunk) return factory.invoke(state, entity, chunk) } init { - registerRenderer(Projectile::class.java, ::ProjectileRenderer) - registerRenderer(ItemEntity::class.java, ::ItemRenderer) + registerRenderer(::ItemRenderer) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ProjectileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ProjectileRenderer.kt deleted file mode 100644 index 76a53abf..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/entity/ProjectileRenderer.kt +++ /dev/null @@ -1,40 +0,0 @@ -package ru.dbotthepony.kstarbound.client.render.entity - -import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf -import ru.dbotthepony.kstarbound.client.ClientChunk -import ru.dbotthepony.kstarbound.client.gl.GLStateTracker -import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers -import ru.dbotthepony.kstarbound.client.gl.vertex.quadRotatedZ -import ru.dbotthepony.kstarbound.client.render.FrameAnimator -import ru.dbotthepony.kstarbound.client.render.SpriteAnimator -import ru.dbotthepony.kstarbound.client.render.makeSpriteAnimator -import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile -import ru.dbotthepony.kvector.matrix.Matrix4fStack - -open class ProjectileRenderer(state: GLStateTracker, entity: Projectile, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) { - private val def = entity.def - private val animator = def.image.makeSpriteAnimator(state, def.animationCycle, def.animationLoops) - - override fun render(stack: Matrix4fStack) { - state.shaderVertexTexture.use() - state.shaderVertexTexture.transform.set(stack.last) - state.activeTexture = 0 - state.shaderVertexTexture["_texture"] = 0 - - animator.advance() - val sprite = animator.sprite - sprite.texture.bind() - - val builder = state.flat2DTexturedQuads.small - - builder.begin() - - val width = (sprite.width / PIXELS_IN_STARBOUND_UNITf) / 2f - val height = (sprite.height / PIXELS_IN_STARBOUND_UNITf) / 2f - - builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, sprite.transformer) - - builder.upload() - builder.draw() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt index d384eceb..af5c2a8a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/AssetReference.kt @@ -8,6 +8,7 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import ru.dbotthepony.kstarbound.api.ISBFileLocator import ru.dbotthepony.kstarbound.util.AssetPathStack import java.io.Reader import java.lang.reflect.ParameterizedType @@ -19,7 +20,7 @@ import java.util.concurrent.ConcurrentHashMap * * Созданный [TypeAdapter] имеет встроенный кеш. */ -class AssetReferenceFactory(val remapper: AssetPathStack, val reader: (String) -> Reader?) : TypeAdapterFactory { +class AssetReferenceFactory(val remapper: AssetPathStack, val locator: ISBFileLocator) : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == AssetReference::class.java) { val param = type.type as? ParameterizedType ?: return null @@ -51,13 +52,15 @@ class AssetReferenceFactory(val remapper: AssetPathStack, val reader: (String) - if (fullPath in missing) return null - val reader = reader.invoke(fullPath) + val file = locator.locate(fullPath) - if (reader == null) { + if (!file.exists) { missing.add(fullPath) return AssetReference(path, fullPath, null) } + val reader = file.reader() + val value = remapper(fullPath) { adapter.read(JsonReader(reader).also { it.isLenient = true diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt index dd643e1e..1cb03a7a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IThingWithDescription.kt @@ -1,13 +1,15 @@ package ru.dbotthepony.kstarbound.defs import com.google.common.collect.ImmutableMap +import com.google.common.collect.Interner +import com.google.gson.Gson 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 ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation -import ru.dbotthepony.kstarbound.sbIntern -import ru.dbotthepony.kstarbound.util.NotNullVar @JsonImplementation(ThingDescription::class) interface IThingWithDescription { @@ -77,43 +79,76 @@ data class ThingDescription( override val racialDescription: Map, override val racialShortDescription: Map, ) : IThingWithDescription { - companion object : TypeAdapter() { - override fun write(out: JsonWriter, value: ThingDescription) { - TODO("Not yet implemented") - } + class Factory(val interner: Interner = Interner { it }) : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == ThingDescription::class.java) { + return object : TypeAdapter() { + override fun write(out: JsonWriter, value: ThingDescription?) { + if (value == null) + out.nullValue() + else { + out.beginObject() - override fun read(`in`: JsonReader): ThingDescription { - `in`.beginObject() + out.name("shortdescription") + out.value(value.shortdescription) - var shortdescription = "..." - var description = "..." - val racial = ImmutableMap.Builder() - val racialShort = ImmutableMap.Builder() + out.name("description") + out.value(value.description) - while (`in`.peek() !== JsonToken.END_OBJECT) { - when (val name = `in`.nextName()) { - "shortdescription" -> shortdescription = `in`.nextString() - "description" -> description = `in`.nextString() - else -> { - if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) { - racialShort.put(name.substring(0, name.length - "shortdescription".length).sbIntern(), `in`.nextString()) - } else if (name.endsWith("description") || name.endsWith("Description")) { - racial.put(name.substring(0, name.length - "description".length).sbIntern(), `in`.nextString()) - } else { - `in`.skipValue() + for ((k, v) in value.racialDescription) { + out.name("${k}Description") + out.value(v) + } + + for ((k, v) in value.racialShortDescription) { + out.name("${k}Shortdescription") + out.value(v) + } + + out.endObject() } } - } + + override fun read(`in`: JsonReader): ThingDescription? { + if (`in`.peek() == JsonToken.NULL) + return null + + `in`.beginObject() + + var shortdescription = "..." + var description = "..." + val racial = ImmutableMap.Builder() + val racialShort = ImmutableMap.Builder() + + while (`in`.peek() !== JsonToken.END_OBJECT) { + when (val name = `in`.nextName()) { + "shortdescription" -> shortdescription = `in`.nextString() + "description" -> description = `in`.nextString() + else -> { + if (name.endsWith("shortdescription") || name.endsWith("shortDescription") || name.endsWith("Shortdescription") || name.endsWith("ShortDescription")) { + racialShort.put(interner.intern(name.substring(0, name.length - "shortdescription".length)), interner.intern(`in`.nextString())) + } else if (name.endsWith("description") || name.endsWith("Description")) { + racial.put(interner.intern(name.substring(0, name.length - "description".length)), interner.intern(`in`.nextString())) + } else { + `in`.skipValue() + } + } + } + } + + `in`.endObject() + + return ThingDescription( + shortdescription = shortdescription, + description = description, + racialDescription = racial.build(), + racialShortDescription = racialShort.build() + ) + } + } as TypeAdapter } - `in`.endObject() - - return ThingDescription( - shortdescription = shortdescription, - description = description, - racialDescription = racial.build(), - racialShortDescription = racialShort.build() - ) + return null } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt deleted file mode 100644 index b32dfbb0..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt +++ /dev/null @@ -1,45 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.common.collect.ImmutableMap -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.util.enrollMap -import ru.dbotthepony.kstarbound.defs.util.flattenMap -import ru.dbotthepony.kstarbound.io.json.builder.INativeJsonHolder - -/** - * Базовый класс описания прототипа игрового объекта - * - * Должен иметь все (или больше) поля объекта, который он будет создавать - * - * Поля должны иметь базовые ограничения (т.е. ограничения, которые применимы для всех конфигураций прототипа). - * Если границы поля зависят от других полей, то проверка такого поля должна осуществляться уже при самой - * сборке прототипа. - */ -abstract class RawPrototype, ASSEMBLED : AssembledPrototype> : - INativeJsonHolder { - val json = Object2ObjectArrayMap() - fun enroll() = enrollMap(json, Starbound.STRING_INTERNER::intern) - abstract fun assemble(directory: String = ""): ASSEMBLED - - override fun acceptJson(json: MutableMap) { - this.json.clear() - this.json.putAll(json) - } -} - -/** - * Базовый класс описанного прототипа игрового объекта - * - * Должен иметь все поля объекта, которые будут использоваться движком напрямую - * - * Создается соответствующим [RawPrototype], который проверил уже все поля - * на их правильность. - */ -abstract class AssembledPrototype, RAW : RawPrototype>( - val json: ImmutableMap -) { - open fun getParameter(key: String): Any? = json[key] - fun unroll() = flattenMap(json) - abstract fun disassemble(): RAW -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt index 78693b54..ef3a3470 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/AtlasConfiguration.kt @@ -13,11 +13,16 @@ import com.google.gson.TypeAdapterFactory import com.google.gson.internal.bind.TypeAdapters 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.Starbound +import ru.dbotthepony.kstarbound.api.ISBFileLocator import ru.dbotthepony.kstarbound.client.gl.GLTexture2D import ru.dbotthepony.kstarbound.io.json.stream import ru.dbotthepony.kstarbound.io.json.transform import ru.dbotthepony.kstarbound.registerTypeAdapter +import ru.dbotthepony.kstarbound.util.AssetPathStack +import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector4i import java.util.concurrent.ConcurrentHashMap @@ -155,14 +160,16 @@ class AtlasConfiguration private constructor( } } - companion object : TypeAdapterFactory { + companion object { val EMPTY: AtlasConfiguration init { val sprite = Sprite("root", Vector4i(0, 0, 0, 0)) EMPTY = AtlasConfiguration("null", ImmutableMap.of("root", sprite, "default", sprite, "0", sprite), ImmutableList.of(sprite)) } + } + class Registry(val locator: ISBFileLocator, val remapper: AssetPathStack, val gson: Gson) { private val cache = ConcurrentHashMap() private fun generateFakeNames(dimensions: Vector2i): JsonArray { @@ -193,8 +200,8 @@ class AtlasConfiguration private constructor( val sprites = LinkedHashMap() if (frameGrid is JsonObject) { - val size = Starbound.GSON.fromJson(frameGrid["size"] ?: throw JsonSyntaxException("Missing frameGrid.size"), Vector2i::class.java) - val dimensions = Starbound.GSON.fromJson(frameGrid["dimensions"] ?: throw JsonSyntaxException("Missing frameGrid.dimensions"), Vector2i::class.java) + val size = gson.fromJson(frameGrid["size"] ?: throw JsonSyntaxException("Missing frameGrid.size"), Vector2i::class.java) + val dimensions = gson.fromJson(frameGrid["dimensions"] ?: throw JsonSyntaxException("Missing frameGrid.dimensions"), Vector2i::class.java) require(size.x >= 0) { "Invalid size.x: ${size.x}" } require(size.y >= 0) { "Invalid size.y: ${size.y}" } @@ -233,7 +240,7 @@ class AtlasConfiguration private constructor( } for ((spriteName, coords) in frameList.entrySet()) { - sprites[spriteName] = Sprite(spriteName, Starbound.GSON.fromJson(coords, Vector4i::class.java)) + sprites[spriteName] = Sprite(spriteName, gson.fromJson(coords, Vector4i::class.java)) } } @@ -256,7 +263,7 @@ class AtlasConfiguration private constructor( while (current != "/" && current != "") { val get = cache.computeIfAbsent("$current/$name") { - val file = Starbound.locate("$it.frames") + val file = locator.locate("$it.frames") if (file.exists) { try { @@ -306,13 +313,5 @@ class AtlasConfiguration private constructor( return EMPTY } - - override fun create(gson: Gson, type: TypeToken): TypeAdapter? { - if (type.rawType == AtlasConfiguration::class.java) { - return gson.getAdapter(String::class.java).transform(read = read@{ get(it ?: return@read it as AtlasConfiguration?) }, write = write@{ it?.name }) as TypeAdapter - } - - return null - } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt index 8caf2ad2..fb20b0b4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/ImageReference.kt @@ -17,14 +17,7 @@ data class ImageReference( val image: String, val config: AtlasConfiguration, ) { - /** - * Вызывает [AtlasConfiguration.Companion.get] автоматически - * - * @see ImageReference - */ - constructor(image: String) : this(image, AtlasConfiguration.get(image)) - - class Adapter(val remapper: AssetPathStack) : TypeAdapter() { + class Adapter(private val remapper: AssetPathStack, private val atlasRegistry: () -> AtlasConfiguration.Registry) : TypeAdapter() { override fun write(out: JsonWriter, value: ImageReference?) { if (value == null) out.nullValue() @@ -42,7 +35,7 @@ data class ImageReference( if (image.contains(':')) throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image") - return ImageReference(image, AtlasConfiguration.get(image)) + return ImageReference(image, atlasRegistry.invoke().get(image)) } throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()} near ${`in`.path}") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt index 4776a215..8a800526 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt @@ -22,7 +22,7 @@ data class SpriteReference( return atlas[resolved] ?: atlas.any() } - class Adapter(val remapper: AssetPathStack) : TypeAdapter() { + class Adapter(private val remapper: AssetPathStack, private val atlasRegistry: () -> AtlasConfiguration.Registry) : TypeAdapter() { override fun write(out: JsonWriter, value: SpriteReference?) { if (value == null) out.nullValue() @@ -40,11 +40,9 @@ data class SpriteReference( return parse(remapper.remap(value)) } - } - companion object { fun parse(input: String): SpriteReference { - val grid = AtlasConfiguration.get(input.substringBefore(':')) + val grid = atlasRegistry.invoke().get(input.substringBefore(':')) return when (input.count { it == ':' }) { 0 -> SpriteReference(input, grid, SBPattern.raw(grid.any().name)) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt index 435dcd9a..a4f2cd70 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IArmorItemDefinition.kt @@ -46,7 +46,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti override val backSleeve: ImageReference? = null, override val frontSleeve: ImageReference? = null, ) : IArmorFrames { - class Factory(private val remapper: AssetPathStack) : TypeAdapterFactory { + class Factory(private val remapper: AssetPathStack, private val atlasRegistry: () -> AtlasConfiguration.Registry) : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == ArmorFrames::class.java) { return object : TypeAdapter() { @@ -70,7 +70,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti if (`in`.peek() == JsonToken.STRING) { val path = remapper.remap(`in`.nextString()) - return ArmorFrames(ImageReference(path, AtlasConfiguration.get(path)), null, null) + return ArmorFrames(ImageReference(path, atlasRegistry.invoke().get(path)), null, null) } return adapter.read(`in`) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt index df70092e..6a6ed981 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IItemDefinition.kt @@ -53,7 +53,7 @@ interface IItemDefinition : IThingWithDescription { data class InventoryIcon( override val image: SpriteReference ) : IInventoryIcon { - class Factory(val remapper: AssetPathStack) : TypeAdapterFactory { + class Factory(val remapper: AssetPathStack, val spriteRegistry: SpriteReference.Adapter) : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == InventoryIcon::class.java) { return object : TypeAdapter() { @@ -71,7 +71,7 @@ interface IItemDefinition : IThingWithDescription { return null if (`in`.peek() == JsonToken.STRING) { - return InventoryIcon(SpriteReference.parse(remapper.remap(`in`.nextString()))) + return InventoryIcon(spriteRegistry.parse(remapper.remap(`in`.nextString()))) } return adapter.read(`in`) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt deleted file mode 100644 index 03ac6e03..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt +++ /dev/null @@ -1,220 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.projectile - -import com.google.common.collect.ImmutableList -import com.google.gson.GsonBuilder -import com.google.gson.JsonObject -import it.unimi.dsi.fastutil.objects.ObjectArraySet -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.* -import ru.dbotthepony.kstarbound.defs.image.ImageReference -import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter -import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter -import ru.dbotthepony.kstarbound.io.json.builder.JsonBuilder -import ru.dbotthepony.kstarbound.registerTypeAdapter -import ru.dbotthepony.kstarbound.util.NotNullVar -import ru.dbotthepony.kvector.vector.Color -import java.util.concurrent.ConcurrentHashMap -import kotlin.properties.Delegates - -@JsonBuilder -class ConfigurableProjectile : RawPrototype() { - var projectileName by Delegates.notNull() - var physics: ProjectilePhysics = ProjectilePhysics.DEFAULT - var damageKindImage: String? = null - var damageType = DamageType.NORMAL - var damageKind: String? = null - - var pointLight: Boolean = false - var animationLoops: Boolean = true - var lightColor: Color? = null - - var onlyHitTerrain: Boolean = false - var orientationLocked: Boolean = false - - var image: String? = null - - var timeToLive: Double = Double.POSITIVE_INFINITY - var animationCycle: Double = Double.POSITIVE_INFINITY - var bounces: Int = -1 - var frameNumber: Int = 1 - - var scripts: ArrayList = ArrayList() - - var hydrophobic: Boolean = false - - // we can't have concrete type here, since final class is commanded by `action` property of each entry - var actionOnReap: ArrayList? = null - - var piercing = false - - var speed = 0.0 - var power = 0.0 - - override fun assemble(directory: String): ConfiguredProjectile { - val actions = ArrayList() - - if (actionOnReap != null) { - for (action in actionOnReap!!) { - val configurable = constructAction(action) - - if (configurable != null) { - actions.add(configurable.configure(directory)) - } - } - } - - if (timeToLive.isInfinite() && animationCycle.isFinite() && !animationLoops) { - timeToLive = animationCycle * (frameNumber - 1) - // LOGGER.warn("{} has no time to live defined, assuming it live as long as its animation plays: {}", projectileName, timeToLive) - } - - check(timeToLive >= 0.0) { "Invalid time to live $timeToLive" } - - return ConfiguredProjectile( - json = enroll(), - projectileName = projectileName, - physics = physics, - damageKindImage = damageKindImage, - damageType = damageType, - damageKind = damageKind, - pointLight = pointLight, - lightColor = lightColor, - onlyHitTerrain = onlyHitTerrain, - orientationLocked = orientationLocked, - image = ImageReference(Starbound.assetFolder(requireNotNull(image) { "image is null" })), - timeToLive = timeToLive, - animationCycle = animationCycle, - bounces = bounces, - frameNumber = frameNumber, - scripts = scripts.toTypedArray(), - actionOnReap = ImmutableList.copyOf(actions), - animationLoops = animationLoops, - hydrophobic = hydrophobic, - piercing = piercing, - speed = speed, - power = power, - ) - } -} - -///////////////////////////////// -// Action on Reap -///////////////////////////////// - -interface IConfigurableAction { - fun configure(directory: String = ""): IActionOnReap -} - -private val MISSING_ACTIONS = ObjectArraySet() -private val LOGGER = LogManager.getLogger() - -/** - * Определяет тип действия и возвращает прототип действия - */ -private fun constructAction(input: JsonObject): IConfigurableAction? { - return when (val elem = (input["action"] ?: throw IllegalArgumentException("Action has no, well, `action` key to specify whatever is it.")).asString) { - "config" -> Starbound.GSON.fromJson(input, ActionConfig::class.java) - "projectile" -> Starbound.GSON.fromJson(input, ActionProjectile::class.java) - "sound" -> Starbound.GSON.fromJson(input, ActionSound::class.java) - "loop" -> Starbound.GSON.fromJson(input, ActionLoop::class.java) - "actions" -> Starbound.GSON.fromJson(input, ActionActions::class.java) - else -> { - if (!MISSING_ACTIONS.contains(elem)) { - MISSING_ACTIONS.add(elem) - LOGGER.error("No projectile action on reap handler is registered for '{}'!", elem) - } - - return null - } - } -} - -/** - * Выполняет действия из указанного файла - * - * По смыслу равен include - */ -class ActionConfig : IConfigurableAction { - lateinit var file: String - - override fun configure(directory: String): IActionOnReap { - return cache.computeIfAbsent(ensureAbsolutePath(file, directory)) { - if (!Starbound.exists(it)) { - LOGGER.error("Config $it does not exist") - return@computeIfAbsent CActionConfig(file, null) - } - - return@computeIfAbsent CActionConfig(file, constructAction(Starbound.loadJson(it) as JsonObject)?.configure()) - } - } - - companion object { - val ADAPTER = BuilderAdapter.Builder(::ActionConfig, ActionConfig::file).ignoreKey("action") - - private val cache = ConcurrentHashMap() - } -} - -/** - * Создает новый прожектайл с заданными параметрами - */ -class ActionProjectile : IConfigurableAction { - var type by NotNullVar() - var angle = 0.0 - var inheritDamageFactor = 1.0 - - override fun configure(directory: String): IActionOnReap { - return CActionProjectile(type, angle, inheritDamageFactor) - } - - companion object { - val ADAPTER = BuilderAdapter.Builder(::ActionProjectile, ActionProjectile::type, ActionProjectile::angle, ActionProjectile::inheritDamageFactor).ignoreKey("action") - } -} - -/** - * Проигрывает звук - */ -class ActionSound : IConfigurableAction { - lateinit var options: Array - - override fun configure(directory: String): IActionOnReap { - return CActionSound(ImmutableList.copyOf(options)) - } - - companion object { - val ADAPTER = BuilderAdapter.Builder(::ActionSound, ActionSound::options).ignoreKey("action") - } -} - -/** - * Выполняет указанные действия в [body] на [count] раз - */ -class ActionLoop : IConfigurableAction { - var count by Delegates.notNull() - var body by Delegates.notNull>() - - override fun configure(directory: String): IActionOnReap { - return CActionLoop(count, ImmutableList.copyOf(body.mapNotNull { constructAction(it)?.configure() })) - } - - companion object { - val ADAPTER = BuilderAdapter.Builder(::ActionLoop, ActionLoop::count, ActionLoop::body).ignoreKey("action") - } -} - -/** - * Выполняет указанные [list] действия - */ -class ActionActions : IConfigurableAction { - var list by Delegates.notNull>() - - override fun configure(directory: String): IActionOnReap { - return CActionActions(ImmutableList.copyOf(list.mapNotNull { constructAction(it)?.configure() })) - } - - companion object { - val ADAPTER = BuilderAdapter.Builder(::ActionActions, ActionActions::list).ignoreKey("action") - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configured.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configured.kt deleted file mode 100644 index 68f745ed..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configured.kt +++ /dev/null @@ -1,129 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.projectile - -import com.google.common.collect.ImmutableMap -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.AssembledPrototype -import ru.dbotthepony.kstarbound.defs.DamageType -import ru.dbotthepony.kstarbound.defs.image.ImageReference -import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController -import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile -import ru.dbotthepony.kvector.vector.Color - -class ConfiguredProjectile( - json: ImmutableMap, - val projectileName: String, - val physics: ProjectilePhysics, - val damageKindImage: String?, - val damageType: DamageType, - val damageKind: String?, - val pointLight: Boolean, - val lightColor: Color?, - val onlyHitTerrain: Boolean, - val orientationLocked: Boolean, - val image: ImageReference, - val timeToLive: Double, - val animationCycle: Double, - val bounces: Int, - val frameNumber: Int, - val scripts: Array, - val actionOnReap: List, - val animationLoops: Boolean, - val hydrophobic: Boolean, - val piercing: Boolean, - val speed: Double, - val power: Double, -) : AssembledPrototype(json) { - override fun disassemble(): ConfigurableProjectile { - TODO("Not yet implemented") - } - - override fun toString(): String { - return "ConfiguredProjectile($projectileName)" - } -} - -interface IActionOnReap { - val name: String - fun execute(projectile: Projectile) -} - -data class CActionConfig( - val file: String, - val delegate: IActionOnReap?, -) : IActionOnReap { - override val name: String = "config" - - override fun execute(projectile: Projectile) { - delegate?.execute(projectile) - } -} - -data class CActionProjectile( - val type: String, - val angle: Double, - val inheritDamageFactor: Double, -) : IActionOnReap { - override val name: String = "projectile" - - override fun execute(projectile: Projectile) { - val def = Starbound.PROJECTILE[type] - - if (def == null) { - LOGGER.error("Tried to create unknown projectile '{}' as result of reap of '{}'!", type, projectile.def.projectileName) - return - } - - val ent = Projectile(projectile.world, def) - ent.position = projectile.position - // ent.angle = projectile.angle - ent.angle = Math.toRadians(angle) - - if (ent.movement is AbstractProjectileMovementController) { - ent.movement.push() - } - - ent.spawn() - } - - companion object { - private val LOGGER = LogManager.getLogger(CActionProjectile::class.java) - } -} - -data class CActionSound( - val options: List -) : IActionOnReap { - override val name: String = "sound" - - override fun execute(projectile: Projectile) { - println("Play sound ${options.random()}!") - } -} - -data class CActionLoop( - val count: Int, - val body: List -) : IActionOnReap { - override val name: String = "loop" - - override fun execute(projectile: Projectile) { - for (i in 0 until count) { - for (action in body) { - action.execute(projectile) - } - } - } -} - -data class CActionActions( - val list: List -) : IActionOnReap { - override val name: String = "actions" - - override fun execute(projectile: Projectile) { - for (action in list) { - action.execute(projectile) - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectilePhysics.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectilePhysics.kt deleted file mode 100644 index 37ccd766..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/ProjectilePhysics.kt +++ /dev/null @@ -1,118 +0,0 @@ -package ru.dbotthepony.kstarbound.defs.projectile - -import com.google.gson.stream.JsonWriter -import ru.dbotthepony.kstarbound.io.json.builder.IStringSerializable - -enum class ProjectilePhysics(vararg aliases: String) : IStringSerializable { - GAS, - LASER, - BOOMERANG, - DEFAULT, - BULLET, - STICKY_BULLET("STICKYBULLET"), - ARROW, - UNDERWATER_ARROW("UNDERWATERARROW"), - UNDERWATER_ARROW_NO_STICKY("UNDERWATERARROWNOSTICKY"), - ROCKET, - GRAVITY_BULLET("GRAVITYBULLET"), - FLAME, - ARROW_NO_STICKY("ARROWNOSTICKY"), - - SQUIRT, - FLYBUG, - ROLLER, - BOWLDER, - SMOOTH_ROLLING_BOULDER("SMOOTHROLLINGBOULDER"), - ROLLING_BOULDER("ROLLINGBOULDER"), - - DRAGON_BONE("DRAGONBONE"), - DRAGON_HEAD("DRAGONHEAD"), - - STICKY, - BOWLING_BALL("BOWLINGBALL"), - PAPER_PLANE("PAPERPLANE"), - BOULDER, - - STATUS_POD("STATUSPOD"), - - // ??? - ILLUSION, - ILLUSION_ROCKET("ROCKETILLUSION"), - - // ????????????? - FRIENDLY_BUBBLE("FRIENDLYBUBBLE"), - - STICKY_HEAVY_GAS("STICKYHEAVYGAS"), - HEAVY_GAS("HEAVYGAS"), - BOUNCY_GAS("BOUNCYGAS"), - FIREBALL, - SLIDER, - GOOP, - HOVER, - - BONE_THORN("BONETHORN"), - - BIG_BUBBLE("BIGBUBBLE"), - FIREWORK_FALL("FIREWORKFALL"), - LIGHTNING_BOLT("LIGHTNINGBOLT"), - SIMPLE_ARC("SIMPLEARC"), - LOW_GRAVITY_ARC("LOWGRAVARC"), - - SPIKE_BALL("SPIKEBALL"), - SHRAPNEL, - - // что - WEATHER, - - FIRE_SPREAD("FIRESPREAD"), - - GRAPPLE_HOOK("GRAPPLEHOOK"), - BALLISTIC_GRAPPLE_HOOK("BALLISTICGRAPPLEHOOK"), - - FLOATY_STICKY_BOMB("FLOATYSTICKYBOMB"), - STICKY_BOMB("STICKYBOMB"), - BOUNCY, - GRAVITY_BOMB("GRAVITYBOMB"), - DISC, - HEAVY_BOUNCER("HEAVYBOUNCER"), - - WALL_STICKY("WALLSTICKY"), - FISHING_LURE_SINKING("FISHINGLURESINKING"), - FISHING_LURE("FISHINGLURE"), - RAIN("RAIN"), - - PET_BALL("PETBALL"), - BOUNCY_BALL("BOUNCYBALL"), - BEACH_BALL("BEACHBALL"), - NOVELTY_BANANA("NOVELTYBANANA"), - - SPACE_MINE("SPACEMINE"), - MECH_BATTERY("MECHBATTERY"), - - GRENADE, - GRENADE_LARGE("LARGEGRENADE"), - GRENADE_Z_BOMB("GRENADEZBOMB"), - GRENADE_STICKY("STICKYGRENADE"), - GRENADE_SUPER_GRAVITY("SUPERHIGHGRAVGRENADE"), - GRENADE_HIGH_GRAVITY_V("VHIGHGRAVGRENADE"), - GRENADE_HIGH_GRAVITY("HIGHGRAVGRENADE"), - GRENADE_LOW_BOUNCE("GRENADELOWBOUNCE"), - GRENADE_NO_BOUNCE("GRENADENOBOUNCE"); - - private val aliases = Array(aliases.size) { aliases[it].lowercase() } - - override fun match(name: String): Boolean { - @Suppress("name_shadowing") - val name = name.lowercase() - - for (alias in aliases) - if (name == alias) - return true - - return name == this.name - } - - override fun write(out: JsonWriter) { - out.value(this.name) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt index 8613bb8f..1cbfa83d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet +import com.google.common.collect.Interner import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException @@ -90,6 +91,8 @@ class BuilderAdapter private constructor( * @see Builder.logMisses */ val logMisses: Boolean, + + val stringInterner: Interner = Interner { it }, ) : TypeAdapter() { private val loggedMisses = ObjectOpenHashSet() @@ -112,7 +115,7 @@ class BuilderAdapter private constructor( reader = JsonTreeReader(json) if (instance is INativeJsonHolder) { - instance.acceptJson(flattenJsonElement(json, Starbound.STRING_INTERNER::intern)) + instance.acceptJson(flattenJsonElement(json, stringInterner::intern)) } else if (instance is IJsonHolder) { instance.acceptJson(json) } @@ -216,6 +219,7 @@ class BuilderAdapter private constructor( var extraPropertiesAreFatal = false var logMisses: Boolean? = null private val factoryReturnType by lazy { factory.invoke()::class.java } + var stringInterner: Interner = Interner { it } override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == factoryReturnType) { @@ -234,6 +238,7 @@ class BuilderAdapter private constructor( return BuilderAdapter( factory = factory, properties = map.build(), + stringInterner = stringInterner, ignoreKeys = ImmutableSet.copyOf(ignoreKeys), extraPropertiesAreFatal = extraPropertiesAreFatal, logMisses = logMisses ?: properties.none { it.isFlat }, @@ -250,6 +255,7 @@ class BuilderAdapter private constructor( return BuilderAdapter( factory = factory, properties = map.build(), + stringInterner = stringInterner, ignoreKeys = ImmutableSet.copyOf(ignoreKeys), extraPropertiesAreFatal = extraPropertiesAreFatal, logMisses = logMisses ?: properties.none { it.isFlat }, @@ -348,7 +354,7 @@ class BuilderAdapter private constructor( } } - companion object : TypeAdapterFactory { + companion object { private val LOGGER = LogManager.getLogger() private val declCache = Reference2ObjectOpenHashMap, ArrayList>>() @@ -386,7 +392,9 @@ class BuilderAdapter private constructor( synchronized(declCache) { declCache.put(input, list) } return list } + } + class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { val raw = type.rawType @@ -400,6 +408,7 @@ class BuilderAdapter private constructor( builder.logMisses = bconfig.realLogMisses builder.extraPropertiesAreFatal = bconfig.extraPropertiesAreFatal + builder.stringInterner = stringInterner for (name in bconfig.ignoreKeys) { builder.ignoreKey(name) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index 6ec515da..eb1bcaee 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap +import com.google.common.collect.Interner import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject @@ -43,6 +44,7 @@ class FactoryAdapter private constructor( val asJsonArray: Boolean, val storesJson: Boolean, val logMisses: Boolean, + val stringInterner: Interner ) : TypeAdapter() { private val name2index = Object2IntArrayMap() private val loggedMisses = ObjectArraySet() @@ -186,7 +188,7 @@ class FactoryAdapter private constructor( } reader = JsonTreeReader(readArray) - readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, Starbound.STRING_INTERNER::intern) + readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, stringInterner::intern) } reader.beginArray() @@ -196,11 +198,7 @@ class FactoryAdapter private constructor( val name = fieldId.toString() if (!storesJson && loggedMisses.add(name)) { - if (currentSymbolicName == null) { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name") - } else { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name (reading: $currentSymbolicName)") - } + LOGGER.warn("${bound.qualifiedName} has no property for storing $name") } reader.skipValue() @@ -235,7 +233,7 @@ class FactoryAdapter private constructor( json = readMap reader = JsonTreeReader(readMap) - readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, Starbound.STRING_INTERNER::intern) + readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, stringInterner::intern) } reader.beginObject() @@ -246,11 +244,7 @@ class FactoryAdapter private constructor( if (fieldId == -1) { if (!storesJson && !hasFlatValues && logMisses && loggedMisses.add(name)) { - if (currentSymbolicName == null) { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name ") - } else { - LOGGER.warn("${bound.qualifiedName} has no property for storing $name (reading: $currentSymbolicName)") - } + LOGGER.warn("${bound.qualifiedName} has no property for storing $name") } reader.skipValue() @@ -386,6 +380,12 @@ class FactoryAdapter private constructor( private var logMisses = true private val types = ArrayList>() private var stringTransformer: ((String) -> T)? = null + var stringInterner: Interner = Interner { it } + + fun stringInterner(interner: Interner): Builder { + this.stringInterner = interner + return this + } fun ifString(transformer: (String) -> T): Builder { stringTransformer = transformer @@ -412,6 +412,7 @@ class FactoryAdapter private constructor( asJsonArray = asList, storesJson = storesJson, logMisses = logMisses, + stringInterner = stringInterner, ).let { if (stringTransformer != null) it.ifString(stringTransformer!!) @@ -433,10 +434,11 @@ class FactoryAdapter private constructor( return FactoryAdapter( bound = clazz, - types = types.stream().map { it.resolve(null) }.collect(ImmutableList.toImmutableList()), + types = ImmutableList.copyOf(types.map { it.resolve(null) }), asJsonArray = asList, storesJson = storesJson, logMisses = logMisses, + stringInterner = stringInterner, ).let { if (stringTransformer != null) it.ifString(stringTransformer!!) @@ -522,11 +524,11 @@ class FactoryAdapter private constructor( } } - companion object : TypeAdapterFactory { + companion object { private val LOGGER = LogManager.getLogger() + } - var currentSymbolicName by ThreadLocal() - + class Factory(val stringInterner: Interner = Interner { it }) : TypeAdapterFactory { override fun create(gson: Gson, type: TypeToken): TypeAdapter? { val raw = type.rawType @@ -545,6 +547,7 @@ class FactoryAdapter private constructor( builder.storesJson(bconfig.storesJson) builder.logMisses(bconfig.logMisses) + builder.stringInterner = stringInterner if (properties.isEmpty()) { throw IllegalArgumentException("${kclass.qualifiedName} has no valid members") @@ -554,14 +557,18 @@ class FactoryAdapter private constructor( if (!bconfig.storesJson) { for (argument in foundConstructor.parameters) { - builder.auto(properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) }) + val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } + val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? + builder.auto(property, isFlat = config?.isFlat ?: false) } } else { val params = foundConstructor.parameters for (i in 0 until params.size - 1) { val argument = params[i] - builder.auto(properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) }) + val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) } + val config = property.annotations.firstOrNull { it.annotationClass == JsonPropertyConfig::class } as JsonPropertyConfig? + builder.auto(property, isFlat = config?.isFlat ?: false) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index b0e6acf6..60c32ae0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -17,7 +17,6 @@ import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.util.Timer import ru.dbotthepony.kstarbound.world.entities.CollisionResolution import ru.dbotthepony.kstarbound.world.entities.Entity -import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController import ru.dbotthepony.kvector.narray.Double2Dimensional import ru.dbotthepony.kvector.narray.Int2Dimensional import ru.dbotthepony.kvector.util2d.AABB @@ -25,7 +24,6 @@ import ru.dbotthepony.kvector.util2d.AABBi import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.nint.Vector2i import java.util.LinkedList -import java.util.function.DoubleBinaryOperator import kotlin.math.PI import kotlin.math.absoluteValue import kotlin.math.cos @@ -162,9 +160,9 @@ abstract class World, ChunkType : Chunk(entity) { - var bounces = 0 - protected set - - override fun beginContact(contact: AbstractContact) { - val dataA = contact.fixtureA.body!!.userData - val dataB = contact.fixtureB.body!!.userData - - if (dataA is Chunk<*, *>.TileLayer || dataB is Chunk<*, *>.TileLayer) { - bounces++ - - if (def.bounces > 0 && bounces >= def.bounces) { - // We can't detonate inside physics simulation - entity.markForDetonation() - } - } else if (dataA is MovementController<*>) { - entity.collideWithEntity(dataA.entity) - } else if (dataB is MovementController<*>) { - entity.collideWithEntity(dataB.entity) - } - } - - override fun endContact(contact: AbstractContact) { - - } - - override fun preSolve(contact: AbstractContact, oldManifold: Manifold) { - - } - - override fun postSolve(contact: AbstractContact, impulse: ContactImpulse) { - - } - - /** - * Applies linear velocity along current facing angle scaled with [ConfiguredProjectile.speed] - */ - open fun push() { - body.linearVelocity += Vector2d.POSITIVE_Y.rotate(body.angle) * def.speed - } - - protected open fun updateAngle() { - body.setTransform(position, body.linearVelocity.normalized.toAngle()) - } - - override fun think(delta: Double) { - super.think(delta) - updateAngle() - } - - companion object { - fun factorize(entity: Projectile, def: ConfiguredProjectile): MovementController<*>? { - return when (def.physics) { - ProjectilePhysics.DEFAULT -> LogicalMovementController(entity) - ProjectilePhysics.BOUNCY -> BouncyPhysics(entity, def) - ProjectilePhysics.FLAME -> FlamePhysics(entity, def) - else -> null - } - } - } -} - -class BouncyPhysics(entity: Projectile, def: ConfiguredProjectile) : AbstractProjectileMovementController(entity, def) { - override fun onSpawnedInWorld() { - super.onSpawnedInWorld() - - body.createFixture(FixtureDef( - shape = PolygonShape().also { it.setAsBox(0.5, 0.2) }, - restitution = 0.9, - friction = 0.7, - density = 2.0, - )) - } -} - -class FlamePhysics(entity: Projectile, def: ConfiguredProjectile) : AbstractProjectileMovementController(entity, def) { - override fun onSpawnedInWorld() { - super.onSpawnedInWorld() - - body.createFixture(FixtureDef( - shape = PolygonShape().also { it.setAsBox(0.2, 0.2) }, - restitution = 0.0, - friction = 1.0, - density = 0.3, - )) - } - - private var touchedGround = false - private var fixedRotation = false - - override fun updateAngle() { - if (!fixedRotation && !touchedGround) - super.updateAngle() - } - - override fun think(delta: Double) { - super.think(delta) - - if (touchedGround && !fixedRotation) { - fixedRotation = true - body.setTransform(body.position, -PI / 2.0) - body.isFixedRotation = true - } - } - - override fun beginContact(contact: AbstractContact) { - super.beginContact(contact) - touchedGround = true - } -} - diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/projectile/Projectile.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/projectile/Projectile.kt deleted file mode 100644 index d73f7baa..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/projectile/Projectile.kt +++ /dev/null @@ -1,55 +0,0 @@ -package ru.dbotthepony.kstarbound.world.entities.projectile - -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.defs.projectile.ConfiguredProjectile -import ru.dbotthepony.kstarbound.world.World -import ru.dbotthepony.kstarbound.world.entities.Entity -import ru.dbotthepony.kstarbound.world.entities.IEntity -import ru.dbotthepony.kstarbound.world.entities.LogicalMovementController -import ru.dbotthepony.kstarbound.world.entities.MovementController - -class Projectile(world: World<*, *>, val def: ConfiguredProjectile) : Entity(world) { - override val movement: MovementController = ( - AbstractProjectileMovementController.factorize(this, def) ?: - LogicalMovementController(this).also { LOGGER.error("No physics controller for ${def.physics}, defaulting to dummy movement controller!") }) as MovementController - - private var timeToLive = def.timeToLive - private var markForDeath = false - - override fun thinkAI(delta: Double) { - timeToLive -= delta - - if (timeToLive <= 0.0 || markForDeath) { - detonate() - } - } - - fun markForDetonation() { - markForDeath = true - } - - fun collideWithEntity(other: IEntity) { - // Can't do anything if we are technically dead - if (markForDeath) - return - - if (!def.piercing) { - markForDeath = true - } - - if (def.damageKind != null) - other.dealDamage(def.power, def.damageKind, def.damageType) - } - - fun detonate() { - for (action in def.actionOnReap) { - action.execute(this) - } - - remove() - } - - companion object { - private val LOGGER = LogManager.getLogger(Projectile::class.java) - } -}