diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 04dbf8aa..fab92644 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -38,6 +38,7 @@ import ru.dbotthepony.kstarbound.defs.item.LiquidItemPrototype import ru.dbotthepony.kstarbound.defs.item.MaterialItemPrototype import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype +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.RenderParameters @@ -59,6 +60,7 @@ import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory 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.WriteOnce import java.io.* import java.text.DateFormat import java.util.* @@ -180,6 +182,16 @@ object Starbound { .also(::addStarboundJsonAdapters) + .registerTypeAdapterFactory(RegistryReferenceFactory() + .add(tiles::get) + .add(tileModifiers::get) + .add(liquid::get) + .add(projectiles::get) + .add(parallax::get) + .add(functions::get) + .add(items::get) + ) + .create() @Suppress("unchecked_cast") @@ -278,6 +290,9 @@ object Starbound { fun getTileDefinition(name: String) = tiles[name] private val initCallbacks = ArrayList<() -> Unit>() + var playerDefinition: PlayerDefinition by WriteOnce() + private set + private fun loadStage( callback: (Boolean, Boolean, String) -> Unit, loader: ((String) -> Unit) -> Unit, @@ -333,6 +348,10 @@ object Starbound { loadStage(callback, this::loadLiquidDefinitions, "liquid definitions") loadStage(callback, this::loadItemDefinitions, "item definitions") + assetFolder = "/" + playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) + assetFolder = null + initializing = false initialized = true callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt index c7387e7e..3c040f13 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/StarboundJsonAdapters.kt @@ -4,21 +4,13 @@ import com.google.gson.GsonBuilder import ru.dbotthepony.kstarbound.defs.DamageType import ru.dbotthepony.kstarbound.defs.JsonFunction import ru.dbotthepony.kstarbound.defs.MaterialReference +import ru.dbotthepony.kstarbound.defs.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.ArmorItemPrototype -import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition -import ru.dbotthepony.kstarbound.defs.item.IFossilItemDefinition import ru.dbotthepony.kstarbound.defs.item.IItemDefinition -import ru.dbotthepony.kstarbound.defs.item.ItemPrototype -import ru.dbotthepony.kstarbound.defs.item.ItemRarity -import ru.dbotthepony.kstarbound.defs.item.ItemTooltipKind 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.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototypeLayer import ru.dbotthepony.kstarbound.defs.projectile.ActionActions @@ -26,23 +18,10 @@ 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.projectile.ConfigurableProjectile -import ru.dbotthepony.kstarbound.defs.projectile.ProjectilePhysics -import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier -import ru.dbotthepony.kstarbound.defs.tile.RenderMatch -import ru.dbotthepony.kstarbound.defs.tile.RenderMatchList -import ru.dbotthepony.kstarbound.defs.tile.RenderParameters -import ru.dbotthepony.kstarbound.defs.tile.RenderPiece -import ru.dbotthepony.kstarbound.defs.tile.RenderRuleList -import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate -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.SkyType -import ru.dbotthepony.kstarbound.defs.world.dungeon.BeamUpRule -import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonType import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.io.ColorTypeAdapter import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter @@ -52,12 +31,12 @@ import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter -import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory -import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.math.PolyTypeAdapter fun addStarboundJsonAdapters(builder: GsonBuilder) { with(builder) { + registerTypeAdapterFactory(SBPattern.Companion) + registerTypeAdapter(ColorTypeAdapter.nullSafe()) // математические классы diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt index 3b32f066..5132bef9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/BoundSprite.kt @@ -46,14 +46,14 @@ class BoundSprite( * Создаёт связку текстуры-атласа + координгат спрайта на ней */ fun SpriteReference.bind(texture: GLTexture2D): BoundSprite { - return BoundSprite(sprite, texture) + return BoundSprite(stateless, texture) } /** * Создаёт связку текстуры-атласа, которая загружается через [GLStateTracker.loadNamedTextureSafe] + координгат спрайта на ней */ fun SpriteReference.bind(state: GLStateTracker): BoundSprite { - return BoundSprite(sprite, state.loadNamedTextureSafe(image)) + return BoundSprite(stateless, state.loadNamedTextureSafe(image)) } /** diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt new file mode 100644 index 00000000..a4ee9fb8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/IScriptable.kt @@ -0,0 +1,13 @@ +package ru.dbotthepony.kstarbound.defs + +interface IScriptable { + /** + * Lua скрипты для выполнения + */ + val scripts: List + + /** + * Через какое количество тиков вызывать обновления скриптов + */ + val scriptDelta: Int +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt new file mode 100644 index 00000000..3f9bad39 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RegistryReference.kt @@ -0,0 +1,74 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.Interners +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap +import java.lang.ref.Reference +import java.lang.ref.ReferenceQueue +import java.lang.ref.WeakReference +import java.lang.reflect.ParameterizedType +import java.util.concurrent.ConcurrentHashMap +import java.util.function.Supplier + +class RegistryReferenceFactory : TypeAdapterFactory { + private val types = Reference2ObjectArrayMap, (String) -> Nothing?>() + private var isLenient = false + + fun lenient(): RegistryReferenceFactory { + isLenient = true + return this + } + + fun add(clazz: Class, resolver: (String) -> T?): RegistryReferenceFactory { + check(types.put(clazz, resolver as (String) -> Nothing?) == null) { "Already has resolver for class $clazz!" } + return this + } + + inline fun add(noinline resolver: (String) -> T?) = add(T::class.java, resolver) + + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == RegistryReference::class.java) { + val ptype = type.type as? ParameterizedType ?: return null + val registryType = ptype.actualTypeArguments[0] + val resolver = types[registryType] ?: return if (isLenient) null else throw NoSuchElementException("Can't deserialize registry reference with type $registryType!") + return RegistryReferenceTypeAdapter(resolver, gson.getAdapter(String::class.java)) as TypeAdapter + } + + return null + } +} + +class RegistryReferenceTypeAdapter(val resolver: (String) -> T?, val strings: TypeAdapter) : TypeAdapter>() { + override fun write(out: JsonWriter, value: RegistryReference) { + strings.write(out, value.name) + } + + override fun read(`in`: JsonReader): RegistryReference { + return RegistryReference(strings.read(`in`) ?: throw JsonSyntaxException("Can't have null as registry name"), resolver) + } +} + +data class RegistryReference(val name: String, val resolver: (String) -> T?) : Supplier, () -> T?, Lazy { + private val lazy = lazy { resolver.invoke(name) } + + override fun get(): T? { + return lazy.value + } + + override val value: T? + get() = lazy.value + + override fun isInitialized(): Boolean { + return lazy.isInitialized() + } + + override fun invoke(): T? { + return lazy.value + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/SBPattern.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/SBPattern.kt new file mode 100644 index 00000000..64259193 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/SBPattern.kt @@ -0,0 +1,150 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +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.JsonWriter +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap + +/** + * Шаблонизировання строка в стиле Starbound'а + * + * Представляет из себя строки вида: + * * `my.thing` + * * `frame_` + * * `` + */ +class SBPattern private constructor( + val raw: String, + val params: ImmutableMap, + val pieces: ImmutableList, + val names: ImmutableSet +) { + val value by lazy { resolve { null } } + val hasNames get() = names.isNotEmpty() + + override fun toString(): String { + return "SBPattern[$raw]" + } + + override fun equals(other: Any?): Boolean { + return other is SBPattern && other.raw == raw && other.names == names && other.pieces == pieces && other.params == params + } + + @Volatile + private var calculatedHash = false + @Volatile + private var hash = 0 + + override fun hashCode(): Int { + if (!calculatedHash) { + hash = raw.hashCode().xor(params.hashCode()).rotateLeft(12).and(pieces.hashCode()).rotateRight(8).xor(names.hashCode()) + calculatedHash = true + } + + return hash + } + + fun resolve(values: (String) -> String?): String? { + val buffer = ArrayList(pieces.size) + + for (piece in pieces) { + buffer.add(piece.resolve(values, params::get) ?: return null) + } + + var count = 0 + for (piece in buffer) count += piece.length + val builder = StringBuilder(count) + for (piece in buffer) builder.append(piece) + return String(builder) + } + + fun resolve(values: Map): String? { + return resolve(values::get) + } + + fun with(params: Map): SBPattern { + val map = Object2ObjectArrayMap() + map.putAll(this.params) + + for ((key, value) in params.entries) + if (names.contains(key)) + map[key] = value + + return SBPattern(raw, ImmutableMap.copyOf(map), pieces, names) + } + + data class Piece(val name: String? = null, val contents: String? = null) { + init { + check(name != null || contents != null) { "Both name and contents are null" } + check(!(name != null && contents != null)) { "Both name and contents are not null" } + } + + fun resolve(map0: (String) -> String?, map1: (String) -> String?): String? { + return contents ?: map0.invoke(name!!) ?: map1.invoke(name!!) + } + + fun resolve(map0: (String) -> String?): String? { + return contents ?: map0.invoke(name!!) + } + + fun resolve(): String? { + return contents + } + } + + companion object : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == SBPattern::class.java) { + return object : TypeAdapter() { + private val strings = gson.getAdapter(String::class.java) + + override fun write(out: JsonWriter, value: SBPattern?) { + strings.write(out, value?.raw) + } + + override fun read(`in`: JsonReader): SBPattern? { + return of(strings.read(`in`) ?: return null) + } + } as TypeAdapter + } + + return null + } + + @JvmStatic + fun of(raw: String): SBPattern { + val pieces = ImmutableList.Builder() + var i = 0 + + while (i < raw.length) { + val open = raw.indexOf('<', startIndex = i) + + if (open == -1) { + pieces.add(Piece(contents = raw.substring(i))) + break + } else { + val closing = raw.indexOf('>', startIndex = open + 1) + + if (closing == -1) { + throw IllegalArgumentException("Malformed pattern string: $raw") + } + + pieces.add(Piece(name = raw.substring(open + 1, closing - 1))) + i = closing + 1 + } + } + + val built = pieces.build() + return SBPattern(raw, pieces = built, params = ImmutableMap.of(), names = built.stream().map { it.name }.filter { it != null }.collect(ImmutableSet.toImmutableSet())) + } + + @JvmStatic + fun raw(raw: String): SBPattern = SBPattern(raw, ImmutableMap.of(), ImmutableList.of(), ImmutableSet.of()) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt new file mode 100644 index 00000000..b03b6b5e --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Species.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class Species( + val kind: String, + val charCreationTooltip: Tooltip, + val nameGen: ImmutableList, + val ouchNoises: OuchNoises, +) { + @JsonFactory + data class Tooltip(val title: String, val subTitle: String, val description: String) + + @JsonFactory(asList = true) + data class OuchNoises(val male: String, val female: String) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt new file mode 100644 index 00000000..de0af09c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/AnimationDefinition.kt @@ -0,0 +1,90 @@ +package ru.dbotthepony.kstarbound.defs.animation + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.defs.image.SpriteReference +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory(asReference = true) +data class AnimationDefinition( + val animatedParts: AnimatedParts, + val sounds: ImmutableMap = ImmutableMap.of(), + val transformationGroups: ImmutableMap = ImmutableMap.of(), +) { + @JsonFactory + data class TransformConfig( + val interpolated: Boolean? = null + ) + + @JsonFactory + data class Sound( + // TODO + val sound: String? = null + ) + + @JsonFactory + data class ParticleEmitter( + val transformationGroups: ImmutableList = ImmutableList.of(), + val particles: ImmutableList + ) + + @JsonFactory + data class AnimatedParts( + val stateTypes: ImmutableMap, + val parts: ImmutableMap, + ) { + @JsonFactory + data class StateType( + val default: String, + val states: ImmutableMap = ImmutableMap.of(), + ) { + @JsonFactory + data class State( + val frames: Int = 0, + val cycle: Double = 0.0, + val mode: Mode? = null, + val transition: TransitionType? = null, + val properties: Properties = Properties() + ) { + enum class TransitionType { + NONE + } + + enum class Mode { + TRANSITION, + LOOP, + } + + @JsonFactory + data class Properties( + val immediateSound: String? = null + ) + } + } + + @JsonFactory + data class Part( + val properties: Properties = Properties.EMPTY, + val partStates: ImmutableMap> = ImmutableMap.of(), + ) { + @JsonFactory + data class Properties( + val fullbright: Boolean? = null, + val centered: Boolean? = null, + val transformationGroups: ImmutableList? = null, + val offset: Vector2d? = null, + val image: SpriteReference? = null + ) { + companion object { + val EMPTY = Properties() + } + } + + @JsonFactory + data class State( + val properties: Properties = Properties.EMPTY + ) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/DestructionAction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/DestructionAction.kt new file mode 100644 index 00000000..a4dcfe8b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/DestructionAction.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.kstarbound.defs.animation + +enum class DestructionAction { + FADE +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleDefinition.kt new file mode 100644 index 00000000..7f439998 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleDefinition.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.kstarbound.defs.animation + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.defs.image.SpriteReference +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.vector.ndouble.Vector2d +import ru.dbotthepony.kvector.vector.ndouble.Vector4d + +@JsonFactory +data class ParticleDefinition( + val count: Int = 1, + val offset: Vector2d = Vector2d.ZERO, + val offsetRegion: Vector4d = Vector4d.ZERO, + val particles: ImmutableList, +) { + @JsonFactory + data class Config( + val type: ParticleType, + val animation: AnimationDefinition? = null, + val image: SpriteReference? = null, + val position: Vector2d = Vector2d.ZERO, + val offsetRegion: Vector4d = Vector4d.ZERO, + val initialVelocity: Vector2d = Vector2d.ZERO, + val finalVelocity: Vector2d = Vector2d.ZERO, + val approach: Vector2d = Vector2d.ZERO, + val angularVelocity: Double = 0.0, + val destructionAction: DestructionAction, + val destructionTime: Double, + val fade: Double = 0.0, + val size: Double = 1.0, + val layer: ParticleLayer, + val timeToLive: Double = 1.0, + val variance: Variance = Variance.EMPTY + ) + + @JsonFactory + data class Variance( + val initialVelocity: Vector2d = Vector2d.ZERO, + val position: Vector2d = Vector2d.ZERO, + val angularVelocity: Double = 0.0 + ) { + companion object { + val EMPTY = Variance() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleLayer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleLayer.kt new file mode 100644 index 00000000..06517379 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleLayer.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.kstarbound.defs.animation + +enum class ParticleLayer { + FRONT +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleType.kt new file mode 100644 index 00000000..9fe40fa9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/animation/ParticleType.kt @@ -0,0 +1,6 @@ +package ru.dbotthepony.kstarbound.defs.animation + +enum class ParticleType { + ANIMATED, + TEXTURED +} \ No newline at end of file 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 455d1277..2e0a7239 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/SpriteReference.kt @@ -1,32 +1,39 @@ package ru.dbotthepony.kstarbound.defs.image -import com.google.gson.GsonBuilder import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.registerTypeAdapter +import ru.dbotthepony.kstarbound.defs.SBPattern /** * Хранит данные (пару) вида "/example/image.png:sprite.name" */ data class SpriteReference( val image: String, - val sprite: AtlasConfiguration.Sprite + val atlas: AtlasConfiguration, + val sprite: SBPattern ) { + val stateless by lazy { resolve { null } } + + fun resolve(with: (String) -> String?): AtlasConfiguration.Sprite { + val resolved = sprite.resolve(with) ?: return atlas.any() + return atlas[resolved] ?: atlas.any() + } + companion object : TypeAdapter() { fun parse(input: String): SpriteReference { val grid = AtlasConfiguration.get(input.substringBefore(':')) return when (input.count { it == ':' }) { - 0 -> SpriteReference(input, grid.any()) - 1 -> SpriteReference(input.substringBefore(':'), grid.get(input.substringAfter(':')) ?: throw NoSuchElementException("No such sprite with name ${input.substringAfter(':')} present in frame grid ${grid.name} (atlas ${input.substringBefore(':')})")) + 0 -> SpriteReference(input, grid, SBPattern.raw(grid.any().name)) + 1 -> SpriteReference(input.substringBefore(':'), grid, SBPattern.of(input.substringAfter(':'))) else -> throw IllegalArgumentException("Invalid sprite reference: $input") } } override fun write(out: JsonWriter, value: SpriteReference) { - out.value(value.image + ":" + value.sprite.name) + out.value(value.image + ":" + value.sprite.raw) } override fun read(`in`: JsonReader): SpriteReference { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IScriptableItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IScriptableItemDefinition.kt index 862fb749..f9c6c0bf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IScriptableItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/IScriptableItemDefinition.kt @@ -1,13 +1,5 @@ package ru.dbotthepony.kstarbound.defs.item -interface IScriptableItemDefinition : IItemDefinition { - /** - * Lua скрипты для выполнения - */ - val scripts: List +import ru.dbotthepony.kstarbound.defs.IScriptable - /** - * Через какое количество тиков вызывать обновления скриптов - */ - val scriptDelta: Int -} +interface IScriptableItemDefinition : IItemDefinition, IScriptable diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/CompanionsConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/CompanionsConfig.kt new file mode 100644 index 00000000..67c27ca9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/CompanionsConfig.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.defs.IScriptable +import ru.dbotthepony.kstarbound.defs.SBPattern +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class CompanionsConfig( + override val scripts: ImmutableList, + override val scriptDelta: Int, + + val activePodLimit: Int, + val activeCrewLimit: Int, + val crewLimit: Int, + + val recruitDescription: SBPattern, + + val crewBenefits: ImmutableMap>, + + val shipUpgradeDiminishingReturns: Double, + + val uniformSlots: ImmutableMap>, +) : IScriptable + diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt new file mode 100644 index 00000000..8909cd96 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/DeploymentConfig.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.defs.IScriptable +import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.defs.item.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class DeploymentConfig( + override val scripts: ImmutableList, + override val scriptDelta: Int, + + val starterMechSet: ImmutableMap>, + val speciesStarterMechBody: ImmutableMap>, + + val enemyDetectRadius: Double, + val enemyDetectTypeNames: ImmutableList, + + val lowEnergyThreshold: Double, + val lowEnergyFlashTime: Double, + val lowEnergySound: String, +) : IScriptable diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryConfig.kt new file mode 100644 index 00000000..e557fa73 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryConfig.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class InventoryConfig( + val customBarGroups: Int, + val customBarIndexes: Int, + + val itemBags: ImmutableMap, +) { + @JsonFactory + data class BagConfig( + val priority: Int, + val size: Int, + ) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryFilterConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryFilterConfig.kt new file mode 100644 index 00000000..815b561b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/InventoryFilterConfig.kt @@ -0,0 +1,22 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class InventoryFilterConfig( + val typeBlacklist: ImmutableSet? = null, + val typeWhitelist: ImmutableSet? = null, + val tagBlacklist: ImmutableSet? = null, + val categoryBlacklist: ImmutableSet? = null, +) { + fun acceptsType(type: String): Boolean { + if (typeBlacklist != null) { + return !typeBlacklist.contains(type) + } else if (typeWhitelist != null) { + return typeWhitelist.contains(type) + } + + return true + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/JumpProfile.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/JumpProfile.kt new file mode 100644 index 00000000..856e064b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/JumpProfile.kt @@ -0,0 +1,10 @@ +package ru.dbotthepony.kstarbound.defs.player + +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class JumpProfile( + val jumpSpeed: Double, + val jumpInitialPercentage: Double, + val jumpHoldTime: Double, +) \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MatterManipulatorConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MatterManipulatorConfig.kt new file mode 100644 index 00000000..8297b36c --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MatterManipulatorConfig.kt @@ -0,0 +1,32 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory +data class MatterManipulatorConfig( + val image: String, + val endImages: ImmutableList = ImmutableList.of(), + + val handPosition: Vector2d, + val firePosition: Vector2d, + + val segmentsPerUnit: Int, + val nearControlPointElasticity: Double, + val farControlPointElasticity: Double, + val nearControlPointDistance: Double, + val targetSegmentRun: Int, + val innerBrightnessScale: Double, + val firstStripeThickness: Double, + val secondStripeThickness: Double, + + val minBeamWidth: Double, + val maxBeamWidth: Double, + val maxBeamJitter: Double, + val minBeamJitter: Double, + val minBeamTrans: Double, + val maxBeamTrans: Double, + val minBeamLines: Double, + val maxBeamLines: Double, +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MovementParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MovementParameters.kt new file mode 100644 index 00000000..dd51ecf0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/MovementParameters.kt @@ -0,0 +1,11 @@ +package ru.dbotthepony.kstarbound.defs.player + +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +data class MovementParameters( + val flySpeed: Double? = null, + val airFriction: Double? = null, + val airJumpProfile: JumpProfile? = null, + val airForce: Double? = null, +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt new file mode 100644 index 00000000..b371d817 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/PlayerDefinition.kt @@ -0,0 +1,78 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +import com.google.gson.JsonObject +import ru.dbotthepony.kstarbound.defs.RegistryReference +import ru.dbotthepony.kstarbound.defs.SBPattern +import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition +import ru.dbotthepony.kstarbound.defs.item.IItemDefinition +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.util2d.AABB +import ru.dbotthepony.kvector.vector.Color +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory +data class PlayerDefinition( + val defaultHumanoidIdentity: JsonObject, + val blueprintUnlock: SBPattern, + val blueprintAlreadyKnown: SBPattern, + val collectableUnlock: SBPattern, + + val species: ImmutableSet, + val nametagColor: Color, + val ageItemsEvery: Int, + val defaultItems: ImmutableSet>, + + val defaultBlueprints: ImmutableMap>, + + val defaultCodexes: ImmutableMap>>, + val metaBoundBox: AABB, + val movementParameters: MovementParameters, + val zeroGMovementParameters: MovementParameters, + + val statusControllerSettings: StatusControllerSettings, + + val foodLowThreshold: Double, + val foodLowStatusEffects: ImmutableList = ImmutableList.of(), + val foodEmptyStatusEffects: ImmutableList = ImmutableList.of(), + val inCinematicStatusEffects: ImmutableList = ImmutableList.of(), + + val footstepTiming: Double, + val footstepSensor: Vector2d, + + val vaporTrailTime: Double, + val terminalVelocityDifference: Double, + + val initialBeamGunRadius: Double, + val previewGlowBorder: Double, + val objectPreviewInnerAlpha: Double, + val objectPreviewOuterAlpha: Double, + + val beamGunConfig: MatterManipulatorConfig, + + val underwaterSensor: Vector2d = Vector2d(0.0, 0.0), + val underwaterMinWaterLevel: Double, + + val splashConfig: SplashConfig, + + val effectsAnimator: AnimationDefinition, + + val teleportInTime: Double, + val teleportOutTime: Double, + val deployInTime: Double, + val deployOutTime: Double, + + val teleportInStatusEffects: ImmutableList, + + val companionsConfig: CompanionsConfig, + val deploymentConfig: DeploymentConfig, + + val genericScriptContexts: ImmutableMap, + val inventory: InventoryConfig, + val inventoryFilters: ImmutableMap, +) { + @JsonFactory + data class BlueprintUnlock(val item: RegistryReference) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/SplashConfig.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/SplashConfig.kt new file mode 100644 index 00000000..fa66ce20 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/SplashConfig.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.kstarbound.defs.player + +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.vector.Color +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory +data class SplashConfig( + val splashSpeedMin: Double, + val splashMinWaterLevel: Double, + val splashBottomSensor: Vector2d, + val splashTopSensor: Vector2d, + val numSplashParticles: Int, + val splashYVelocityFactor: Double, + val splashParticle: Particle, + val splashParticleVariance: Variance, +) { + @JsonFactory + data class Particle( + val type: String, + val position: Vector2d, + val finalVelocity: Vector2d, + val approach: Vector2d, + val color: Color, + val size: Double, + val timeToLive: Double, + val destructionAction: String, + val destructionTime: Double, + ) + + @JsonFactory + data class Variance( + val velocity: Vector2d, + val size: Double, + ) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/StatusControllerSettings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/StatusControllerSettings.kt new file mode 100644 index 00000000..eea1a1c0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/StatusControllerSettings.kt @@ -0,0 +1,48 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +@JsonFactory +data class StatusControllerSettings( + val statusProperties: Properties, + val appliesEnvironmentStatusEffects: Boolean = true, + val appliesWeatherStatusEffects: Boolean = true, + val minimumLiquidStatusEffectPercentage: Double = 0.1, + + val primaryScriptSources: ImmutableList = ImmutableList.of(), + + val primaryScriptDelta: Int, + + val stats: ImmutableMap = ImmutableMap.of(), + val resources: ImmutableMap = ImmutableMap.of(), +) { + @JsonFactory + data class Properties( + val targetMaterialKind: String, + val mouthPosition: Vector2d, + val breathHealthPenaltyPercentageRate: Double, + val hitInvulnerabilityThreshold: Double, + val hitInvulnerabilityTime: Double, + val hitInvulnerabilityFlash: Double, + val shieldHitInvulnerabilityTime: Double, + val damageFlashOnDirectives: String = "", + val damageFlashOffDirectives: String = "" + ) + + @JsonFactory + data class Stat( + val baseValue: Double + ) + + @JsonFactory + data class Resource( + val maxStat: String? = null, + val deltaStat: String? = null, + val deltaValue: Double? = null, + val initialPercentage: Double? = null, + val initialValue: Double? = null, + ) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/AABBJson.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/AABBJson.kt index 45ee24e5..2bebaa87 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/AABBJson.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/AABBJson.kt @@ -11,14 +11,18 @@ import ru.dbotthepony.kvector.vector.nint.Vector2i object AABBTypeAdapter : TypeAdapter() { override fun write(out: JsonWriter, value: AABB) { `out`.beginArray() - Vector2dTypeAdapter.write(out, value.mins) - Vector2dTypeAdapter.write(out, value.maxs) + `out`.value(value.mins.x) + `out`.value(value.mins.y) + `out`.value(value.maxs.x) + `out`.value(value.maxs.y) `out`.endArray() } override fun read(`in`: JsonReader): AABB { - val (x1, x2) = Vector2dTypeAdapter.read(`in`) - val (y1, y2) = Vector2dTypeAdapter.read(`in`) + `in`.beginArray() + val (x1, x2) = Vector2d(`in`.nextDouble(), `in`.nextDouble()) + val (y1, y2) = Vector2d(`in`.nextDouble(), `in`.nextDouble()) + `in`.endArray() val xMins = x1.coerceAtMost(x2) val xMaxs = x1.coerceAtLeast(x2) @@ -36,14 +40,18 @@ object AABBTypeAdapter : TypeAdapter() { object AABBiTypeAdapter : TypeAdapter() { override fun write(out: JsonWriter, value: AABBi) { `out`.beginArray() - Vector2iTypeAdapter.write(out, value.mins) - Vector2iTypeAdapter.write(out, value.maxs) + `out`.value(value.mins.x) + `out`.value(value.mins.y) + `out`.value(value.maxs.x) + `out`.value(value.maxs.y) `out`.endArray() } override fun read(`in`: JsonReader): AABBi { - val (x1, x2) = Vector2iTypeAdapter.read(`in`) - val (y1, y2) = Vector2iTypeAdapter.read(`in`) + `in`.beginArray() + val (x1, x2) = Vector2i(`in`.nextInt(), `in`.nextInt()) + val (y1, y2) = Vector2i(`in`.nextInt(), `in`.nextInt()) + `in`.endArray() val xMins = x1.coerceAtMost(x2) val xMaxs = x1.coerceAtLeast(x2) 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 20fb2fb0..7246fb4b 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 @@ -223,7 +223,7 @@ class FactoryAdapter private constructor( readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${bound.qualifiedName}", err) } fieldId++ @@ -275,7 +275,7 @@ class FactoryAdapter private constructor( readValues[fieldId] = adapter.read(reader) presentValues[fieldId] = true } catch(err: Throwable) { - throw JsonSyntaxException("Reading field ${field.name} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${bound.qualifiedName}", err) } } } @@ -290,7 +290,7 @@ class FactoryAdapter private constructor( readValues[i] = read } } catch(err: Throwable) { - throw JsonSyntaxException("Reading flat field ${property.property.name} for ${bound.qualifiedName}", err) + throw JsonSyntaxException("Reading flat field \"${property.property.name}\" near ${reader.path} for ${bound.qualifiedName}", err) } } } @@ -372,7 +372,7 @@ class FactoryAdapter private constructor( continue } - throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls") + throw JsonSyntaxException("Field \"${field.name}\" of ${bound.qualifiedName} does not accept nulls near ${reader.path}") } return syntheticFactory.newInstance(*copied) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/FullJsonPath.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/FullJsonPath.kt deleted file mode 100644 index bd93a1af..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/FullJsonPath.kt +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -class FullJsonPath(val fullPath: String) { - init { - val delimers = fullPath.count { it == ':' } - require(delimers < 2) { "Invalid path: $fullPath" } - } - - val path = fullPath.substringBefore(':') - val subpath: JsonPath - - init { - if (path.any { it == ':' }) { - subpath = JsonPath(fullPath.substringAfter(':')) - } else { - subpath = JsonPath.EMPTY - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/JsonPath.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/JsonPath.kt deleted file mode 100644 index c1be930a..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/JsonPath.kt +++ /dev/null @@ -1,96 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import com.google.common.collect.ImmutableList -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject - -class JsonPath(val path: String) { - private class Piece(private val value: String) { - private val asNumber = value.toIntOrNull() - - fun navigate(element: JsonElement): JsonElement? { - if (element is JsonObject) { - return element[value] - } - - if (element is JsonArray) { - if (asNumber == null) { - return null - } - - return element[asNumber] - } - - return null - } - - fun navigate(element: Any): Any? { - if (element is List<*>) { - if (asNumber == null) { - return null - } - - return element.getOrNull(asNumber) - } - - if (element is Map<*, *>) { - return element[value] - } - - return null - } - } - - val parts: List = ImmutableList.copyOf(path.split('.')) - private val pieces: List = ImmutableList.copyOf(parts.map { Piece(it) }) - - fun navigate(element: JsonElement): JsonElement? { - var current: JsonElement? = element - - for (piece in pieces) { - if (current == null) - return null - - current = piece.navigate(current) - } - - return current - } - - fun navigate(element: List): Any? { - var current: Any? = element - - for (piece in pieces) { - if (current == null) - return null - - current = piece.navigate(current) - } - - return current - } - - fun navigate(element: Map): Any? { - var current: Any? = element - - for (piece in pieces) { - if (current == null) - return null - - current = piece.navigate(current) - } - - return current - } - - val isEmpty: Boolean - get() = path.isEmpty() - - val isBlank: Boolean - get() = path.isBlank() - - companion object { - val EMPTY = JsonPath("") - } -}