Bare minimum for object loading
This commit is contained in:
parent
84e9fd842a
commit
57c32beb0d
31
CHANGES.md
Normal file
31
CHANGES.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
## Differences between original game engine and KStarbound
|
||||||
|
|
||||||
|
Despite these two pieces of software try to achieve the same
|
||||||
|
goal of providing environment for mods and their content (including base game,
|
||||||
|
which is technically a mod), they have different ways of doing so.
|
||||||
|
|
||||||
|
While it is no secret that KStarbound contains bits of original code,
|
||||||
|
whenever be it runtime constants, or json deserialization structures,
|
||||||
|
they are never copied directly. This file covers most notable differences
|
||||||
|
between engines which end-users will see.
|
||||||
|
|
||||||
|
### Technical differences
|
||||||
|
|
||||||
|
* Lighting engine is based off original code, but is heavily modified, such as:
|
||||||
|
* Before spreading point lights, potential rectangle is determined, to reduce required calculations
|
||||||
|
* Lights are clasterized, and clusters are processed together, **on different threads** (multithreading)
|
||||||
|
* Point lights are being spread along **both diagonals**, not only along left-right bottom-top diagonal (can be adjusted using "light quality" setting)
|
||||||
|
* While overall performance is marginally better than original game, and scales up to any number of cores, efficiency of spreading algorithm is worse than original
|
||||||
|
* Chunk rendering is split into render regions, which size can be adjusted in settings
|
||||||
|
* Increasing render region size will decrease CPU load when rendering world and increase GPU utilization efficiency, while hurting CPU performance on chunk updates, and vice versa
|
||||||
|
* Render region size themselves align with world borders, so 3000x2000 world would have 30x25 sized render regions
|
||||||
|
|
||||||
|
### Modding differences
|
||||||
|
* Generally, object orientation and parameters can override more properties on fly
|
||||||
|
* Space scan of sprite on atlas is not supported yet (original engine does not support this)
|
||||||
|
* While original game engine is quite lenient about what it can load, KStarbound aims to be more strict about inputs. This implies KStarbound validates input much more than original engine, while also giving more clear hints at whats wrong with prototypes
|
||||||
|
|
||||||
|
### Modding API changes
|
||||||
|
* Objects
|
||||||
|
* `damageTable` can be defined directly, without referencing other JSON file
|
17
NYI.md
Normal file
17
NYI.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
## Not Yet Implemented
|
||||||
|
|
||||||
|
* Objects
|
||||||
|
* BTreeDB writer
|
||||||
|
* Plants
|
||||||
|
* Characters (Avatars)
|
||||||
|
* Collision code (while collision detection technically is present, it does not follow Starbound one)
|
||||||
|
* Entities
|
||||||
|
* Spotlights
|
||||||
|
|
||||||
|
## Implemented
|
||||||
|
* BTreeDB reader
|
||||||
|
* Circular world geometry
|
||||||
|
* Infinite world geometry (**exclusive to KStarbound**)
|
||||||
|
* Chunk geometry renderer
|
||||||
|
* Point lights
|
@ -36,8 +36,8 @@ tasks.compileKotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.10")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.10")
|
||||||
|
|
||||||
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
||||||
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
||||||
@ -82,7 +82,7 @@ dependencies {
|
|||||||
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
||||||
|
|
||||||
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
|
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
|
||||||
implementation("ru.dbotthepony:kvector:2.4.0")
|
implementation("ru.dbotthepony:kvector:2.5.0")
|
||||||
|
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,34 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import java.util.Arrays
|
import java.util.Arrays
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.full.createType
|
||||||
|
|
||||||
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
|
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
|
||||||
return registerTypeAdapter(T::class.java, adapter)
|
return registerTypeAdapter(T::class.java, adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> GsonBuilder.registerTypeAdapter(noinline factory: (Gson) -> TypeAdapter<T>): GsonBuilder {
|
||||||
|
val token = TypeToken.get(T::class.java)
|
||||||
|
|
||||||
|
return registerTypeAdapterFactory(object : TypeAdapterFactory {
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type == token) {
|
||||||
|
return factory(gson) as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
|
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
|
||||||
|
|
||||||
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T? {
|
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T? {
|
||||||
|
@ -20,6 +20,7 @@ import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader
|
|||||||
import ru.dbotthepony.kstarbound.io.json.VersionedJson
|
import ru.dbotthepony.kstarbound.io.json.VersionedJson
|
||||||
import ru.dbotthepony.kstarbound.io.readString
|
import ru.dbotthepony.kstarbound.io.readString
|
||||||
import ru.dbotthepony.kstarbound.io.readVarInt
|
import ru.dbotthepony.kstarbound.io.readVarInt
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
@ -70,33 +71,21 @@ fun main() {
|
|||||||
val ent = PlayerEntity(client.world!!)
|
val ent = PlayerEntity(client.world!!)
|
||||||
|
|
||||||
Starbound.onInitialize {
|
Starbound.onInitialize {
|
||||||
var find = 0L
|
|
||||||
var set = 0L
|
|
||||||
var parse = 0L
|
|
||||||
|
|
||||||
//for (chunkX in 17 .. 18) {
|
//for (chunkX in 17 .. 18) {
|
||||||
//for (chunkX in 14 .. 24) {
|
//for (chunkX in 14 .. 24) {
|
||||||
for (chunkX in 0 .. 100) {
|
for (chunkX in 0 .. 100) {
|
||||||
// for (chunkY in 21 .. 21) {
|
// for (chunkY in 21 .. 21) {
|
||||||
for (chunkY in 18 .. 24) {
|
for (chunkY in 18 .. 24) {
|
||||||
var t = System.currentTimeMillis()
|
|
||||||
val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte()))
|
val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte()))
|
||||||
val data2 = db.read(byteArrayOf(2, 0, chunkX.toByte(), 0, chunkY.toByte()))
|
val data2 = db.read(byteArrayOf(2, 0, chunkX.toByte(), 0, chunkY.toByte()))
|
||||||
find += System.currentTimeMillis() - t
|
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
t = System.currentTimeMillis()
|
var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
|
||||||
val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
|
|
||||||
parse += System.currentTimeMillis() - t
|
|
||||||
|
|
||||||
reader.skipBytes(3)
|
reader.skipBytes(3)
|
||||||
|
|
||||||
t = System.currentTimeMillis()
|
|
||||||
|
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 31) {
|
||||||
for (x in 0 .. 31) {
|
for (x in 0 .. 31) {
|
||||||
val cell = client.world!!.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
|
val cell = client.world!!.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
|
||||||
|
|
||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
IChunkCell.skip(reader)
|
IChunkCell.skip(reader)
|
||||||
} else {
|
} else {
|
||||||
@ -104,8 +93,6 @@ fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set += System.currentTimeMillis() - t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data2 != null) {
|
if (data2 != null) {
|
||||||
@ -132,8 +119,6 @@ fun main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("$find $set $parse")
|
|
||||||
|
|
||||||
//client.world!!.parallax = Starbound.parallaxAccess["garden"]
|
//client.world!!.parallax = Starbound.parallaxAccess["garden"]
|
||||||
|
|
||||||
val item = Starbound.items.values.random()
|
val item = Starbound.items.values.random()
|
||||||
@ -152,9 +137,9 @@ fun main() {
|
|||||||
|
|
||||||
// println(Starbound.statusEffects["firecharge"])
|
// println(Starbound.statusEffects["firecharge"])
|
||||||
|
|
||||||
Starbound.pathStack.push("/animations/dust4")
|
AssetPathStack.push("/animations/dust4")
|
||||||
val def = Starbound.gson.fromJson(Starbound.locate("/animations/dust4/dust4.animation").reader(), AnimationDefinition::class.java)
|
val def = Starbound.gson.fromJson(Starbound.locate("/animations/dust4/dust4.animation").reader(), AnimationDefinition::class.java)
|
||||||
Starbound.pathStack.pop()
|
AssetPathStack.pop()
|
||||||
|
|
||||||
val animator = Animator(client.world!!, def)
|
val animator = Animator(client.world!!, def)
|
||||||
|
|
||||||
|
@ -9,17 +9,11 @@ import com.google.gson.internal.bind.JsonTreeReader
|
|||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
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.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 org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaState
|
import ru.dbotthepony.kstarbound.lua.LuaState
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.set
|
import ru.dbotthepony.kstarbound.util.set
|
||||||
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -140,10 +134,10 @@ class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: (
|
|||||||
intObjects.clear()
|
intObjects.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(gson: Gson, file: IStarboundFile, pathStack: PathStack): Boolean {
|
fun add(file: IStarboundFile): Boolean {
|
||||||
return pathStack(file.computeDirectory()) {
|
return AssetPathStack(file.computeDirectory()) {
|
||||||
val elem = gson.fromJson(file.reader(), JsonElement::class.java)
|
val elem = Starbound.gson.fromJson(file.reader(), JsonElement::class.java)
|
||||||
val value = gson.fromJson<T>(JsonTreeReader(elem), clazz.java)
|
val value = Starbound.gson.fromJson<T>(JsonTreeReader(elem), clazz.java)
|
||||||
add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import ru.dbotthepony.kstarbound.api.IStarboundFile
|
|||||||
import ru.dbotthepony.kstarbound.api.NonExistingFile
|
import ru.dbotthepony.kstarbound.api.NonExistingFile
|
||||||
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
import ru.dbotthepony.kstarbound.api.PhysicalFile
|
||||||
import ru.dbotthepony.kstarbound.defs.*
|
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.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition
|
import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition
|
||||||
@ -38,6 +37,7 @@ import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
|
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition
|
import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition
|
import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
|
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
|
||||||
@ -46,6 +46,7 @@ import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.player.TechDefinition
|
import ru.dbotthepony.kstarbound.defs.player.TechDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
|
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.util.JsonArrayCollector
|
import ru.dbotthepony.kstarbound.util.JsonArrayCollector
|
||||||
@ -54,10 +55,12 @@ import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter
|
|||||||
import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.ColorTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.ColorTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.FastutilTypeAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter
|
import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.LongRangeAdapter
|
import ru.dbotthepony.kstarbound.io.json.LongRangeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.NothingAdapter
|
import ru.dbotthepony.kstarbound.io.json.NothingAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.OneOfTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
|
||||||
@ -69,13 +72,14 @@ import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
|||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.factory.PairAdapterFactory
|
||||||
import ru.dbotthepony.kstarbound.lua.LuaState
|
import ru.dbotthepony.kstarbound.lua.LuaState
|
||||||
import ru.dbotthepony.kstarbound.lua.loadInternalScript
|
import ru.dbotthepony.kstarbound.lua.loadInternalScript
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.util.ITimeSource
|
import ru.dbotthepony.kstarbound.util.ITimeSource
|
||||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
import ru.dbotthepony.kstarbound.util.WriteOnce
|
import ru.dbotthepony.kstarbound.util.WriteOnce
|
||||||
import ru.dbotthepony.kstarbound.util.filterNotNull
|
import ru.dbotthepony.kstarbound.util.filterNotNull
|
||||||
@ -105,8 +109,6 @@ object Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
private val logger = LogManager.getLogger()
|
private val logger = LogManager.getLogger()
|
||||||
|
|
||||||
val pathStack = PathStack(strings)
|
|
||||||
|
|
||||||
private val _tiles = ObjectRegistry("tiles", TileDefinition::materialName, TileDefinition::materialId)
|
private val _tiles = ObjectRegistry("tiles", TileDefinition::materialName, TileDefinition::materialId)
|
||||||
val tiles = _tiles.view
|
val tiles = _tiles.view
|
||||||
val tilesByID = _tiles.intView
|
val tilesByID = _tiles.intView
|
||||||
@ -188,6 +190,9 @@ object Starbound : ISBFileLocator {
|
|||||||
// ImmutableList, ImmutableSet, ImmutableMap
|
// ImmutableList, ImmutableSet, ImmutableMap
|
||||||
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(strings))
|
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(strings))
|
||||||
|
|
||||||
|
// fastutil collections
|
||||||
|
registerTypeAdapterFactory(FastutilTypeAdapterFactory(strings))
|
||||||
|
|
||||||
// ArrayList
|
// ArrayList
|
||||||
registerTypeAdapterFactory(ArrayListAdapterFactory)
|
registerTypeAdapterFactory(ArrayListAdapterFactory)
|
||||||
|
|
||||||
@ -202,13 +207,25 @@ object Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
// Either<>
|
// Either<>
|
||||||
registerTypeAdapterFactory(EitherTypeAdapter)
|
registerTypeAdapterFactory(EitherTypeAdapter)
|
||||||
|
// OneOf<>
|
||||||
|
registerTypeAdapterFactory(OneOfTypeAdapter)
|
||||||
|
|
||||||
|
// Pair<>
|
||||||
|
registerTypeAdapterFactory(PairAdapterFactory)
|
||||||
registerTypeAdapterFactory(SBPattern.Companion)
|
registerTypeAdapterFactory(SBPattern.Companion)
|
||||||
|
|
||||||
|
registerTypeAdapterFactory(JsonReference.Companion)
|
||||||
|
|
||||||
registerTypeAdapter(ColorReplacements.Companion)
|
registerTypeAdapter(ColorReplacements.Companion)
|
||||||
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
||||||
|
|
||||||
registerTypeAdapter(ColorTypeAdapter.nullSafe())
|
registerTypeAdapter(ColorTypeAdapter.nullSafe())
|
||||||
|
|
||||||
|
registerTypeAdapter(Drawable::Adapter)
|
||||||
|
registerTypeAdapter(ObjectOrientation::Adapter)
|
||||||
|
registerTypeAdapter(ObjectDefinition::Adapter)
|
||||||
|
registerTypeAdapter(StatModifier::Adapter)
|
||||||
|
|
||||||
// математические классы
|
// математические классы
|
||||||
registerTypeAdapter(AABBTypeAdapter)
|
registerTypeAdapter(AABBTypeAdapter)
|
||||||
registerTypeAdapter(AABBiTypeAdapter)
|
registerTypeAdapter(AABBiTypeAdapter)
|
||||||
@ -218,6 +235,7 @@ object Starbound : ISBFileLocator {
|
|||||||
registerTypeAdapter(Vector4iTypeAdapter)
|
registerTypeAdapter(Vector4iTypeAdapter)
|
||||||
registerTypeAdapter(Vector4dTypeAdapter)
|
registerTypeAdapter(Vector4dTypeAdapter)
|
||||||
registerTypeAdapter(PolyTypeAdapter)
|
registerTypeAdapter(PolyTypeAdapter)
|
||||||
|
registerTypeAdapter(LineF::Adapter)
|
||||||
|
|
||||||
// Функции
|
// Функции
|
||||||
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
||||||
@ -230,11 +248,11 @@ object Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
|
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
|
||||||
|
|
||||||
registerTypeAdapterFactory(InventoryIcon.Factory(pathStack))
|
registerTypeAdapter(InventoryIcon.Companion)
|
||||||
|
|
||||||
registerTypeAdapterFactory(IArmorItemDefinition.Frames.Factory)
|
registerTypeAdapterFactory(IArmorItemDefinition.Frames.Factory)
|
||||||
registerTypeAdapterFactory(AssetPath.Companion)
|
registerTypeAdapterFactory(AssetPath.Companion)
|
||||||
registerTypeAdapterFactory(ImageReference.Factory({ atlasRegistry.get(it) }, pathStack))
|
registerTypeAdapter(ImageReference.Companion)
|
||||||
|
|
||||||
registerTypeAdapterFactory(AssetReference.Companion)
|
registerTypeAdapterFactory(AssetReference.Companion)
|
||||||
|
|
||||||
@ -269,7 +287,13 @@ object Starbound : ISBFileLocator {
|
|||||||
create()
|
create()
|
||||||
}
|
}
|
||||||
|
|
||||||
val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson)
|
init {
|
||||||
|
val f = NonExistingFile("/metamaterials.config")
|
||||||
|
|
||||||
|
for (material in BuiltinMetaMaterials.MATERIALS) {
|
||||||
|
_tiles.add(material, JsonNull.INSTANCE, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val imageCache: Cache<String, ImageData> = Caffeine.newBuilder()
|
private val imageCache: Cache<String, ImageData> = Caffeine.newBuilder()
|
||||||
.softValues()
|
.softValues()
|
||||||
@ -369,7 +393,7 @@ object Starbound : ISBFileLocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!file.isFile) {
|
if (!file.isFile) {
|
||||||
throw IllegalStateException("File $file is a directory")
|
throw FileNotFoundException("File $file is a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
val getWidth = intArrayOf(0)
|
val getWidth = intArrayOf(0)
|
||||||
@ -935,7 +959,7 @@ object Starbound : ISBFileLocator {
|
|||||||
for (listedFile in files) {
|
for (listedFile in files) {
|
||||||
try {
|
try {
|
||||||
it("Loading $listedFile")
|
it("Loading $listedFile")
|
||||||
registry.add(gson, listedFile, pathStack)
|
registry.add(listedFile)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
logger.error("Loading ${registry.name} definition file $listedFile", err)
|
logger.error("Loading ${registry.name} definition file $listedFile", err)
|
||||||
}
|
}
|
||||||
@ -1023,7 +1047,7 @@ object Starbound : ISBFileLocator {
|
|||||||
loadStage(callback, _monsterSkills, ext2files["monsterskill"] ?: listOf())
|
loadStage(callback, _monsterSkills, ext2files["monsterskill"] ?: listOf())
|
||||||
//loadStage(callback, _monsterTypes, ext2files["monstertype"] ?: listOf())
|
//loadStage(callback, _monsterTypes, ext2files["monstertype"] ?: listOf())
|
||||||
|
|
||||||
pathStack.block("/") {
|
AssetPathStack.block("/") {
|
||||||
//playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
|
//playerDefinition = gson.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1087,7 +1111,7 @@ object Starbound : ISBFileLocator {
|
|||||||
try {
|
try {
|
||||||
callback("Loading $listedFile")
|
callback("Loading $listedFile")
|
||||||
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
val json = gson.fromJson(listedFile.reader(), JsonObject::class.java)
|
||||||
val def: IItemDefinition = pathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), clazz) }
|
val def: IItemDefinition = AssetPathStack(listedFile.computeDirectory()) { gson.fromJson(JsonTreeReader(json), clazz) }
|
||||||
_items.add(def, json, listedFile)
|
_items.add(def, json, listedFile)
|
||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
logger.error("Loading item definition file $listedFile", err)
|
logger.error("Loading item definition file $listedFile", err)
|
||||||
|
@ -297,20 +297,12 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
var fullbright = true
|
var fullbright = true
|
||||||
|
|
||||||
init {
|
|
||||||
//viewportLightingTexture.textureMinFilter = GL_NEAREST
|
|
||||||
viewportLightingTexture.textureMagFilter = GL_LINEAR
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateViewportParams() {
|
fun updateViewportParams() {
|
||||||
viewportRectangle = AABB.rectangle(
|
viewportRectangle = AABB.rectangle(
|
||||||
camera.pos.toDoubleVector(),
|
camera.pos.toDoubleVector(),
|
||||||
viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT,
|
viewportWidth / settings.zoom / PIXELS_IN_STARBOUND_UNIT,
|
||||||
viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)
|
viewportHeight / settings.zoom / PIXELS_IN_STARBOUND_UNIT)
|
||||||
|
|
||||||
val oldWidth = viewportCellWidth
|
|
||||||
val oldHeight = viewportCellHeight
|
|
||||||
|
|
||||||
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 4
|
viewportCellX = roundTowardsNegativeInfinity(viewportRectangle.mins.x) - 4
|
||||||
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 4
|
viewportCellY = roundTowardsNegativeInfinity(viewportRectangle.mins.y) - 4
|
||||||
viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 8
|
viewportCellWidth = roundTowardsPositiveInfinity(viewportRectangle.width) + 8
|
||||||
@ -319,14 +311,13 @@ class StarboundClient : Closeable {
|
|||||||
if (viewportLighting.width != viewportCellWidth || viewportLighting.height != viewportCellHeight) {
|
if (viewportLighting.width != viewportCellWidth || viewportLighting.height != viewportCellHeight) {
|
||||||
viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
viewportLighting = LightCalculator(viewportCells, viewportCellWidth, viewportCellHeight)
|
||||||
viewportLighting.multithreaded = true
|
viewportLighting.multithreaded = true
|
||||||
}
|
|
||||||
|
|
||||||
if (oldWidth != viewportCellWidth && oldHeight != viewportCellHeight)
|
|
||||||
if (viewportCellWidth > 0 && viewportCellHeight > 0) {
|
if (viewportCellWidth > 0 && viewportCellHeight > 0) {
|
||||||
viewportLightingMem = ByteBuffer.allocateDirect(viewportCellWidth.coerceAtMost(4096) * viewportCellHeight.coerceAtMost(4096) * 3)
|
viewportLightingMem = ByteBuffer.allocateDirect(viewportCellWidth.coerceAtMost(4096) * viewportCellHeight.coerceAtMost(4096) * 3)
|
||||||
} else {
|
} else {
|
||||||
viewportLightingMem = null
|
viewportLightingMem = null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onDrawGUI = ArrayList<() -> Unit>()
|
private val onDrawGUI = ArrayList<() -> Unit>()
|
||||||
@ -421,8 +412,6 @@ class StarboundClient : Closeable {
|
|||||||
|
|
||||||
layers.render(gl.matrixStack)
|
layers.render(gl.matrixStack)
|
||||||
|
|
||||||
viewportLighting.multithreaded = true
|
|
||||||
|
|
||||||
val viewportLightingMem = viewportLightingMem
|
val viewportLightingMem = viewportLightingMem
|
||||||
|
|
||||||
if (viewportLightingMem != null && !fullbright) {
|
if (viewportLightingMem != null && !fullbright) {
|
||||||
@ -432,6 +421,9 @@ class StarboundClient : Closeable {
|
|||||||
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
viewportLighting.calculate(viewportLightingMem, viewportLighting.width.coerceAtMost(4096), viewportLighting.height.coerceAtMost(4096))
|
||||||
viewportLightingMem.position(0)
|
viewportLightingMem.position(0)
|
||||||
|
|
||||||
|
val old = gl.textureUnpackAlignment
|
||||||
|
gl.textureUnpackAlignment = if (viewportLighting.width.coerceAtMost(4096) % 4 == 0) 4 else 1
|
||||||
|
|
||||||
viewportLightingTexture.upload(
|
viewportLightingTexture.upload(
|
||||||
GL_RGB,
|
GL_RGB,
|
||||||
viewportLighting.width.coerceAtMost(4096),
|
viewportLighting.width.coerceAtMost(4096),
|
||||||
@ -441,7 +433,10 @@ class StarboundClient : Closeable {
|
|||||||
viewportLightingMem
|
viewportLightingMem
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gl.textureUnpackAlignment = old
|
||||||
|
|
||||||
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
viewportLightingTexture.textureMinFilter = GL_LINEAR
|
||||||
|
//viewportLightingTexture.textureMagFilter = GL_NEAREST
|
||||||
|
|
||||||
//viewportLightingTexture.generateMips()
|
//viewportLightingTexture.generateMips()
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import java.io.File
|
|||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.function.IntConsumer
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
@ -54,7 +55,11 @@ private class GLStateSwitchTracker(private val enum: Int, private var value: Boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GLStateFuncTracker(private val glFunc: (Int) -> Unit, private var value: Int) {
|
private class GLStateIntTracker(private val fn: Function, private val enum: Int, private var value: Int) {
|
||||||
|
fun interface Function {
|
||||||
|
fun invoke(enum: Int, value: Int)
|
||||||
|
}
|
||||||
|
|
||||||
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int {
|
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@ -65,7 +70,24 @@ private class GLStateFuncTracker(private val glFunc: (Int) -> Unit, private var
|
|||||||
if (value == this.value)
|
if (value == this.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
glFunc.invoke(value)
|
fn.invoke(enum, value)
|
||||||
|
checkForGLError()
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GLStateFuncTracker(private val glFunc: IntConsumer, private var value: Int) {
|
||||||
|
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Int) {
|
||||||
|
glStateTracker.ensureSameThread()
|
||||||
|
|
||||||
|
if (value == this.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
glFunc.accept(value)
|
||||||
checkForGLError()
|
checkForGLError()
|
||||||
this.value = value
|
this.value = value
|
||||||
}
|
}
|
||||||
@ -205,6 +227,8 @@ class GLStateTracker(val client: StarboundClient) {
|
|||||||
var cull by GLStateSwitchTracker(GL_CULL_FACE)
|
var cull by GLStateSwitchTracker(GL_CULL_FACE)
|
||||||
var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK)
|
var cullMode by GLStateFuncTracker(::glCullFace, GL_BACK)
|
||||||
|
|
||||||
|
var textureUnpackAlignment by GLStateIntTracker(::glPixelStorei, GL_UNPACK_ALIGNMENT, 4)
|
||||||
|
|
||||||
var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) {
|
var scissorRect by GLStateGenericTracker(ScissorRect(0, 0, 0, 0)) {
|
||||||
// require(it.x >= 0) { "Invalid X ${it.x}"}
|
// require(it.x >= 0) { "Invalid X ${it.x}"}
|
||||||
// require(it.y >= 0) { "Invalid Y ${it.y}"}
|
// require(it.y >= 0) { "Invalid Y ${it.y}"}
|
||||||
@ -598,6 +622,9 @@ class GLStateTracker(val client: StarboundClient) {
|
|||||||
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
|
private val LOGGER = LogManager.getLogger(GLStateTracker::class.java)
|
||||||
private val TRACKERS = ThreadLocal<GLStateTracker>()
|
private val TRACKERS = ThreadLocal<GLStateTracker>()
|
||||||
|
|
||||||
|
fun current() = checkNotNull(TRACKERS.get()) { "Current thread has no OpenGL State attached" }
|
||||||
|
fun currentOrNull(): GLStateTracker? = TRACKERS.get()
|
||||||
|
|
||||||
private fun readInternal(file: String): String {
|
private fun readInternal(file: String): String {
|
||||||
return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader()
|
return ClassLoader.getSystemClassLoader().getResourceAsStream(file)!!.bufferedReader()
|
||||||
.let {
|
.let {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl.vertex
|
package ru.dbotthepony.kstarbound.client.gl.vertex
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.UVCoordinates
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
|
||||||
typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder
|
typealias QuadVertexTransformer = (VertexBuilder, Int) -> VertexBuilder
|
||||||
@ -52,6 +53,14 @@ object QuadTransformers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun uv(uv: UVCoordinates): QuadVertexTransformer {
|
||||||
|
return uv(uv.u0, uv.v0, uv.u1, uv.v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uv(uv: UVCoordinates, lambda: QuadVertexTransformer): QuadVertexTransformer {
|
||||||
|
return uv(uv.u0, uv.v0, uv.u1, uv.v1, lambda)
|
||||||
|
}
|
||||||
|
|
||||||
fun vec4(x: Float, y: Float, z: Float, w: Float, after: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): QuadVertexTransformer {
|
fun vec4(x: Float, y: Float, z: Float, w: Float, after: QuadVertexTransformer = EMPTY_VERTEX_TRANSFORM): QuadVertexTransformer {
|
||||||
return transformer@{ it, index ->
|
return transformer@{ it, index ->
|
||||||
it.pushVec4f(x, y, z, w)
|
it.pushVec4f(x, y, z, w)
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
|
* Позволяет вызывать отрисовщики в определённой (послойной) последовательности
|
||||||
*/
|
*/
|
||||||
class LayeredRenderer {
|
class LayeredRenderer {
|
||||||
private val layers = Int2ObjectAVLTreeMap<ArrayList<(Matrix4fStack) -> Unit>>()
|
private val layers = Long2ObjectAVLTreeMap<ArrayList<(Matrix4fStack) -> Unit>>()
|
||||||
private val layersHash = Int2ObjectOpenHashMap<ArrayList<(Matrix4fStack) -> Unit>>()
|
private val layersHash = Long2ObjectOpenHashMap<ArrayList<(Matrix4fStack) -> Unit>>()
|
||||||
|
|
||||||
/**
|
fun add(layer: Long, renderer: (Matrix4fStack) -> Unit) {
|
||||||
* Сортировка [layer] происходит от дальнего (БОЛЬШЕ!) к ближнему (МЕНЬШЕ!)
|
|
||||||
*
|
|
||||||
* Пример:
|
|
||||||
* `8 -> 6 -> 4 -> 1 -> -4 -> -7`
|
|
||||||
*/
|
|
||||||
fun add(layer: Int, renderer: (Matrix4fStack) -> Unit) {
|
|
||||||
var list = layersHash[layer]
|
var list = layersHash[layer]
|
||||||
|
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = ArrayList()
|
list = ArrayList()
|
||||||
layers[-layer] = list
|
layers[layer] = list
|
||||||
layersHash[layer] = list
|
layersHash[layer] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType
|
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
class MultiMeshBuilder {
|
class MultiMeshBuilder {
|
||||||
data class Entry(val config: RenderConfig<*>, val builder: VertexBuilder, val layer: Int)
|
data class Entry(val config: RenderConfig<*>, val builder: VertexBuilder, val layer: Long)
|
||||||
|
|
||||||
private val meshes = Reference2ObjectOpenHashMap<RenderConfig<*>, Int2ObjectOpenHashMap<Entry>>()
|
private val meshes = Reference2ObjectOpenHashMap<RenderConfig<*>, Long2ObjectOpenHashMap<Entry>>()
|
||||||
|
|
||||||
fun get(config: RenderConfig<*>, layer: Int): VertexBuilder {
|
fun get(config: RenderConfig<*>, layer: Long): VertexBuilder {
|
||||||
return meshes.computeIfAbsent(config, Reference2ObjectFunction {
|
return meshes.computeIfAbsent(config, Reference2ObjectFunction {
|
||||||
Int2ObjectOpenHashMap()
|
Long2ObjectOpenHashMap()
|
||||||
}).computeIfAbsent(layer, Int2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes, config.initialBuilderCapacity), layer) }).builder
|
}).computeIfAbsent(layer, Long2ObjectFunction { Entry(config, VertexBuilder(config.program.attributes, config.initialBuilderCapacity), layer) }).builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() = meshes.clear()
|
fun clear() = meshes.clear()
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
|
||||||
|
const val RenderLayerUpperBits = 5
|
||||||
|
const val RenderLayerLowerBits = 32 - RenderLayerUpperBits
|
||||||
|
const val RenderLayerLowerMask = 0.inv() shr RenderLayerUpperBits
|
||||||
|
|
||||||
|
enum class RenderLayer(val index: Long) {
|
||||||
|
BackgroundOverlay (1L shl RenderLayerLowerBits),
|
||||||
|
BackgroundTile (2L shl RenderLayerLowerBits),
|
||||||
|
Platform (3L shl RenderLayerLowerBits),
|
||||||
|
Plant (4L shl RenderLayerLowerBits),
|
||||||
|
PlantDrop (5L shl RenderLayerLowerBits),
|
||||||
|
Object (6L shl RenderLayerLowerBits),
|
||||||
|
PreviewObject (7L shl RenderLayerLowerBits),
|
||||||
|
BackParticle (8L shl RenderLayerLowerBits),
|
||||||
|
Vehicle (9L shl RenderLayerLowerBits),
|
||||||
|
Effect (10L shl RenderLayerLowerBits),
|
||||||
|
Projectile (11L shl RenderLayerLowerBits),
|
||||||
|
Monster (12L shl RenderLayerLowerBits),
|
||||||
|
Npc (13L shl RenderLayerLowerBits),
|
||||||
|
Player (14L shl RenderLayerLowerBits),
|
||||||
|
ItemDrop (15L shl RenderLayerLowerBits),
|
||||||
|
Liquid (16L shl RenderLayerLowerBits),
|
||||||
|
MiddleParticle (17L shl RenderLayerLowerBits),
|
||||||
|
ForegroundTile (18L shl RenderLayerLowerBits),
|
||||||
|
ForegroundEntity (19L shl RenderLayerLowerBits),
|
||||||
|
ForegroundOverlay (20L shl RenderLayerLowerBits),
|
||||||
|
FrontParticle (21L shl RenderLayerLowerBits),
|
||||||
|
Overlay (22L shl RenderLayerLowerBits);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LogManager.getLogger()
|
||||||
|
|
||||||
|
private inline fun perform(value: String, symbol: Char, operator: (Long, Long) -> Long): Long {
|
||||||
|
val split = value.split(symbol)
|
||||||
|
|
||||||
|
if (split.size != 2) {
|
||||||
|
logger.error("Ambiguous render layer string: $value; assuming 0")
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
val enum = entries.find { it.name == split[0] }
|
||||||
|
|
||||||
|
if (enum == null) {
|
||||||
|
logger.error("Unknown render layer: ${split[0]} in $value; assuming 0")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val num = split[1].toLongOrNull()
|
||||||
|
|
||||||
|
if (num == null) {
|
||||||
|
logger.error("Invalid render layer string: $value; assuming 0")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return operator(enum.index, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(value: String): Long {
|
||||||
|
if ('+' in value) {
|
||||||
|
return perform(value, '+', Long::plus)
|
||||||
|
} else if ('-' in value) {
|
||||||
|
return perform(value, '-', Long::minus)
|
||||||
|
} else if ('*' in value) {
|
||||||
|
return perform(value, '*', Long::times)
|
||||||
|
} else if ('/' in value) {
|
||||||
|
return perform(value, '/', Long::div)
|
||||||
|
} else {
|
||||||
|
val enum = entries.find { it.name == value }
|
||||||
|
|
||||||
|
if (enum == null) {
|
||||||
|
logger.error("Unknown render layer: $value; assuming 0")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return enum.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -98,9 +98,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val state get() = renderers.state
|
val state get() = renderers.state
|
||||||
val texture = state.loadTexture(def.renderParameters.texture.imagePath.value!!).also {
|
val texture = def.renderParameters.texture?.imagePath?.value?.let { state.loadTexture(it).also { it.textureMagFilter = GL_NEAREST }}
|
||||||
it.textureMagFilter = GL_NEAREST
|
|
||||||
}
|
|
||||||
|
|
||||||
val equalityTester: EqualityRuleTester = when (def) {
|
val equalityTester: EqualityRuleTester = when (def) {
|
||||||
is TileDefinition -> TileEqualityTester(def)
|
is TileDefinition -> TileEqualityTester(def)
|
||||||
@ -108,8 +106,8 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
|
|
||||||
val bakedProgramState = renderers.foreground(texture)
|
val bakedProgramState = texture?.let { renderers.foreground(it) }
|
||||||
val bakedBackgroundProgramState = renderers.background(texture)
|
val bakedBackgroundProgramState = texture?.let { renderers.background(it) }
|
||||||
// private var notifiedDepth = false
|
// private var notifiedDepth = false
|
||||||
|
|
||||||
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
|
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileAccess, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
|
||||||
@ -147,7 +145,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
maxs += piece.colorStride * self.color.ordinal
|
maxs += piece.colorStride * self.color.ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
val (u0, v0) = texture.pixelToUV(mins)
|
val (u0, v0) = texture!!.pixelToUV(mins)
|
||||||
val (u1, v1) = texture.pixelToUV(maxs)
|
val (u1, v1) = texture.pixelToUV(maxs)
|
||||||
|
|
||||||
builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (isModifier) self.modifierHueShift else self.hueShift) })
|
builder.quadZ(a, b, c, d, Z_LEVEL, QuadTransformers.uv(u0, v1, u1, v0).after { it, _ -> it.push(if (isModifier) self.modifierHueShift else self.hueShift) })
|
||||||
@ -206,11 +204,13 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
|
||||||
*/
|
*/
|
||||||
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
|
fun tesselate(self: ITileState, getter: ITileAccess, meshBuilder: MultiMeshBuilder, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
|
||||||
|
if (texture == null) return
|
||||||
|
|
||||||
// если у нас нет renderTemplate
|
// если у нас нет renderTemplate
|
||||||
// то мы просто не можем его отрисовать
|
// то мы просто не можем его отрисовать
|
||||||
val template = def.renderTemplate.value ?: return
|
val template = def.renderTemplate.value ?: return
|
||||||
|
|
||||||
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel).mode(GeometryType.QUADS)
|
val vertexBuilder = meshBuilder.get(if (background) bakedBackgroundProgramState!! else bakedProgramState!!, def.renderParameters.zLevel).mode(GeometryType.QUADS)
|
||||||
|
|
||||||
for ((_, matcher) in template.matches) {
|
for ((_, matcher) in template.matches) {
|
||||||
for (matchPiece in matcher) {
|
for (matchPiece in matcher) {
|
||||||
|
@ -6,15 +6,6 @@ import ru.dbotthepony.kstarbound.world.Chunk
|
|||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
|
|
||||||
/**
|
|
||||||
* Псевдо zPos у фоновых тайлов
|
|
||||||
*
|
|
||||||
* Добавление этого числа к zPos гарантирует, что фоновые тайлы будут отрисованы
|
|
||||||
* первыми (на самом дальнем плане)
|
|
||||||
*/
|
|
||||||
const val Z_LEVEL_BACKGROUND = 60000
|
|
||||||
const val Z_LEVEL_LIQUID = 10000
|
|
||||||
|
|
||||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
|
||||||
val state: GLStateTracker get() = world.client.gl
|
val state: GLStateTracker get() = world.client.gl
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ import ru.dbotthepony.kstarbound.client.render.ConfiguredMesh
|
|||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.client.render.Mesh
|
import ru.dbotthepony.kstarbound.client.render.Mesh
|
||||||
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
|
import ru.dbotthepony.kstarbound.client.render.MultiMeshBuilder
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||||
|
import ru.dbotthepony.kstarbound.client.world.`object`.ClientWorldObject
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
@ -73,7 +75,7 @@ class ClientWorld(
|
|||||||
inner class RenderRegion(val x: Int, val y: Int) {
|
inner class RenderRegion(val x: Int, val y: Int) {
|
||||||
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||||
private val state get() = client.gl
|
private val state get() = client.gl
|
||||||
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Int>>()
|
val bakedMeshes = ArrayList<Pair<ConfiguredMesh<*>, Long>>()
|
||||||
var isDirty = true
|
var isDirty = true
|
||||||
|
|
||||||
fun bake() {
|
fun bake() {
|
||||||
@ -91,7 +93,7 @@ class ClientWorld(
|
|||||||
val tile = view.getTile(x, y) ?: continue
|
val tile = view.getTile(x, y) ?: continue
|
||||||
val material = tile.material
|
val material = tile.material
|
||||||
|
|
||||||
if (material != null) {
|
if (material != null && !material.isMeta) {
|
||||||
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
|
client.tileRenderers.getMaterialRenderer(material.materialName).tesselate(tile, view, meshes, Vector2i(x, y), background = isBackground)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((baked, zLevel) in background.bakedMeshes) {
|
for ((baked, zLevel) in background.bakedMeshes) {
|
||||||
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
layers.add(zLevel + RenderLayer.BackgroundTile.index) {
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
baked.render(it.last())
|
baked.render(it.last())
|
||||||
it.pop()
|
it.pop()
|
||||||
@ -161,7 +163,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((baked, zLevel) in foreground.bakedMeshes) {
|
for ((baked, zLevel) in foreground.bakedMeshes) {
|
||||||
layers.add(zLevel) {
|
layers.add(zLevel + RenderLayer.ForegroundTile.index) {
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
baked.render(it.last())
|
baked.render(it.last())
|
||||||
it.pop()
|
it.pop()
|
||||||
@ -169,7 +171,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (liquidMesh.isNotEmpty()) {
|
if (liquidMesh.isNotEmpty()) {
|
||||||
layers.add(Z_LEVEL_LIQUID) {
|
layers.add(RenderLayer.Liquid.index) {
|
||||||
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
|
|
||||||
val program = client.gl.programs.liquid
|
val program = client.gl.programs.liquid
|
||||||
@ -266,7 +268,8 @@ class ClientWorld(
|
|||||||
|
|
||||||
for (obj in objects) {
|
for (obj in objects) {
|
||||||
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
if (obj.pos.x in client.viewportCellX .. client.viewportCellX + client.viewportCellWidth && obj.pos.y in client.viewportCellY .. client.viewportCellY + client.viewportCellHeight) {
|
||||||
layers.add(-999999) {
|
//layers.add(RenderLayer.Object.index) {
|
||||||
|
layers.add(obj.orientation?.renderLayer ?: continue) {
|
||||||
client.gl.quadWireframe {
|
client.gl.quadWireframe {
|
||||||
it.quad(
|
it.quad(
|
||||||
obj.pos.x.toFloat(),
|
obj.pos.x.toFloat(),
|
||||||
@ -275,6 +278,11 @@ class ClientWorld(
|
|||||||
obj.pos.y + 1f,
|
obj.pos.y + 1f,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(obj as ClientWorldObject).drawables.forEach {
|
||||||
|
val (x, y) = obj.orientation?.imagePosition ?: Vector2f.ZERO
|
||||||
|
it.with { "default" }.render(client.gl, x = obj.pos.x.toFloat() + x, y = obj.pos.y.toFloat() + y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
obj.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
||||||
|
@ -5,6 +5,7 @@ import com.google.gson.JsonObject
|
|||||||
import ru.dbotthepony.kstarbound.RegistryObject
|
import ru.dbotthepony.kstarbound.RegistryObject
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||||
|
import ru.dbotthepony.kstarbound.defs.Drawable
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||||
@ -19,6 +20,7 @@ class ClientWorldObject(world: ClientWorld, prototype: RegistryObject<ObjectDefi
|
|||||||
var animationPosition: Vector2i by Property(Vector2i.ZERO)
|
var animationPosition: Vector2i by Property(Vector2i.ZERO)
|
||||||
|
|
||||||
val animation: AnimationDefinition? by LazyData(listOf("animationCustom", "animation")) {
|
val animation: AnimationDefinition? by LazyData(listOf("animationCustom", "animation")) {
|
||||||
|
/*
|
||||||
val custom = dataValue("animationCustom")
|
val custom = dataValue("animationCustom")
|
||||||
|
|
||||||
if (custom !is JsonObject) {
|
if (custom !is JsonObject) {
|
||||||
@ -27,6 +29,16 @@ class ClientWorldObject(world: ClientWorld, prototype: RegistryObject<ObjectDefi
|
|||||||
animAdapter.fromJsonTree(merge(prototype.value.animation.json!!, custom))
|
animAdapter.fromJsonTree(merge(prototype.value.animation.json!!, custom))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
}*/
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val drawables: List<Drawable> by LazyData {
|
||||||
|
if (orientationIndex !in 0 until validOrientations) {
|
||||||
|
return@LazyData listOf()
|
||||||
|
} else {
|
||||||
|
return@LazyData orientations[orientationIndex].drawables
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
|
||||||
data class AssetPath(val path: String, val fullPath: String) {
|
data class AssetPath(val path: String, val fullPath: String) {
|
||||||
companion object : TypeAdapterFactory {
|
companion object : TypeAdapterFactory {
|
||||||
@ -25,7 +26,7 @@ data class AssetPath(val path: String, val fullPath: String) {
|
|||||||
override fun read(`in`: JsonReader): AssetPath? {
|
override fun read(`in`: JsonReader): AssetPath? {
|
||||||
val path = strings.read(`in`) ?: return null
|
val path = strings.read(`in`) ?: return null
|
||||||
if (path == "") return null
|
if (path == "") return null
|
||||||
return AssetPath(path, Starbound.pathStack.remap(path))
|
return AssetPath(path, AssetPathStack.remap(path))
|
||||||
}
|
}
|
||||||
} as TypeAdapter<T>
|
} as TypeAdapter<T>
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,17 @@ import com.google.gson.stream.JsonWriter
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.api.ISBFileLocator
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?, val json: JsonElement?) {
|
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?, val json: JsonElement?) {
|
||||||
companion object : TypeAdapterFactory {
|
companion object : TypeAdapterFactory {
|
||||||
|
val EMPTY = AssetReference(null, null, null, null)
|
||||||
|
|
||||||
|
fun <V> empty() = EMPTY as AssetReference<V>
|
||||||
|
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == AssetReference::class.java) {
|
if (type.rawType == AssetReference::class.java) {
|
||||||
val param = type.type as? ParameterizedType ?: return null
|
val param = type.type as? ParameterizedType ?: return null
|
||||||
@ -44,7 +47,7 @@ data class AssetReference<V>(val path: String?, val fullPath: String?, val value
|
|||||||
return null
|
return null
|
||||||
} else if (`in`.peek() == JsonToken.STRING) {
|
} else if (`in`.peek() == JsonToken.STRING) {
|
||||||
val path = strings.read(`in`)!!
|
val path = strings.read(`in`)!!
|
||||||
val fullPath = Starbound.pathStack.remap(path)
|
val fullPath = AssetPathStack.remap(path)
|
||||||
val get = cache[fullPath]
|
val get = cache[fullPath]
|
||||||
|
|
||||||
if (get != null)
|
if (get != null)
|
||||||
@ -66,7 +69,7 @@ data class AssetReference<V>(val path: String?, val fullPath: String?, val value
|
|||||||
it.isLenient = true
|
it.isLenient = true
|
||||||
})
|
})
|
||||||
|
|
||||||
val value = Starbound.pathStack(fullPath) {
|
val value = AssetPathStack(fullPath) {
|
||||||
adapter.read(JsonTreeReader(json))
|
adapter.read(JsonTreeReader(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
enum class CollisionType {
|
||||||
|
NULL,
|
||||||
|
NONE,
|
||||||
|
PLATFORM,
|
||||||
|
DYNAMIC,
|
||||||
|
SLIPPERY,
|
||||||
|
BLOCK;
|
||||||
|
}
|
175
src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt
Normal file
175
src/main/kotlin/ru/dbotthepony/kstarbound/defs/Drawable.kt
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
|
||||||
|
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.math.LineF
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
import ru.dbotthepony.kstarbound.util.contains
|
||||||
|
import ru.dbotthepony.kvector.arrays.Matrix3f
|
||||||
|
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
||||||
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector3f
|
||||||
|
|
||||||
|
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
|
||||||
|
class Line(
|
||||||
|
val line: LineF,
|
||||||
|
val width: Float,
|
||||||
|
position: Vector2f = Vector2f.ZERO,
|
||||||
|
color: RGBAColor = RGBAColor.WHITE,
|
||||||
|
fullbright: Boolean = false
|
||||||
|
) : Drawable(position, color, fullbright) {
|
||||||
|
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Poly(
|
||||||
|
val vertices: ImmutableList<Vector2f>,
|
||||||
|
position: Vector2f = Vector2f.ZERO,
|
||||||
|
color: RGBAColor = RGBAColor.WHITE,
|
||||||
|
fullbright: Boolean = false
|
||||||
|
) : Drawable(position, color, fullbright) {
|
||||||
|
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Image(
|
||||||
|
val path: ImageReference,
|
||||||
|
val transform: Matrix3f,
|
||||||
|
val centered: Boolean,
|
||||||
|
position: Vector2f = Vector2f.ZERO,
|
||||||
|
color: RGBAColor = RGBAColor.WHITE,
|
||||||
|
fullbright: Boolean = false
|
||||||
|
) : Drawable(position, color, fullbright) {
|
||||||
|
override fun with(values: (String) -> String?): Image {
|
||||||
|
val newPath = path.with(values)
|
||||||
|
|
||||||
|
if (newPath == path) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return Image(newPath, transform, centered, position, color, fullbright)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {
|
||||||
|
val sprite = path.sprite ?: return
|
||||||
|
val texture = gl.loadTexture(path.imagePath.value!!)
|
||||||
|
|
||||||
|
if (centered) {
|
||||||
|
gl.quadTexture(texture) {
|
||||||
|
it.quad(x - (sprite.width(texture.width) / PIXELS_IN_STARBOUND_UNITf) * 0.5f, y - (sprite.height(texture.height) / PIXELS_IN_STARBOUND_UNITf) * 0.5f, x + sprite.width(texture.width) / PIXELS_IN_STARBOUND_UNITf * 0.5f, y + sprite.height(texture.height) / PIXELS_IN_STARBOUND_UNITf * 0.5f, QuadTransformers.uv(sprite.compute(texture)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gl.quadTexture(texture) {
|
||||||
|
it.quad(x, y, x + sprite.width(texture.width) / PIXELS_IN_STARBOUND_UNITf, y + sprite.height(texture.height) / PIXELS_IN_STARBOUND_UNITf, QuadTransformers.uv(sprite.compute(texture)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Empty(position: Vector2f = Vector2f.ZERO, color: RGBAColor = RGBAColor.WHITE, fullbright: Boolean = false) : Drawable(position, color, fullbright) {
|
||||||
|
override fun render(gl: GLStateTracker, stack: Matrix4fStack, x: Float, y: Float) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun with(values: (String) -> String?): Drawable {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun render(gl: GLStateTracker = GLStateTracker.current(), stack: Matrix4fStack = gl.matrixStack, x: Float = 0f, y: Float = 0f)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY = Empty()
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<Drawable>() {
|
||||||
|
private val lines = gson.getAdapter(LineF::class.java)
|
||||||
|
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||||
|
private val vectors = gson.getAdapter(Vector2f::class.java)
|
||||||
|
private val vectors3 = gson.getAdapter(Vector3f::class.java)
|
||||||
|
private val colors = gson.getAdapter(RGBAColor::class.java)
|
||||||
|
private val images = gson.getAdapter(ImageReference::class.java)
|
||||||
|
private val vertices = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, Vector2f::class.java)) as TypeAdapter<ImmutableList<Vector2f>>
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter?, value: Drawable?) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Drawable {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return EMPTY
|
||||||
|
} else {
|
||||||
|
val value = objects.read(`in`)!!
|
||||||
|
|
||||||
|
val position = value["position"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO
|
||||||
|
val color = value["color"]?.let { colors.fromJsonTree(it) } ?: RGBAColor.WHITE
|
||||||
|
val fullbright = value["fullbright"]?.asBoolean ?: false
|
||||||
|
|
||||||
|
if ("line" in value) {
|
||||||
|
return Line(lines.fromJsonTree(value["line"]), value["width"].asFloat, position, color, fullbright)
|
||||||
|
} else if ("poly" in value) {
|
||||||
|
return Poly(vertices.fromJsonTree(value["poly"]), position, color, fullbright)
|
||||||
|
} else if ("image" in value) {
|
||||||
|
val image = images.fromJsonTree(value["image"])
|
||||||
|
val mat = Matrix3f.identity()
|
||||||
|
|
||||||
|
if ("transformation" in value) {
|
||||||
|
val array = value["transformation"].asJsonArray
|
||||||
|
|
||||||
|
// original starbound use GLM, which reflects OpenGL, which in turn make matrices row-major
|
||||||
|
val row0 = vectors3.fromJsonTree(array[0])
|
||||||
|
val row1 = vectors3.fromJsonTree(array[1])
|
||||||
|
val row2 = vectors3.fromJsonTree(array[2])
|
||||||
|
|
||||||
|
mat.r00 = row0.x
|
||||||
|
mat.r01 = row0.y
|
||||||
|
mat.r02 = row0.z
|
||||||
|
|
||||||
|
mat.r10 = row1.x
|
||||||
|
mat.r11 = row1.y
|
||||||
|
mat.r12 = row1.z
|
||||||
|
|
||||||
|
mat.r20 = row2.x
|
||||||
|
mat.r21 = row2.y
|
||||||
|
mat.r22 = row2.z
|
||||||
|
} else {
|
||||||
|
if ("rotation" in value) {
|
||||||
|
LOGGER.warn("Rotation is not supported yet (required by ${image.raw})")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("mirrored" in value && value["mirrored"].asBoolean) {
|
||||||
|
mat.scale(-1f, -1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("scale" in value) {
|
||||||
|
if (value["scale"].isJsonArray) {
|
||||||
|
mat.scale(vectors.fromJsonTree(value["scale"]))
|
||||||
|
} else {
|
||||||
|
val scale = value["scale"].asFloat
|
||||||
|
mat.scale(scale, scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Image(image, mat, value["centered"]?.asBoolean ?: false, position, color, fullbright)
|
||||||
|
} else {
|
||||||
|
return Empty(position, color, fullbright)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -76,9 +76,13 @@ interface IThingWithDescription {
|
|||||||
data class ThingDescription(
|
data class ThingDescription(
|
||||||
override val shortdescription: String = "...",
|
override val shortdescription: String = "...",
|
||||||
override val description: String = "...",
|
override val description: String = "...",
|
||||||
override val racialDescription: Map<String, String>,
|
override val racialDescription: Map<String, String> = mapOf(),
|
||||||
override val racialShortDescription: Map<String, String>,
|
override val racialShortDescription: Map<String, String> = mapOf(),
|
||||||
) : IThingWithDescription {
|
) : IThingWithDescription {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = ThingDescription()
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(val interner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
class Factory(val interner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == ThingDescription::class.java) {
|
if (type.rawType == ThingDescription::class.java) {
|
||||||
|
@ -6,6 +6,7 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.Either
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
import ru.dbotthepony.kstarbound.util.set
|
import ru.dbotthepony.kstarbound.util.set
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
@ -39,7 +40,7 @@ abstract class JsonDriven(val path: String) {
|
|||||||
lazies.forEach { it.invalidate() }
|
lazies.forEach { it.invalidate() }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected inner class LazyData<T>(names: Iterable<String>, private val initializer: () -> T) : Lazy<T> {
|
protected inner class LazyData<T>(names: Iterable<String> = listOf(), private val initializer: () -> T) : Lazy<T> {
|
||||||
constructor(initializer: () -> T) : this(listOf(), initializer)
|
constructor(initializer: () -> T) : this(listOf(), initializer)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -110,12 +111,12 @@ abstract class JsonDriven(val path: String) {
|
|||||||
} else if (default.isLeft) {
|
} else if (default.isLeft) {
|
||||||
return default.left().get()
|
return default.left().get()
|
||||||
} else {
|
} else {
|
||||||
Starbound.pathStack.block(path) {
|
AssetPathStack.block(path) {
|
||||||
return adapter!!.fromJsonTree(default.right())
|
return adapter!!.fromJsonTree(default.right())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Starbound.pathStack.block(path) {
|
AssetPathStack.block(path) {
|
||||||
return adapter!!.fromJsonTree(value)
|
return adapter!!.fromJsonTree(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +139,7 @@ abstract class JsonDriven(val path: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(t: T) {
|
override fun accept(t: T) {
|
||||||
Starbound.pathStack.block(path) {
|
AssetPathStack.block(path) {
|
||||||
properties[checkNotNull(name)] = adapter!!.toJsonTree(t)
|
properties[checkNotNull(name)] = adapter!!.toJsonTree(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
117
src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonReference.kt
Normal file
117
src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonReference.kt
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.value
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
|
||||||
|
sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: String?) {
|
||||||
|
abstract val value: E
|
||||||
|
|
||||||
|
class Element(path: String?, fullPath: String?, override val value: JsonElement?) : JsonReference<JsonElement?>(path, fullPath)
|
||||||
|
class Object(path: String?, fullPath: String?, override val value: JsonObject?) : JsonReference<JsonObject?>(path, fullPath)
|
||||||
|
class Array(path: String?, fullPath: String?, override val value: JsonArray?) : JsonReference<JsonArray?>(path, fullPath)
|
||||||
|
class Primitive(path: String?, fullPath: String?, override val value: JsonPrimitive?) : JsonReference<JsonPrimitive?>(path, fullPath)
|
||||||
|
|
||||||
|
class NElement(path: String?, fullPath: String?, override val value: JsonElement) : JsonReference<JsonElement>(path, fullPath)
|
||||||
|
class NObject(path: String?, fullPath: String?, override val value: JsonObject) : JsonReference<JsonObject>(path, fullPath)
|
||||||
|
class NArray(path: String?, fullPath: String?, override val value: JsonArray) : JsonReference<JsonArray>(path, fullPath)
|
||||||
|
class NPrimitive(path: String?, fullPath: String?, override val value: JsonPrimitive) : JsonReference<JsonPrimitive>(path, fullPath)
|
||||||
|
|
||||||
|
private fun write(writer: JsonWriter) {
|
||||||
|
if (fullPath != null) {
|
||||||
|
writer.value(fullPath)
|
||||||
|
} else {
|
||||||
|
writer.value(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "JsonReference[$fullPath / $value]"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Adapter0<E : JsonElement?, J : JsonReference<E>>(gson: Gson, type: TypeToken<E>, private val factory: (String?, String?, E?) -> J) : TypeAdapter<J>() {
|
||||||
|
private val adapter = gson.getAdapter(type)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: J?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
value.write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): J {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return factory(null, null, null)
|
||||||
|
} else {
|
||||||
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
val path = `in`.nextString()
|
||||||
|
val full = AssetPathStack.remapSafe(path)
|
||||||
|
val get = Starbound.loadJsonAsset(full) ?: return factory(path, full, null)
|
||||||
|
return factory(path, full, adapter.fromJsonTree(get))
|
||||||
|
} else {
|
||||||
|
return factory(null, null, adapter.read(`in`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Adapter1<E : JsonElement, J : JsonReference<E>>(gson: Gson, type: TypeToken<E>, private val factory: (String?, String?, E) -> J) : TypeAdapter<J>() {
|
||||||
|
private val adapter = gson.getAdapter(type)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: J?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
value.write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): J {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
throw JsonSyntaxException("Referenced json element is literal null, which is not allowed")
|
||||||
|
} else {
|
||||||
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
|
val path = `in`.nextString()
|
||||||
|
val full = AssetPathStack.remapSafe(path)
|
||||||
|
val get = Starbound.loadJsonAsset(full) ?: throw JsonSyntaxException("Json asset at $full does not exist")
|
||||||
|
return factory(path, full, adapter.fromJsonTree(get) ?: throw JsonSyntaxException("Json asset at $full is literal null, which is not allowed"))
|
||||||
|
} else {
|
||||||
|
return factory(null, null, adapter.read(`in`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : TypeAdapterFactory {
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
return when (type.rawType) {
|
||||||
|
JsonReference::class.java -> throw IllegalArgumentException("Don't use JsonReference class directly, use one of its subtypes")
|
||||||
|
|
||||||
|
Element::class.java -> Adapter0(gson, TypeToken.get(JsonElement::class.java), ::Element)
|
||||||
|
Object::class.java -> Adapter0(gson, TypeToken.get(JsonObject::class.java), ::Object)
|
||||||
|
Array::class.java -> Adapter0(gson, TypeToken.get(JsonArray::class.java), ::Array)
|
||||||
|
Primitive::class.java -> Adapter0(gson, TypeToken.get(JsonPrimitive::class.java), ::Primitive)
|
||||||
|
|
||||||
|
NElement::class.java -> Adapter1(gson, TypeToken.get(JsonElement::class.java), ::NElement)
|
||||||
|
NObject::class.java -> Adapter1(gson, TypeToken.get(JsonObject::class.java), ::NObject)
|
||||||
|
NArray::class.java -> Adapter1(gson, TypeToken.get(JsonArray::class.java), ::NArray)
|
||||||
|
NPrimitive::class.java -> Adapter1(gson, TypeToken.get(JsonPrimitive::class.java), ::NPrimitive)
|
||||||
|
else -> null
|
||||||
|
} as TypeAdapter<T>?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.util.contains
|
||||||
|
import ru.dbotthepony.kstarbound.util.get
|
||||||
|
|
||||||
|
enum class StatModifierType(vararg names: String) {
|
||||||
|
BASE_ADDITION("value", "baseAddition"),
|
||||||
|
BASE_MULTIPLICATION("baseMultiplier"),
|
||||||
|
|
||||||
|
OVERALL_ADDITION("effectiveValue", "effectiveAddition"),
|
||||||
|
OVERALL_MULTIPLICATION("effectiveMultiplier");
|
||||||
|
|
||||||
|
val names: ImmutableSet<String> = ImmutableSet.copyOf(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
|
||||||
|
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: StatModifier?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
out.beginObject()
|
||||||
|
out.name("stat")
|
||||||
|
out.value(value.stat)
|
||||||
|
out.name(value.type.names.first())
|
||||||
|
out.value(value.value)
|
||||||
|
out.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): StatModifier? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
val read = objects.read(`in`)
|
||||||
|
|
||||||
|
val stat = read["stat"]?.asString ?: throw JsonSyntaxException("No stat to modify specified")
|
||||||
|
|
||||||
|
for (type in StatModifierType.entries)
|
||||||
|
for (name in type.names)
|
||||||
|
if (name in read)
|
||||||
|
return StatModifier(stat, read.get(name, 0.0), type)
|
||||||
|
|
||||||
|
throw JsonSyntaxException("Not a stat modifier: $read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt
Normal file
22
src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
enum class TeamType {
|
||||||
|
NULL,
|
||||||
|
// non-PvP-enabled players and player allied NPCs
|
||||||
|
FRIENDLY,
|
||||||
|
// hostile and neutral NPCs and monsters
|
||||||
|
ENEMY,
|
||||||
|
// PvP-enabled players
|
||||||
|
PVP,
|
||||||
|
// cannot damage anything, can be damaged by Friendly/PVP/Assistant
|
||||||
|
PASSIVE,
|
||||||
|
// cannot damage or be damaged
|
||||||
|
GHOSTLY,
|
||||||
|
// cannot damage enemies, can be damaged by anything except enemy
|
||||||
|
ENVIRONMENT,
|
||||||
|
// damages anything except ghostly, damaged by anything except ghostly/passive
|
||||||
|
// used for self damage
|
||||||
|
INDISCRIMINATE,
|
||||||
|
// cannot damage friendlies and cannot be damaged by anything
|
||||||
|
ASSISTANT
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class TouchDamage(
|
||||||
|
val poly: ImmutableList<Vector2d> = ImmutableList.of(),
|
||||||
|
val teamType: TeamType = TeamType.ENVIRONMENT,
|
||||||
|
val damage: Double = 0.0,
|
||||||
|
val damageSourceKind: String = "",
|
||||||
|
val knockback: Double = 0.0,
|
||||||
|
val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
)
|
@ -10,10 +10,12 @@ import com.google.gson.JsonSyntaxException
|
|||||||
import com.google.gson.internal.bind.TypeAdapters
|
import com.google.gson.internal.bind.TypeAdapters
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.api.ISBFileLocator
|
import ru.dbotthepony.kstarbound.api.ISBFileLocator
|
||||||
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
|
||||||
import ru.dbotthepony.kstarbound.io.json.stream
|
import ru.dbotthepony.kstarbound.io.json.stream
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector4i
|
import ru.dbotthepony.kvector.vector.Vector4i
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -115,14 +117,14 @@ class AtlasConfiguration private constructor(
|
|||||||
*
|
*
|
||||||
* Нулевой размер имеет особое значение - считается, что спрайт покрывает весь атлас
|
* Нулевой размер имеет особое значение - считается, что спрайт покрывает весь атлас
|
||||||
*/
|
*/
|
||||||
val width = position.x - position.z
|
val width = position.z - position.x
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Высота, в пикселях
|
* Высота, в пикселях
|
||||||
*
|
*
|
||||||
* Нулевой размер имеет особое значение - считается, что спрайт покрывает весь атлас
|
* Нулевой размер имеет особое значение - считается, что спрайт покрывает весь атлас
|
||||||
*/
|
*/
|
||||||
val height = position.y - position.w
|
val height = position.w - position.y
|
||||||
|
|
||||||
fun width(atlasWidth: Int): Int {
|
fun width(atlasWidth: Int): Int {
|
||||||
if (width == 0)
|
if (width == 0)
|
||||||
@ -185,9 +187,7 @@ class AtlasConfiguration private constructor(
|
|||||||
val sprite = Sprite("root", Vector4i(0, 0, 0, 0))
|
val sprite = Sprite("root", Vector4i(0, 0, 0, 0))
|
||||||
EMPTY = AtlasConfiguration("null", ImmutableMap.of("root", sprite, "default", sprite, "0", sprite), ImmutableList.of(sprite))
|
EMPTY = AtlasConfiguration("null", ImmutableMap.of("root", sprite, "default", sprite, "0", sprite), ImmutableList.of(sprite))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class Registry(val locator: ISBFileLocator, val remapper: PathStack, val gson: Gson) {
|
|
||||||
private val cache = ConcurrentHashMap<String, AtlasConfiguration>()
|
private val cache = ConcurrentHashMap<String, AtlasConfiguration>()
|
||||||
|
|
||||||
private fun generateFakeNames(dimensions: Vector2i): JsonArray {
|
private fun generateFakeNames(dimensions: Vector2i): JsonArray {
|
||||||
@ -218,8 +218,8 @@ class AtlasConfiguration private constructor(
|
|||||||
val sprites = LinkedHashMap<String, Sprite>()
|
val sprites = LinkedHashMap<String, Sprite>()
|
||||||
|
|
||||||
if (frameGrid is JsonObject) {
|
if (frameGrid is JsonObject) {
|
||||||
val size = gson.fromJson(frameGrid["size"] ?: throw JsonSyntaxException("Missing frameGrid.size"), Vector2i::class.java)
|
val size = Starbound.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)
|
val dimensions = Starbound.gson.fromJson(frameGrid["dimensions"] ?: throw JsonSyntaxException("Missing frameGrid.dimensions"), Vector2i::class.java)
|
||||||
|
|
||||||
require(size.x >= 0) { "Invalid size.x: ${size.x}" }
|
require(size.x >= 0) { "Invalid size.x: ${size.x}" }
|
||||||
require(size.y >= 0) { "Invalid size.y: ${size.y}" }
|
require(size.y >= 0) { "Invalid size.y: ${size.y}" }
|
||||||
@ -258,7 +258,7 @@ class AtlasConfiguration private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((spriteName, coords) in frameList.entrySet()) {
|
for ((spriteName, coords) in frameList.entrySet()) {
|
||||||
sprites[spriteName] = Sprite(spriteName, gson.fromJson(coords, Vector4i::class.java))
|
sprites[spriteName] = Sprite(spriteName, Starbound.gson.fromJson(coords, Vector4i::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,8 +269,27 @@ class AtlasConfiguration private constructor(
|
|||||||
if (aliases !is JsonObject)
|
if (aliases !is JsonObject)
|
||||||
throw JsonSyntaxException("aliases expected to be a Json object, $aliases given")
|
throw JsonSyntaxException("aliases expected to be a Json object, $aliases given")
|
||||||
|
|
||||||
|
val remainingAliases = Object2ObjectArrayMap<String, String>()
|
||||||
|
|
||||||
for ((k, v) in aliases.entrySet())
|
for ((k, v) in aliases.entrySet())
|
||||||
sprites[k] = sprites[v.asString] ?: throw JsonSyntaxException("$k want to refer to sprite $v, but it does not exist")
|
remainingAliases[k] = v.asString
|
||||||
|
|
||||||
|
var changes = true
|
||||||
|
|
||||||
|
while (remainingAliases.isNotEmpty() && changes) {
|
||||||
|
changes = false
|
||||||
|
val i = remainingAliases.entries.iterator()
|
||||||
|
|
||||||
|
for ((k, v) in i) {
|
||||||
|
val sprite = sprites[v] ?: continue
|
||||||
|
sprites[k] = sprite
|
||||||
|
changes = true
|
||||||
|
i.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((k, v) in remainingAliases.entries)
|
||||||
|
sprites[k] = sprites[v] ?: throw JsonSyntaxException("Alias '$k' want to refer to sprite '$v', but it does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
return AtlasConfiguration(name, ImmutableMap.copyOf(sprites), spriteList)
|
return AtlasConfiguration(name, ImmutableMap.copyOf(sprites), spriteList)
|
||||||
@ -281,7 +300,7 @@ class AtlasConfiguration private constructor(
|
|||||||
|
|
||||||
while (current != "/" && current != "") {
|
while (current != "/" && current != "") {
|
||||||
val get = cache.computeIfAbsent("$current/$name") {
|
val get = cache.computeIfAbsent("$current/$name") {
|
||||||
val file = locator.locate("$it.frames")
|
val file = Starbound.locate("$it.frames")
|
||||||
|
|
||||||
if (file.exists) {
|
if (file.exists) {
|
||||||
try {
|
try {
|
||||||
|
@ -8,19 +8,20 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see [AtlasConfiguration.Registry.get]
|
* @see [AtlasConfiguration.Companion.get]
|
||||||
*/
|
*/
|
||||||
class ImageReference private constructor(
|
class ImageReference private constructor(
|
||||||
val raw: AssetPath,
|
val raw: AssetPath,
|
||||||
val imagePath: SBPattern,
|
val imagePath: SBPattern,
|
||||||
val spritePath: SBPattern?,
|
val spritePath: SBPattern?,
|
||||||
val atlas: AtlasConfiguration?,
|
val atlas: AtlasConfiguration?,
|
||||||
private val atlasLocator: (String) -> AtlasConfiguration?
|
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Спрайт, на которое ссылается данный референс, или `null` если:
|
* Спрайт, на которое ссылается данный референс, или `null` если:
|
||||||
@ -28,13 +29,13 @@ class ImageReference private constructor(
|
|||||||
* * [spritePath] является шаблоном и определены не все значения
|
* * [spritePath] является шаблоном и определены не все значения
|
||||||
* * [spritePath] не является правильным именем спрайта внутри [atlas] (смотрим [AtlasConfiguration.get])
|
* * [spritePath] не является правильным именем спрайта внутри [atlas] (смотрим [AtlasConfiguration.get])
|
||||||
*/
|
*/
|
||||||
val sprite by lazy(LazyThreadSafetyMode.NONE) {
|
val sprite by lazy {
|
||||||
if (atlas == null)
|
if (atlas == null)
|
||||||
null
|
null
|
||||||
else if (spritePath == null)
|
else if (spritePath == null)
|
||||||
atlas.any()
|
atlas.any()
|
||||||
else
|
else
|
||||||
atlas.get(spritePath.value ?: return@lazy null)
|
atlas[spritePath.value ?: return@lazy null]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun with(values: (String) -> String?): ImageReference {
|
fun with(values: (String) -> String?): ImageReference {
|
||||||
@ -54,11 +55,11 @@ class ImageReference private constructor(
|
|||||||
val resolved = imagePath.value
|
val resolved = imagePath.value
|
||||||
|
|
||||||
if (resolved == null)
|
if (resolved == null)
|
||||||
return ImageReference(raw, imagePath, spritePath, null, atlasLocator)
|
return ImageReference(raw, imagePath, spritePath, null)
|
||||||
else
|
else
|
||||||
return ImageReference(raw, imagePath, spritePath, atlasLocator.invoke(resolved), atlasLocator)
|
return ImageReference(raw, imagePath, spritePath, AtlasConfiguration.get(resolved))
|
||||||
} else {
|
} else {
|
||||||
return ImageReference(raw, imagePath, spritePath, atlas, atlasLocator)
|
return ImageReference(raw, imagePath, spritePath, atlas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,49 +82,42 @@ class ImageReference private constructor(
|
|||||||
return "ImageReference[$imagePath:$spritePath]"
|
return "ImageReference[$imagePath:$spritePath]"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Factory(private val atlasLocator: (String) -> AtlasConfiguration, private val remapper: PathStack) : TypeAdapterFactory {
|
companion object : TypeAdapter<ImageReference>() {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
val NEVER = ImageReference(AssetPath("", ""), SBPattern.EMPTY, null, null)
|
||||||
if (type.rawType == ImageReference::class.java) {
|
|
||||||
return object : TypeAdapter<ImageReference>() {
|
|
||||||
private val strings = gson.getAdapter(String::class.java)
|
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: ImageReference?) {
|
private val strings by lazy { Starbound.gson.getAdapter(String::class.java) }
|
||||||
out.value(value?.raw?.fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): ImageReference? {
|
override fun write(out: JsonWriter, value: ImageReference?) {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
out.value(value?.raw?.fullPath)
|
||||||
return null
|
}
|
||||||
|
|
||||||
val path = strings.read(`in`)
|
@JvmStatic
|
||||||
|
fun create(path: String): ImageReference {
|
||||||
|
if (path == "")
|
||||||
|
return NEVER
|
||||||
|
|
||||||
if (path == "")
|
val split = path.split(':')
|
||||||
return NEVER
|
|
||||||
|
|
||||||
val split = path.split(':')
|
if (split.size > 2) {
|
||||||
|
throw JsonSyntaxException("Ambiguous image reference: $path")
|
||||||
if (split.size > 2) {
|
|
||||||
throw JsonSyntaxException("Ambiguous image reference: $path")
|
|
||||||
}
|
|
||||||
|
|
||||||
val imagePath = if (split.size == 2) SBPattern.of(split[0]) else SBPattern.of(path)
|
|
||||||
val spritePath = if (split.size == 2) SBPattern.of(split[1]) else null
|
|
||||||
|
|
||||||
if (imagePath.isPlainString) {
|
|
||||||
val remapped = remapper.remap(split[0])
|
|
||||||
return ImageReference(AssetPath(path, remapper.remap(path)), SBPattern.raw(remapped), spritePath, atlasLocator.invoke(remapped), atlasLocator)
|
|
||||||
} else {
|
|
||||||
return ImageReference(AssetPath(path, path), imagePath, spritePath, null, atlasLocator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as TypeAdapter<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
val imagePath = if (split.size == 2) SBPattern.of(split[0]) else SBPattern.of(path)
|
||||||
|
val spritePath = if (split.size == 2) SBPattern.of(split[1]) else null
|
||||||
|
|
||||||
|
if (imagePath.isPlainString) {
|
||||||
|
val remapped = AssetPathStack.remap(split[0])
|
||||||
|
return ImageReference(AssetPath(path, AssetPathStack.remap(path)), SBPattern.raw(remapped), spritePath, AtlasConfiguration.get(remapped))
|
||||||
|
} else {
|
||||||
|
return ImageReference(AssetPath(path, path), imagePath, spritePath, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): ImageReference? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
return create(strings.read(`in`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
val NEVER = ImageReference(AssetPath("", ""), SBPattern.EMPTY, null, null) { null }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,41 +9,35 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
||||||
import ru.dbotthepony.kstarbound.util.PathStack
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
|
|
||||||
data class InventoryIcon(
|
data class InventoryIcon(
|
||||||
override val image: ImageReference
|
override val image: ImageReference
|
||||||
) : IInventoryIcon {
|
) : IInventoryIcon {
|
||||||
class Factory(val remapper: PathStack) : TypeAdapterFactory {
|
companion object : TypeAdapter<InventoryIcon>() {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
private val adapter by lazy { FactoryAdapter.createFor(InventoryIcon::class, JsonFactory(), Starbound.gson) }
|
||||||
if (type.rawType == InventoryIcon::class.java) {
|
private val images by lazy { Starbound.gson.getAdapter(ImageReference::class.java) }
|
||||||
return object : TypeAdapter<InventoryIcon>() {
|
|
||||||
private val adapter = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).build(gson)
|
|
||||||
private val images = gson.getAdapter(ImageReference::class.java)
|
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: InventoryIcon?) {
|
override fun write(out: JsonWriter, value: InventoryIcon?) {
|
||||||
if (value == null)
|
if (value == null)
|
||||||
out.nullValue()
|
out.nullValue()
|
||||||
else
|
else
|
||||||
adapter.write(out, value)
|
adapter.write(out, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): InventoryIcon? {
|
override fun read(`in`: JsonReader): InventoryIcon? {
|
||||||
if (`in`.peek() == JsonToken.NULL)
|
if (`in`.peek() == JsonToken.NULL)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
return InventoryIcon(images.read(JsonTreeReader(JsonPrimitive(remapper.remap(`in`.nextString())))))
|
return InventoryIcon(images.read(JsonTreeReader(JsonPrimitive(AssetPathStack.remap(`in`.nextString())))))
|
||||||
}
|
|
||||||
|
|
||||||
return adapter.read(`in`)
|
|
||||||
}
|
|
||||||
} as TypeAdapter<T>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return adapter.read(`in`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import com.google.gson.stream.JsonToken
|
|||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
|
||||||
|
|
||||||
interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefinition {
|
interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefinition {
|
||||||
@ -43,12 +44,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti
|
|||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == Frames::class.java) {
|
if (type.rawType == Frames::class.java) {
|
||||||
return object : TypeAdapter<Frames>() {
|
return object : TypeAdapter<Frames>() {
|
||||||
private val adapter = FactoryAdapter.Builder(
|
private val adapter = FactoryAdapter.createFor(Frames::class, JsonFactory(), gson)
|
||||||
Frames::class,
|
|
||||||
Frames::body,
|
|
||||||
Frames::backSleeve,
|
|
||||||
Frames::frontSleeve,
|
|
||||||
).build(gson)
|
|
||||||
|
|
||||||
private val frames = gson.getAdapter(ImageReference::class.java)
|
private val frames = gson.getAdapter(ImageReference::class.java)
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.`object`
|
||||||
|
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
|
data class Anchor(val isForeground: Boolean, val pos: Vector2i, val isTilled: Boolean, val isSoil: Boolean, val anchorMaterial: String?)
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.`object`
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.TeamType
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class DamageTeam(
|
||||||
|
val type: TeamType = TeamType.ENVIRONMENT,
|
||||||
|
val team: Int = 0
|
||||||
|
)
|
@ -1,36 +1,240 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.`object`
|
package ru.dbotthepony.kstarbound.defs.`object`
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.ItemReference
|
import ru.dbotthepony.kstarbound.defs.ItemReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.JsonReference
|
||||||
import ru.dbotthepony.kstarbound.defs.RegistryReference
|
import ru.dbotthepony.kstarbound.defs.RegistryReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.StatModifier
|
||||||
|
import ru.dbotthepony.kstarbound.defs.TouchDamage
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.listAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.stream
|
||||||
|
import ru.dbotthepony.kstarbound.math.PeriodicFunction
|
||||||
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
|
import ru.dbotthepony.kstarbound.util.contains
|
||||||
|
import ru.dbotthepony.kstarbound.util.get
|
||||||
|
import ru.dbotthepony.kstarbound.util.getArray
|
||||||
|
import ru.dbotthepony.kstarbound.util.set
|
||||||
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
|
||||||
/**
|
|
||||||
* Concrete (final) values of in-world object
|
|
||||||
*/
|
|
||||||
@JsonFactory(logMisses = false)
|
|
||||||
data class ObjectDefinition(
|
data class ObjectDefinition(
|
||||||
val objectName: String,
|
val objectName: String,
|
||||||
val objectType: ObjectType = ObjectType.OBJECT,
|
val objectType: ObjectType = ObjectType.OBJECT,
|
||||||
val race: String = "generic",
|
val race: String = "generic",
|
||||||
val category: String = "other",
|
val category: String = "other",
|
||||||
val colonyTags: ImmutableSet<String> = ImmutableSet.of(),
|
val colonyTags: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
|
||||||
val scripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
val scripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
val animationScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
val animationScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
|
|
||||||
val hasObjectItem: Boolean = true,
|
val hasObjectItem: Boolean = true,
|
||||||
val scannable: Boolean = true,
|
val scannable: Boolean = true,
|
||||||
val printable: Boolean = true,
|
|
||||||
val retainObjectParametersInItem: Boolean = false,
|
val retainObjectParametersInItem: Boolean = false,
|
||||||
|
|
||||||
val breakDropPool: RegistryReference<TreasurePoolDefinition>? = null,
|
val breakDropPool: RegistryReference<TreasurePoolDefinition>? = null,
|
||||||
|
// null - not specified, empty list - always drop nothing
|
||||||
|
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
|
||||||
|
val smashDropPool: RegistryReference<TreasurePoolDefinition>? = null,
|
||||||
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
|
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
|
||||||
|
//val animation: AssetReference<AnimationDefinition>? = null,
|
||||||
|
val animation: AssetPath? = null,
|
||||||
|
val smashSounds: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
|
val smashParticles: JsonArray? = null,
|
||||||
|
val smashable: Boolean = false,
|
||||||
|
val unbreakable: Boolean = false,
|
||||||
|
val damageShakeMagnitude: Double = 0.2,
|
||||||
|
val damageMaterialKind: String = "solid",
|
||||||
|
val damageTeam: DamageTeam = DamageTeam(),
|
||||||
|
val lightColor: RGBAColor? = null,
|
||||||
|
val lightColors: ImmutableMap<String, RGBAColor> = ImmutableMap.of(),
|
||||||
|
val pointLight: Boolean = false,
|
||||||
|
val pointBeam: Double = 0.0,
|
||||||
|
val beamAmbience: Double = 0.0,
|
||||||
|
val soundEffect: AssetPath? = null,
|
||||||
|
val soundEffectRangeMultiplier: Double = 1.0,
|
||||||
|
val price: Long = 1L,
|
||||||
|
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
|
val touchDamage: JsonReference.Object = JsonReference.Object(null, null, null),
|
||||||
|
val minimumLiquidLevel: Float? = null,
|
||||||
|
val maximumLiquidLevel: Float? = null,
|
||||||
|
val liquidCheckInterval: Float = 0.5f,
|
||||||
|
val health: Double = 1.0,
|
||||||
|
val rooting: Boolean = false,
|
||||||
|
val biomePlaced: Boolean = false,
|
||||||
|
val printable: Boolean = false,
|
||||||
|
val smashOnBreak: Boolean = false,
|
||||||
|
val damageConfig: TileDamageConfig,
|
||||||
|
val flickerPeriod: PeriodicFunction? = null,
|
||||||
|
val orientations: ImmutableList<ObjectOrientation>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
|
||||||
val animation: AssetReference<AnimationDefinition>? = null,
|
}
|
||||||
)
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() {
|
||||||
|
@JsonFactory(logMisses = false)
|
||||||
|
class PlainData(
|
||||||
|
val objectName: String,
|
||||||
|
val objectType: ObjectType = ObjectType.OBJECT,
|
||||||
|
val race: String = "generic",
|
||||||
|
val category: String = "other",
|
||||||
|
val colonyTags: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val scripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
|
val animationScripts: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
|
val hasObjectItem: Boolean = true,
|
||||||
|
val scannable: Boolean = true,
|
||||||
|
val retainObjectParametersInItem: Boolean = false,
|
||||||
|
val breakDropPool: RegistryReference<TreasurePoolDefinition>? = null,
|
||||||
|
// null - not specified, empty list - always drop nothing
|
||||||
|
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
|
||||||
|
val smashDropPool: RegistryReference<TreasurePoolDefinition>? = null,
|
||||||
|
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
|
||||||
|
//val animation: AssetReference<AnimationDefinition>? = null,
|
||||||
|
val animation: AssetPath? = null,
|
||||||
|
val smashSounds: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||||
|
val smashParticles: JsonArray? = null,
|
||||||
|
val smashable: Boolean = false,
|
||||||
|
val unbreakable: Boolean = false,
|
||||||
|
val damageShakeMagnitude: Double = 0.2,
|
||||||
|
val damageMaterialKind: String = "solid",
|
||||||
|
val damageTeam: DamageTeam = DamageTeam(),
|
||||||
|
val lightColor: RGBAColor? = null,
|
||||||
|
val lightColors: ImmutableMap<String, RGBAColor> = ImmutableMap.of(),
|
||||||
|
val pointLight: Boolean = false,
|
||||||
|
val pointBeam: Double = 0.0,
|
||||||
|
val beamAmbience: Double = 0.0,
|
||||||
|
val soundEffect: AssetPath? = null,
|
||||||
|
val soundEffectRangeMultiplier: Double = 1.0,
|
||||||
|
val price: Long = 1L,
|
||||||
|
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
|
//val touchDamage: TouchDamage,
|
||||||
|
val touchDamage: JsonReference.Object = JsonReference.Object(null, null, null),
|
||||||
|
val minimumLiquidLevel: Float? = null,
|
||||||
|
val maximumLiquidLevel: Float? = null,
|
||||||
|
val liquidCheckInterval: Float = 0.5f,
|
||||||
|
val health: Double = 1.0,
|
||||||
|
val rooting: Boolean = false,
|
||||||
|
val biomePlaced: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||||
|
private val objectRef = gson.getAdapter(JsonReference.Object::class.java)
|
||||||
|
private val basic = gson.getAdapter(PlainData::class.java)
|
||||||
|
private val damageConfig = gson.getAdapter(TileDamageConfig::class.java)
|
||||||
|
private val damageTeam = gson.getAdapter(DamageTeam::class.java)
|
||||||
|
private val orientations = gson.getAdapter(ObjectOrientation::class.java)
|
||||||
|
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
||||||
|
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: ObjectDefinition?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): ObjectDefinition? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val read = objects.read(`in`)
|
||||||
|
val basic = basic.fromJsonTree(read)
|
||||||
|
|
||||||
|
val printable = basic.hasObjectItem && read.get("printable", basic.scannable)
|
||||||
|
val smashOnBreak = read.get("smashOnBreak", basic.smashable)
|
||||||
|
|
||||||
|
val getDamageParams = objectRef.fromJsonTree(read.get("damageTable", JsonPrimitive("/objects/defaultParameters.config:damageTable")))
|
||||||
|
getDamageParams?.value ?: throw JsonSyntaxException("No valid damageTable specified")
|
||||||
|
|
||||||
|
getDamageParams.value["health"] = read["health"]
|
||||||
|
getDamageParams.value["harvestLevel"] = read["harvestLevel"]
|
||||||
|
|
||||||
|
val damageConfig = damageConfig.fromJsonTree(getDamageParams.value)
|
||||||
|
|
||||||
|
val flickerPeriod = if ("flickerPeriod" in read) {
|
||||||
|
PeriodicFunction(
|
||||||
|
read.get("flickerPeriod", 0.0),
|
||||||
|
read.get("flickerMinIntensity", 0.0),
|
||||||
|
read.get("flickerMaxIntensity", 0.0),
|
||||||
|
read.get("flickerPeriodVariance", 0.0),
|
||||||
|
read.get("flickerIntensityVariance", 0.0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val orientations = ObjectOrientation.preprocess(read.getArray("orientations"))
|
||||||
|
.stream()
|
||||||
|
.map { orientations.fromJsonTree(it) }
|
||||||
|
.collect(ImmutableList.toImmutableList())
|
||||||
|
|
||||||
|
if ("particleEmitter" in read) {
|
||||||
|
orientations.forEach { it.particleEmitters.add(emitter.fromJsonTree(read["particleEmitter"])) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("particleEmitters" in read) {
|
||||||
|
orientations.forEach { it.particleEmitters.addAll(emitters.fromJsonTree(read["particleEmitters"])) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectDefinition(
|
||||||
|
objectName = basic.objectName,
|
||||||
|
objectType = basic.objectType,
|
||||||
|
race = basic.race,
|
||||||
|
category = basic.category,
|
||||||
|
colonyTags = basic.colonyTags,
|
||||||
|
scripts = basic.scripts,
|
||||||
|
animationScripts = basic.animationScripts,
|
||||||
|
hasObjectItem = basic.hasObjectItem,
|
||||||
|
scannable = basic.scannable,
|
||||||
|
retainObjectParametersInItem = basic.retainObjectParametersInItem,
|
||||||
|
breakDropPool = basic.breakDropPool,
|
||||||
|
breakDropOptions = basic.breakDropOptions,
|
||||||
|
smashDropPool = basic.smashDropPool,
|
||||||
|
smashDropOptions = basic.smashDropOptions,
|
||||||
|
animation = basic.animation,
|
||||||
|
smashSounds = basic.smashSounds,
|
||||||
|
smashParticles = basic.smashParticles,
|
||||||
|
smashable = basic.smashable,
|
||||||
|
unbreakable = basic.unbreakable,
|
||||||
|
damageShakeMagnitude = basic.damageShakeMagnitude,
|
||||||
|
damageMaterialKind = basic.damageMaterialKind,
|
||||||
|
damageTeam = basic.damageTeam,
|
||||||
|
lightColor = basic.lightColor,
|
||||||
|
lightColors = basic.lightColors,
|
||||||
|
pointLight = basic.pointLight,
|
||||||
|
pointBeam = basic.pointBeam,
|
||||||
|
beamAmbience = basic.beamAmbience,
|
||||||
|
soundEffect = basic.soundEffect,
|
||||||
|
soundEffectRangeMultiplier = basic.soundEffectRangeMultiplier,
|
||||||
|
price = basic.price,
|
||||||
|
statusEffects = basic.statusEffects,
|
||||||
|
touchDamage = basic.touchDamage,
|
||||||
|
minimumLiquidLevel = basic.minimumLiquidLevel,
|
||||||
|
maximumLiquidLevel = basic.maximumLiquidLevel,
|
||||||
|
liquidCheckInterval = basic.liquidCheckInterval,
|
||||||
|
health = basic.health,
|
||||||
|
rooting = basic.rooting,
|
||||||
|
biomePlaced = basic.biomePlaced,
|
||||||
|
printable = printable,
|
||||||
|
smashOnBreak = smashOnBreak,
|
||||||
|
damageConfig = damageConfig,
|
||||||
|
flickerPeriod = flickerPeriod,
|
||||||
|
orientations = orientations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,262 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.`object`
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||||
|
import ru.dbotthepony.kstarbound.defs.Drawable
|
||||||
|
import ru.dbotthepony.kstarbound.defs.JsonReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.JsonArray
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.clear
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.listAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.setAdapter
|
||||||
|
import ru.dbotthepony.kstarbound.util.contains
|
||||||
|
import ru.dbotthepony.kstarbound.util.get
|
||||||
|
import ru.dbotthepony.kstarbound.util.set
|
||||||
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
|
import ru.dbotthepony.kvector.util2d.AABBi
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
import kotlin.math.PI
|
||||||
|
|
||||||
|
data class ObjectOrientation(
|
||||||
|
val json: JsonObject,
|
||||||
|
val flipImages: Boolean = false,
|
||||||
|
val drawables: ImmutableList<Drawable>,
|
||||||
|
val renderLayer: Long,
|
||||||
|
val imagePosition: Vector2f,
|
||||||
|
val frames: Int,
|
||||||
|
val animationCycle: Double,
|
||||||
|
// world tiles this object occupy while in this orientation
|
||||||
|
val occupySpaces: ImmutableSet<Vector2i>,
|
||||||
|
val boundingBox: AABBi,
|
||||||
|
val metaBoundBox: AABB?,
|
||||||
|
val anchors: ImmutableSet<Anchor>,
|
||||||
|
val anchorAny: Boolean,
|
||||||
|
val directionAffinity: Direction?,
|
||||||
|
val materialSpaces: ImmutableList<Pair<Vector2i, String>>,
|
||||||
|
val interactiveSpaces: ImmutableSet<Vector2i>,
|
||||||
|
val lightPosition: Vector2i,
|
||||||
|
val beamAngle: Double,
|
||||||
|
val statusEffectArea: Vector2d?,
|
||||||
|
val touchDamage: JsonReference.Object?,
|
||||||
|
val particleEmitters: ArrayList<ParticleEmissionEntry>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun preprocess(json: JsonArray): JsonArray {
|
||||||
|
val actual = ArrayList<JsonObject>()
|
||||||
|
|
||||||
|
for (elem in json) {
|
||||||
|
val obj = elem.asJsonObject
|
||||||
|
|
||||||
|
if ("dualImage" in obj) {
|
||||||
|
var copy = obj.deepCopy()
|
||||||
|
copy["image"] = obj["dualImage"]!!
|
||||||
|
copy["flipImages"] = true
|
||||||
|
copy["direction"] = "left"
|
||||||
|
actual.add(copy)
|
||||||
|
|
||||||
|
copy = obj.deepCopy()
|
||||||
|
copy["image"] = obj["dualImage"]!!
|
||||||
|
copy["flipImages"] = false
|
||||||
|
copy["direction"] = "right"
|
||||||
|
actual.add(copy)
|
||||||
|
} else if ("leftImage" in obj) {
|
||||||
|
require("rightImage" in obj) { "Provided leftImage, but there is no rightImage!" }
|
||||||
|
|
||||||
|
var copy = obj.deepCopy()
|
||||||
|
copy["image"] = obj["leftImage"]!!
|
||||||
|
copy["direction"] = "left"
|
||||||
|
actual.add(copy)
|
||||||
|
|
||||||
|
copy = obj.deepCopy()
|
||||||
|
copy["image"] = obj["rightImage"]!!
|
||||||
|
copy["direction"] = "right"
|
||||||
|
actual.add(copy)
|
||||||
|
} else {
|
||||||
|
actual.add(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json.clear()
|
||||||
|
actual.forEach { json.add(it) }
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<ObjectOrientation>() {
|
||||||
|
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||||
|
private val vectors = gson.getAdapter(Vector2f::class.java)
|
||||||
|
private val vectorsi = gson.getAdapter(Vector2i::class.java)
|
||||||
|
private val vectorsd = gson.getAdapter(Vector2d::class.java)
|
||||||
|
private val drawables = gson.getAdapter(Drawable::class.java)
|
||||||
|
private val aabbs = gson.getAdapter(AABB::class.java)
|
||||||
|
private val objectRefs = gson.getAdapter(JsonReference.Object::class.java)
|
||||||
|
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
||||||
|
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
||||||
|
private val spaces = gson.setAdapter<Vector2i>()
|
||||||
|
private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter<ImmutableList<Pair<Vector2i, String>>>
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: ObjectOrientation?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): ObjectOrientation? {
|
||||||
|
if (`in`.consumeNull())
|
||||||
|
return null
|
||||||
|
|
||||||
|
val obj = objects.read(`in`)
|
||||||
|
val drawables = ArrayList<Drawable>()
|
||||||
|
val flipImages = obj.get("flipImages", false)
|
||||||
|
val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object"))
|
||||||
|
|
||||||
|
if ("imageLayers" in obj) {
|
||||||
|
for (value in obj["imageLayers"].asJsonArray) {
|
||||||
|
drawables.add(this.drawables.fromJsonTree(value))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drawables.add(this.drawables.fromJsonTree(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
val imagePosition = (obj["imagePosition"]?.let { vectors.fromJsonTree(it) } ?: Vector2f.ZERO) / PIXELS_IN_STARBOUND_UNITf
|
||||||
|
val imagePositionI = (obj["imagePosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO)
|
||||||
|
val frames = obj.get("frames", 1)
|
||||||
|
val animationCycle = obj.get("animationCycle", 1.0)
|
||||||
|
var occupySpaces = obj["spaces"]?.let { spaces.fromJsonTree(it) } ?: ImmutableSet.of(Vector2i.ZERO)
|
||||||
|
|
||||||
|
if ("spaceScan" in obj) {
|
||||||
|
try {
|
||||||
|
for (drawable in drawables) {
|
||||||
|
if (drawable is Drawable.Image) {
|
||||||
|
val bound = drawable.path.with { "default" }
|
||||||
|
val sprite = bound.sprite ?: throw IllegalStateException("Not a valid sprite reference: ${bound.raw} (${bound.imagePath} / ${bound.spritePath})")
|
||||||
|
val image = Starbound.imageData(bound.imagePath.value ?: throw IllegalStateException("Incomplete image path: ${bound.imagePath}"))
|
||||||
|
val width = sprite.width(image.width)
|
||||||
|
val height = sprite.height(image.height)
|
||||||
|
|
||||||
|
if (width != image.width || height != image.height) {
|
||||||
|
//throw NotImplementedError("Space scan of sprite is not supported yet")
|
||||||
|
occupySpaces = ImmutableSet.of()
|
||||||
|
} else {
|
||||||
|
val new = ImmutableSet.Builder<Vector2i>()
|
||||||
|
new.addAll(occupySpaces)
|
||||||
|
new.addAll(image.worldSpaces(imagePositionI, obj["spaceScan"].asDouble, flipImages))
|
||||||
|
occupySpaces = new.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
throw JsonSyntaxException("Unable to space scan image", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var boundingBox = AABBi(Vector2i.ZERO, Vector2i.ZERO)
|
||||||
|
|
||||||
|
for (vec in occupySpaces) {
|
||||||
|
boundingBox = boundingBox.expand(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) }
|
||||||
|
val requireTilledAnchors = obj.get("requireTilledAnchors", false)
|
||||||
|
val requireSoilAnchors = obj.get("requireSoilAnchors", false)
|
||||||
|
val anchorMaterial = obj["anchorMaterial"]?.asString
|
||||||
|
val anchors = ImmutableSet.Builder<Anchor>()
|
||||||
|
|
||||||
|
for (v in obj.get("anchors", JsonArray())) {
|
||||||
|
when (v.asString.lowercase()) {
|
||||||
|
"left" -> occupySpaces.stream().filter { it.x == boundingBox.mins.x }.forEach { anchors.add(Anchor(true, it + Vector2i.NEGATIVE_X, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) }
|
||||||
|
"right" -> occupySpaces.stream().filter { it.x == boundingBox.maxs.x }.forEach { anchors.add(Anchor(true, it + Vector2i.POSITIVE_X, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) }
|
||||||
|
"top" -> occupySpaces.stream().filter { it.y == boundingBox.maxs.y }.forEach { anchors.add(Anchor(true, it + Vector2i.POSITIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) }
|
||||||
|
"bottom" -> occupySpaces.stream().filter { it.y == boundingBox.maxs.y }.forEach { anchors.add(Anchor(true, it + Vector2i.NEGATIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) }
|
||||||
|
"background" -> occupySpaces.forEach { anchors.add(Anchor(false, it + Vector2i.NEGATIVE_Y, requireTilledAnchors, requireSoilAnchors, anchorMaterial)) }
|
||||||
|
else -> throw JsonSyntaxException("Unknown anchor type $v")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (v in obj.get("bgAnchors", JsonArray()))
|
||||||
|
anchors.add(Anchor(false, vectorsi.fromJsonTree(v), requireTilledAnchors, requireSoilAnchors, anchorMaterial))
|
||||||
|
|
||||||
|
for (v in obj.get("fgAnchors", JsonArray()))
|
||||||
|
anchors.add(Anchor(true, vectorsi.fromJsonTree(v), requireTilledAnchors, requireSoilAnchors, anchorMaterial))
|
||||||
|
|
||||||
|
val anchorAny = obj["anchorAny"]?.asBoolean ?: false
|
||||||
|
val directionAffinity = obj["directionAffinity"]?.asString?.uppercase()?.let { Direction.valueOf(it) }
|
||||||
|
val materialSpaces: ImmutableList<Pair<Vector2i, String>>
|
||||||
|
|
||||||
|
if ("materialSpaces" in obj) {
|
||||||
|
materialSpaces = this.materialSpaces.fromJsonTree(obj["materialSpaces"])
|
||||||
|
} else {
|
||||||
|
val collisionSpaces = obj["collisionSpaces"]?.let { this.spaces.fromJsonTree(it) } ?: occupySpaces
|
||||||
|
val builder = ImmutableList.Builder<Pair<Vector2i, String>>()
|
||||||
|
|
||||||
|
when (val collisionType = obj.get("collisionType", "none").lowercase()) {
|
||||||
|
"solid" -> collisionSpaces.forEach { builder.add(it to BuiltinMetaMaterials.OBJECT_SOLID.materialName) }
|
||||||
|
"platform" -> collisionSpaces.forEach { if (it.y == boundingBox.maxs.y) builder.add(it to BuiltinMetaMaterials.OBJECT_PLATFORM.materialName) }
|
||||||
|
"none" -> {}
|
||||||
|
else -> throw JsonSyntaxException("Unknown collision type $collisionType")
|
||||||
|
}
|
||||||
|
|
||||||
|
materialSpaces = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val interactiveSpaces = obj["interactiveSpaces"]?.let { this.spaces.fromJsonTree(it) } ?: occupySpaces
|
||||||
|
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
|
||||||
|
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
|
||||||
|
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
|
||||||
|
val touchDamage = obj["touchDamage"]?.let { objectRefs.fromJsonTree(it) }
|
||||||
|
|
||||||
|
val emitters = ArrayList<ParticleEmissionEntry>()
|
||||||
|
|
||||||
|
if ("particleEmitter" in obj) {
|
||||||
|
emitters.add(this.emitter.fromJsonTree(obj["particleEmitter"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("particleEmitters" in obj) {
|
||||||
|
emitters.addAll(this.emitters.fromJsonTree(obj["particleEmitters"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectOrientation(
|
||||||
|
obj,
|
||||||
|
flipImages = flipImages,
|
||||||
|
drawables = ImmutableList.copyOf(drawables),
|
||||||
|
renderLayer = renderLayer,
|
||||||
|
imagePosition = imagePosition,
|
||||||
|
frames = frames,
|
||||||
|
animationCycle = animationCycle,
|
||||||
|
occupySpaces = occupySpaces,
|
||||||
|
boundingBox = boundingBox,
|
||||||
|
metaBoundBox = metaBoundBox,
|
||||||
|
anchors = anchors.build(),
|
||||||
|
anchorAny = anchorAny,
|
||||||
|
directionAffinity = directionAffinity,
|
||||||
|
materialSpaces = materialSpaces,
|
||||||
|
interactiveSpaces = interactiveSpaces,
|
||||||
|
lightPosition = lightPosition,
|
||||||
|
beamAngle = beamAngle,
|
||||||
|
statusEffectArea = statusEffectArea,
|
||||||
|
touchDamage = touchDamage,
|
||||||
|
particleEmitters = emitters,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.`object`
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ParticleEmissionEntry(
|
||||||
|
val emissionRate: Double = 0.0,
|
||||||
|
val emissionVariance: Double = 0.0,
|
||||||
|
val placeInSpaces: Boolean = false,
|
||||||
|
)
|
@ -0,0 +1,55 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.tile
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.CollisionType
|
||||||
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
|
|
||||||
|
object BuiltinMetaMaterials {
|
||||||
|
private fun make(id: Int, name: String, collisionType: CollisionType) = TileDefinition(
|
||||||
|
materialId = id,
|
||||||
|
materialName = "metamaterial:$name",
|
||||||
|
descriptionData = ThingDescription.EMPTY,
|
||||||
|
category = "meta",
|
||||||
|
renderTemplate = AssetReference.empty(),
|
||||||
|
renderParameters = RenderParameters.META,
|
||||||
|
isMeta = true,
|
||||||
|
collisionKind = collisionType
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* air
|
||||||
|
*/
|
||||||
|
val EMPTY = make(65535, "empty", CollisionType.NONE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* not set / out of bounds
|
||||||
|
*/
|
||||||
|
val NULL = make(65534, "null", CollisionType.BLOCK)
|
||||||
|
|
||||||
|
val STRUCTURE = make(65533, "structure", CollisionType.BLOCK)
|
||||||
|
val BIOME = make(65527, "biome", CollisionType.BLOCK)
|
||||||
|
val BIOME1 = make(65528, "biome1", CollisionType.BLOCK)
|
||||||
|
val BIOME2 = make(65529, "biome2", CollisionType.BLOCK)
|
||||||
|
val BIOME3 = make(65530, "biome3", CollisionType.BLOCK)
|
||||||
|
val BIOME4 = make(65531, "biome4", CollisionType.BLOCK)
|
||||||
|
val BIOME5 = make(65532, "biome5", CollisionType.BLOCK)
|
||||||
|
val BOUNDARY = make(65526, "boundary", CollisionType.SLIPPERY)
|
||||||
|
val OBJECT_SOLID = make(65500, "objectsolid", CollisionType.BLOCK)
|
||||||
|
val OBJECT_PLATFORM = make(65501, "objectplatform", CollisionType.PLATFORM)
|
||||||
|
|
||||||
|
val MATERIALS: ImmutableList<TileDefinition> = ImmutableList.of(
|
||||||
|
EMPTY,
|
||||||
|
NULL,
|
||||||
|
STRUCTURE,
|
||||||
|
BIOME,
|
||||||
|
BIOME1,
|
||||||
|
BIOME2,
|
||||||
|
BIOME3,
|
||||||
|
BIOME4,
|
||||||
|
BIOME5,
|
||||||
|
BOUNDARY,
|
||||||
|
OBJECT_SOLID,
|
||||||
|
OBJECT_PLATFORM,
|
||||||
|
)
|
||||||
|
}
|
@ -2,17 +2,24 @@ package ru.dbotthepony.kstarbound.defs.tile
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
import ru.dbotthepony.kstarbound.defs.image.ImageReference
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonNotNull
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class RenderParameters(
|
data class RenderParameters(
|
||||||
val texture: ImageReference,
|
@JsonNotNull
|
||||||
|
val texture: ImageReference?,
|
||||||
val variants: Int = 0,
|
val variants: Int = 0,
|
||||||
val multiColored: Boolean = false,
|
val multiColored: Boolean = false,
|
||||||
val occludesBelow: Boolean = false,
|
val occludesBelow: Boolean = false,
|
||||||
val lightTransparent: Boolean = false,
|
val lightTransparent: Boolean = false,
|
||||||
val zLevel: Int,
|
val zLevel: Long,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
checkNotNull(texture.imagePath.value) { "Tile render parameters are stateless, but provided image is a pattern: ${texture.raw}" }
|
if (texture != null)
|
||||||
|
checkNotNull(texture.imagePath.value) { "Tile render parameters are stateless, but provided image is a pattern: ${texture.raw}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val META = RenderParameters(null, zLevel = 0, lightTransparent = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs.tile
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2DoubleMap
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
class TileDamageConfig(
|
||||||
|
val damageFactors: Object2DoubleMap<String>,
|
||||||
|
val damageRecovery: Double,
|
||||||
|
val maximumEffectTime: Double,
|
||||||
|
val health: Double? = null,
|
||||||
|
val harvestLevel: Int? = null
|
||||||
|
)
|
@ -2,10 +2,12 @@ package ru.dbotthepony.kstarbound.defs.tile
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.CollisionType
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFlat
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonIgnore
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
@ -26,6 +28,13 @@ data class TileDefinition(
|
|||||||
@JsonFlat
|
@JsonFlat
|
||||||
val descriptionData: ThingDescription,
|
val descriptionData: ThingDescription,
|
||||||
|
|
||||||
|
// meta tiles are treated uniquely by many systems
|
||||||
|
// such as players/projectiles unable to break them, etc
|
||||||
|
@JsonIgnore
|
||||||
|
val isMeta: Boolean = false,
|
||||||
|
|
||||||
|
val collisionKind: CollisionType = CollisionType.BLOCK,
|
||||||
|
|
||||||
override val renderTemplate: AssetReference<RenderTemplate>,
|
override val renderTemplate: AssetReference<RenderTemplate>,
|
||||||
override val renderParameters: RenderParameters,
|
override val renderParameters: RenderParameters,
|
||||||
) : IRenderableTile, IThingWithDescription by descriptionData
|
) : IRenderableTile, IThingWithDescription by descriptionData
|
||||||
|
@ -13,10 +13,6 @@ import com.google.gson.stream.JsonWriter
|
|||||||
import ru.dbotthepony.kstarbound.util.Either
|
import ru.dbotthepony.kstarbound.util.Either
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
/**
|
|
||||||
* При объявлении [Either] для (де)сериализации *НЕОБХОДИМО* объявить
|
|
||||||
* такое *левое* свойство, которое имеет [TypeAdapter] который не "засоряет" [JsonReader]
|
|
||||||
*/
|
|
||||||
object EitherTypeAdapter : TypeAdapterFactory {
|
object EitherTypeAdapter : TypeAdapterFactory {
|
||||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
if (type.rawType == Either::class.java) {
|
if (type.rawType == Either::class.java) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.io.json
|
package ru.dbotthepony.kstarbound.io.json
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
@ -13,6 +15,7 @@ import com.google.gson.reflect.TypeToken
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonToken
|
import com.google.gson.stream.JsonToken
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
|
|
||||||
fun <T> TypeAdapter<T>.transformRead(transformer: (T) -> T): TypeAdapter<T> {
|
fun <T> TypeAdapter<T>.transformRead(transformer: (T) -> T): TypeAdapter<T> {
|
||||||
return object : TypeAdapter<T>() {
|
return object : TypeAdapter<T>() {
|
||||||
@ -130,12 +133,43 @@ fun JsonWriter.value(element: JsonObject) {
|
|||||||
endObject()
|
endObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun JsonWriter.value(element: JsonElement) {
|
fun JsonWriter.value(element: JsonElement?) {
|
||||||
when (element) {
|
when (element) {
|
||||||
is JsonPrimitive -> value(element)
|
is JsonPrimitive -> value(element)
|
||||||
is JsonNull -> value(element)
|
is JsonNull -> value(element)
|
||||||
is JsonArray -> value(element)
|
is JsonArray -> value(element)
|
||||||
is JsonObject -> value(element)
|
is JsonObject -> value(element)
|
||||||
|
null -> nullValue()
|
||||||
else -> throw IllegalArgumentException(element.toString())
|
else -> throw IllegalArgumentException(element.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified C : Collection<E>, reified E> Gson.collectionAdapter(): TypeAdapter<C> {
|
||||||
|
return getAdapter(TypeToken.getParameterized(C::class.java, E::class.java)) as TypeAdapter<C>
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E> Gson.listAdapter(): TypeAdapter<ImmutableList<E>> {
|
||||||
|
return collectionAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E> Gson.mutableListAdapter(): TypeAdapter<ArrayList<E>> {
|
||||||
|
return collectionAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E> Gson.setAdapter(): TypeAdapter<ImmutableSet<E>> {
|
||||||
|
return collectionAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified E> Gson.mutableSetAdapter(): TypeAdapter<ObjectOpenHashSet<E>> {
|
||||||
|
return collectionAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonArray(elements: Collection<JsonElement>): JsonArray {
|
||||||
|
return JsonArray(elements.size).also { elements.forEach(it::add) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonArray.clear() {
|
||||||
|
while (size() > 0) {
|
||||||
|
remove(size() - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.io.json
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.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.JsonWriter
|
||||||
|
import it.unimi.dsi.fastutil.objects.*
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
class FastutilTypeAdapterFactory(private val interner: Interner<String>) : TypeAdapterFactory {
|
||||||
|
private fun map1(gson: Gson, type: TypeToken<*>, typeValue: TypeToken<*>, factoryHash: () -> Map<Any?, Any?>, factoryTree: () -> Map<Any?, Any?>): TypeAdapter<MutableMap<Any?, Any?>>? {
|
||||||
|
val p = type.type as? ParameterizedType ?: return null
|
||||||
|
val typeKey = TypeToken.get(p.actualTypeArguments[0])
|
||||||
|
val adapterK = gson.getAdapter(typeKey) as TypeAdapter<Any?>
|
||||||
|
val adapterV = gson.getAdapter(typeValue) as TypeAdapter<Any?>
|
||||||
|
|
||||||
|
if (typeKey.isAssignableFrom(String::class.java)) {
|
||||||
|
return object : TypeAdapter<MutableMap<Any?, Any?>>() {
|
||||||
|
override fun write(out: JsonWriter, value: MutableMap<Any?, Any?>?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
out.beginObject()
|
||||||
|
|
||||||
|
for ((k, v) in value.entries) {
|
||||||
|
out.name(k as String)
|
||||||
|
adapterV.write(out, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.endObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): MutableMap<Any?, Any?>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
`in`.beginObject()
|
||||||
|
|
||||||
|
val result = factoryTree.invoke() as MutableMap<Any?, Any?>
|
||||||
|
|
||||||
|
while (`in`.hasNext()) {
|
||||||
|
result[interner.intern(`in`.nextName())] = adapterV.read(`in`)
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endObject()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val factory = if (TypeToken.get(Comparable::class.java).isAssignableFrom(typeKey)) factoryTree else factoryHash
|
||||||
|
|
||||||
|
return object : TypeAdapter<MutableMap<Any?, Any?>>() {
|
||||||
|
override fun write(out: JsonWriter, value: MutableMap<Any?, Any?>?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
out.beginArray()
|
||||||
|
|
||||||
|
for ((k, v) in value.entries) {
|
||||||
|
adapterK.write(out, k)
|
||||||
|
adapterV.write(out, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): MutableMap<Any?, Any?>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
`in`.beginArray()
|
||||||
|
|
||||||
|
val result = factory.invoke() as MutableMap<Any?, Any?>
|
||||||
|
|
||||||
|
while (`in`.hasNext()) {
|
||||||
|
`in`.beginArray()
|
||||||
|
result[adapterK.read(`in`)] = adapterV.read(`in`)
|
||||||
|
`in`.endArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endArray()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
return when (type.rawType) {
|
||||||
|
Object2IntMap::class.java -> map1(gson, type, TypeToken.get(Int::class.java), ::Object2IntOpenHashMap, ::Object2IntAVLTreeMap)
|
||||||
|
Object2ShortMap::class.java -> map1(gson, type, TypeToken.get(Short::class.java), ::Object2ShortOpenHashMap, ::Object2ShortAVLTreeMap)
|
||||||
|
Object2LongMap::class.java -> map1(gson, type, TypeToken.get(Long::class.java), ::Object2LongOpenHashMap, ::Object2LongAVLTreeMap)
|
||||||
|
Object2BooleanMap::class.java -> map1(gson, type, TypeToken.get(Boolean::class.java), ::Object2BooleanOpenHashMap, ::Object2BooleanAVLTreeMap)
|
||||||
|
Object2ByteMap::class.java -> map1(gson, type, TypeToken.get(Byte::class.java), ::Object2ByteOpenHashMap, ::Object2ByteAVLTreeMap)
|
||||||
|
Object2CharMap::class.java -> map1(gson, type, TypeToken.get(Char::class.java), ::Object2CharOpenHashMap, ::Object2CharAVLTreeMap)
|
||||||
|
Object2DoubleMap::class.java -> map1(gson, type, TypeToken.get(Double::class.java), ::Object2DoubleOpenHashMap, ::Object2DoubleAVLTreeMap)
|
||||||
|
else -> null
|
||||||
|
} as TypeAdapter<T>?
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.io.json
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
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 ru.dbotthepony.kstarbound.util.OneOf
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
object OneOfTypeAdapter : TypeAdapterFactory {
|
||||||
|
private class Adapter<A, B, C>(gson: Gson, a: TypeToken<A>, b: TypeToken<B>, c: TypeToken<C>) : TypeAdapter<OneOf<A, B, C>>() {
|
||||||
|
private val adapter0 = gson.getAdapter(a)
|
||||||
|
private val adapter1 = gson.getAdapter(b)
|
||||||
|
private val adapter2 = gson.getAdapter(c)
|
||||||
|
private val elements = gson.getAdapter(JsonElement::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: OneOf<A, B, C>?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
value.v0.ifPresent { adapter0.write(out, it) }
|
||||||
|
value.v1.ifPresent { adapter1.write(out, it) }
|
||||||
|
value.v2.ifPresent { adapter2.write(out, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): OneOf<A, B, C>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
val read = elements.read(`in`)
|
||||||
|
val errors = ArrayList<Throwable>(3)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return OneOf.first(adapter0.fromJsonTree(read))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
errors.add(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return OneOf.second(adapter1.fromJsonTree(read))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
errors.add(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return OneOf.third(adapter2.fromJsonTree(read))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
errors.add(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw JsonSyntaxException("None of type adapters consumed the input: $read").also { errors.forEach(it::addSuppressed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type.rawType === OneOf::class.java) {
|
||||||
|
val p = type.type as? ParameterizedType ?: return null
|
||||||
|
val (a, b, c) = p.actualTypeArguments
|
||||||
|
|
||||||
|
return Adapter(gson, TypeToken.get(a), TypeToken.get(b), TypeToken.get(c)) as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,13 @@ import com.google.gson.TypeAdapterFactory
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strictly prohibits deserializing target field as null from JSON
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class JsonNotNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Заставляет указанное свойство быть проигнорированным при автоматическом создании [BuilderAdapter]
|
* Заставляет указанное свойство быть проигнорированным при автоматическом создании [BuilderAdapter]
|
||||||
*/
|
*/
|
||||||
@ -58,17 +65,8 @@ annotation class JsonBuilder
|
|||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class JsonFactory(
|
annotation class JsonFactory(
|
||||||
/**
|
|
||||||
* @see FactoryAdapter.Builder.storesJson
|
|
||||||
*/
|
|
||||||
val storesJson: Boolean = false,
|
val storesJson: Boolean = false,
|
||||||
|
|
||||||
/**
|
|
||||||
* @see FactoryAdapter.Builder.inputAsList
|
|
||||||
* @see FactoryAdapter.Builder.inputAsMap
|
|
||||||
*/
|
|
||||||
val asList: Boolean = false,
|
val asList: Boolean = false,
|
||||||
|
|
||||||
val logMisses: Boolean = true,
|
val logMisses: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
/**
|
/**
|
||||||
* Свойства объекта [T], которые можно выставлять
|
* Свойства объекта [T], которые можно выставлять
|
||||||
*/
|
*/
|
||||||
val properties: ImmutableMap<String, IResolvedMutableProperty<T, *>>,
|
val properties: ImmutableMap<String, ReferencedMutableProperty<T, *>>,
|
||||||
|
|
||||||
val stringInterner: Interner<String> = Interner { it },
|
val stringInterner: Interner<String> = Interner { it },
|
||||||
) : TypeAdapter<T>() {
|
) : TypeAdapter<T>() {
|
||||||
@ -55,7 +55,7 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
// загружаем указатели на стек
|
// загружаем указатели на стек
|
||||||
val properties = properties
|
val properties = properties
|
||||||
|
|
||||||
val missing = ObjectOpenHashSet<IResolvedMutableProperty<T, *>>()
|
val missing = ObjectOpenHashSet<ReferencedMutableProperty<T, *>>()
|
||||||
missing.addAll(properties.values)
|
missing.addAll(properties.values)
|
||||||
|
|
||||||
val instance = factory.invoke()
|
val instance = factory.invoke()
|
||||||
@ -157,7 +157,7 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapterFactory {
|
class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapterFactory {
|
||||||
private val properties = ArrayList<IResolvableMutableProperty<T, *>>()
|
private val properties = ArrayList<ReferencedMutableProperty<T, *>>()
|
||||||
private val factoryReturnType by lazy { factory.invoke()::class.java }
|
private val factoryReturnType by lazy { factory.invoke()::class.java }
|
||||||
|
|
||||||
var stringInterner: Interner<String> = Interner { it }
|
var stringInterner: Interner<String> = Interner { it }
|
||||||
@ -170,10 +170,10 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun build(gson: Gson): BuilderAdapter<T> {
|
fun build(gson: Gson): BuilderAdapter<T> {
|
||||||
val map = ImmutableMap.Builder<String, IResolvedMutableProperty<T, *>>()
|
val map = ImmutableMap.Builder<String, ReferencedMutableProperty<T, *>>()
|
||||||
|
|
||||||
for (property in properties)
|
for (property in properties)
|
||||||
map.put(property.property.name, property.resolve(gson))
|
map.put(property.property.name, property.also { it.resolve(gson) })
|
||||||
|
|
||||||
return BuilderAdapter(
|
return BuilderAdapter(
|
||||||
factory = factory,
|
factory = factory,
|
||||||
@ -191,7 +191,7 @@ class BuilderAdapter<T : Any> private constructor(
|
|||||||
throw IllegalArgumentException("Property $property is defined twice")
|
throw IllegalArgumentException("Property $property is defined twice")
|
||||||
}
|
}
|
||||||
|
|
||||||
properties.add(ResolvableMutableProperty(
|
properties.add(ReferencedMutableProperty(
|
||||||
property = property,
|
property = property,
|
||||||
mustBePresent = mustBePresent,
|
mustBePresent = mustBePresent,
|
||||||
isFlat = isFlat,
|
isFlat = isFlat,
|
||||||
|
@ -22,6 +22,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
|
|||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.util.enrollList
|
import ru.dbotthepony.kstarbound.defs.util.enrollList
|
||||||
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
import ru.dbotthepony.kstarbound.defs.util.enrollMap
|
||||||
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
|
||||||
@ -41,7 +42,7 @@ import kotlin.reflect.full.primaryConstructor
|
|||||||
*/
|
*/
|
||||||
class FactoryAdapter<T : Any> private constructor(
|
class FactoryAdapter<T : Any> private constructor(
|
||||||
val clazz: KClass<T>,
|
val clazz: KClass<T>,
|
||||||
val types: ImmutableList<IResolvedProperty<T, *>>,
|
val types: ImmutableList<ReferencedProperty<T, *>>,
|
||||||
aliases: Map<String, String>,
|
aliases: Map<String, String>,
|
||||||
val asJsonArray: Boolean,
|
val asJsonArray: Boolean,
|
||||||
val storesJson: Boolean,
|
val storesJson: Boolean,
|
||||||
@ -283,6 +284,15 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tuple.isIgnored) {
|
||||||
|
if (loggedMisses.add(name)) {
|
||||||
|
LOGGER.warn("${clazz.qualifiedName} can not load $name from JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.skipValue()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
val (field, adapter) = tuple
|
val (field, adapter) = tuple
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -380,7 +390,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
class Builder<T : Any>(val clazz: KClass<T>, vararg fields: KProperty1<T, *>) : TypeAdapterFactory {
|
class Builder<T : Any>(val clazz: KClass<T>, vararg fields: KProperty1<T, *>) : TypeAdapterFactory {
|
||||||
private var asList = false
|
private var asList = false
|
||||||
private var storesJson = false
|
private var storesJson = false
|
||||||
private val types = ArrayList<IResolvableProperty<T, *>>()
|
private val types = ArrayList<ReferencedProperty<T, *>>()
|
||||||
private val aliases = Object2ObjectArrayMap<String, String>()
|
private val aliases = Object2ObjectArrayMap<String, String>()
|
||||||
var stringInterner: Interner<String> = Interner { it }
|
var stringInterner: Interner<String> = Interner { it }
|
||||||
var logMisses = true
|
var logMisses = true
|
||||||
@ -407,7 +417,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
|
|
||||||
return FactoryAdapter(
|
return FactoryAdapter(
|
||||||
clazz = clazz,
|
clazz = clazz,
|
||||||
types = ImmutableList.copyOf(types.map { it.resolve(gson) }),
|
types = ImmutableList.copyOf(types.also { it.forEach{ it.resolve(gson) } }),
|
||||||
asJsonArray = asList,
|
asJsonArray = asList,
|
||||||
storesJson = storesJson,
|
storesJson = storesJson,
|
||||||
stringInterner = stringInterner,
|
stringInterner = stringInterner,
|
||||||
@ -429,8 +439,8 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <V> add(field: KProperty1<T, V>, isFlat: Boolean = false, transform: (TypeAdapter<V>) -> TypeAdapter<V> = { it }): Builder<T> {
|
fun <V> add(field: KProperty1<T, V>, isFlat: Boolean = false, isMarkedNullable: Boolean? = null): Builder<T> {
|
||||||
types.add(ResolvableProperty(field, isFlat = isFlat, transform = transform))
|
types.add(ReferencedProperty(field, isFlat = isFlat, isMarkedNullable = isMarkedNullable))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +487,7 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory, gson: Gson, stringInterner: Interner<String> = Interner { it }): TypeAdapter<T> {
|
fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory, gson: Gson, stringInterner: Interner<String> = Starbound.strings): TypeAdapter<T> {
|
||||||
val builder = Builder(kclass)
|
val builder = Builder(kclass)
|
||||||
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()
|
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()
|
||||||
|
|
||||||
@ -501,7 +511,12 @@ class FactoryAdapter<T : Any> private constructor(
|
|||||||
for (i in 0 until lastIndex) {
|
for (i in 0 until lastIndex) {
|
||||||
val argument = params[i]
|
val argument = params[i]
|
||||||
val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) }
|
val property = properties.first { it.name == argument.name && it.returnType.isSupertypeOf(argument.type) }
|
||||||
builder.add(property, isFlat = property.annotations.any { it.annotationClass == JsonFlat::class })
|
|
||||||
|
builder.add(
|
||||||
|
property,
|
||||||
|
isFlat = property.annotations.any { it.annotationClass == JsonFlat::class },
|
||||||
|
isMarkedNullable = if (property.annotations.any { it.annotationClass == JsonNotNull::class }) false else null,
|
||||||
|
)
|
||||||
|
|
||||||
property.annotations.firstOrNull { it.annotationClass == JsonAlias::class }?.let {
|
property.annotations.firstOrNull { it.annotationClass == JsonAlias::class }?.let {
|
||||||
it as JsonAlias
|
it as JsonAlias
|
||||||
|
@ -3,119 +3,47 @@ package ru.dbotthepony.kstarbound.io.json.builder
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import kotlin.properties.Delegates
|
||||||
import kotlin.reflect.KMutableProperty1
|
import kotlin.reflect.KMutableProperty1
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.javaType
|
import kotlin.reflect.javaType
|
||||||
|
|
||||||
interface IProperty {
|
open class ReferencedProperty<T : Any, V>(
|
||||||
val isFlat: Boolean
|
property: KProperty1<T, V>,
|
||||||
val name: String
|
val isFlat: Boolean,
|
||||||
val mustBePresent: Boolean?
|
val mustBePresent: Boolean? = null,
|
||||||
}
|
val isIgnored: Boolean = false,
|
||||||
|
isMarkedNullable: Boolean? = null,
|
||||||
|
) {
|
||||||
|
@Suppress("CanBePrimaryConstructorProperty")
|
||||||
|
open val property: KProperty1<T, V> = property
|
||||||
|
|
||||||
interface IReferencedProperty<T : Any, V> : IProperty {
|
val name: String get() = property.name
|
||||||
val property: KProperty1<T, V>
|
var adapter: TypeAdapter<V> by Delegates.notNull()
|
||||||
override val name: String get() = property.name
|
private set
|
||||||
override val mustBePresent: Boolean?
|
|
||||||
get() = null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IResolvableProperty<T : Any, V> : IReferencedProperty<T, V> {
|
val type: KType = property.returnType
|
||||||
fun resolve(gson: Gson?): IResolvedProperty<T, V>
|
val isMarkedNullable: Boolean = isMarkedNullable?.also {
|
||||||
}
|
if (it && !type.isMarkedNullable) throw IllegalArgumentException("Can't declare non-null property as nullable")
|
||||||
|
} ?: type.isMarkedNullable
|
||||||
interface IResolvedProperty<T : Any, V> : IReferencedProperty<T, V> {
|
|
||||||
val type: KType
|
|
||||||
val adapter: TypeAdapter<V>
|
|
||||||
val isMarkedNullable: Boolean
|
|
||||||
|
|
||||||
operator fun component1() = property
|
|
||||||
operator fun component2() = adapter
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResolvedProperty<T : Any, V>(
|
|
||||||
override val property: KProperty1<T, V>,
|
|
||||||
override val adapter: TypeAdapter<V>,
|
|
||||||
override val isFlat: Boolean
|
|
||||||
) : IResolvableProperty<T, V>, IResolvedProperty<T, V> {
|
|
||||||
override val type: KType = property.returnType
|
|
||||||
override val isMarkedNullable: Boolean = type.isMarkedNullable
|
|
||||||
|
|
||||||
override fun resolve(gson: Gson?): IResolvedProperty<T, V> {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResolvableProperty<T : Any, V>(
|
|
||||||
override val property: KProperty1<T, V>,
|
|
||||||
override val isFlat: Boolean,
|
|
||||||
val transform: (TypeAdapter<V>) -> TypeAdapter<V> = { it }
|
|
||||||
) : IResolvableProperty<T, V> {
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
|
||||||
override fun resolve(gson: Gson?): IResolvedProperty<T, V> {
|
|
||||||
gson ?: throw NullPointerException("Can not resolve without Gson present")
|
|
||||||
|
|
||||||
return ResolvedProperty(
|
|
||||||
property = property,
|
|
||||||
adapter = transform(gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter<V>),
|
|
||||||
isFlat = isFlat
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IReferencedMutableProperty<T : Any, V> : IProperty {
|
|
||||||
val property: KMutableProperty1<T, V>
|
|
||||||
override val name: String get() = property.name
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IResolvableMutableProperty<T : Any, V> : IReferencedMutableProperty<T, V> {
|
|
||||||
fun resolve(gson: Gson?): IResolvedMutableProperty<T, V>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IResolvedMutableProperty<T : Any, V> : IResolvableMutableProperty<T, V> {
|
|
||||||
val type: KType
|
|
||||||
val adapter: TypeAdapter<V>
|
|
||||||
val isMarkedNullable: Boolean
|
|
||||||
|
|
||||||
operator fun component1() = property
|
operator fun component1() = property
|
||||||
operator fun component2() = adapter
|
operator fun component2() = adapter
|
||||||
|
|
||||||
@Suppress("unchecked_cast")
|
private var isResolved = false
|
||||||
fun set(receiver: T, value: Any?) {
|
|
||||||
property.set(receiver, value as V)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResolvedMutableProperty<T : Any, V>(
|
|
||||||
override val property: KMutableProperty1<T, V>,
|
|
||||||
override val adapter: TypeAdapter<V>,
|
|
||||||
override val isFlat: Boolean,
|
|
||||||
override val mustBePresent: Boolean?
|
|
||||||
) : IResolvableMutableProperty<T, V>, IResolvedMutableProperty<T, V> {
|
|
||||||
override val type: KType = property.returnType
|
|
||||||
override val isMarkedNullable: Boolean = type.isMarkedNullable
|
|
||||||
|
|
||||||
override fun resolve(gson: Gson?): IResolvedMutableProperty<T, V> {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResolvableMutableProperty<T : Any, V>(
|
|
||||||
override val property: KMutableProperty1<T, V>,
|
|
||||||
override val isFlat: Boolean,
|
|
||||||
val transform: (TypeAdapter<V>) -> TypeAdapter<V> = { it },
|
|
||||||
override val mustBePresent: Boolean?
|
|
||||||
) : IResolvableMutableProperty<T, V> {
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
override fun resolve(gson: Gson?): IResolvedMutableProperty<T, V> {
|
fun resolve(gson: Gson) {
|
||||||
gson ?: throw NullPointerException("Can not resolve without Gson present")
|
if (!isResolved) {
|
||||||
|
isResolved = true
|
||||||
return ResolvedMutableProperty(
|
adapter = gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter<V>
|
||||||
property = property,
|
}
|
||||||
adapter = transform(gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter<V>),
|
}
|
||||||
isFlat = isFlat,
|
}
|
||||||
mustBePresent = mustBePresent,
|
|
||||||
)
|
class ReferencedMutableProperty<T : Any, V>(override val property: KMutableProperty1<T, V>, isFlat: Boolean, mustBePresent: Boolean?) : ReferencedProperty<T, V>(property, isFlat, mustBePresent) {
|
||||||
|
fun set(instance: T, value: Any?) {
|
||||||
|
(this.property as KMutableProperty1<T, Any?>).set(instance, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.io.json.factory
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonToken
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kstarbound.io.json.consumeNull
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
object PairAdapterFactory : TypeAdapterFactory {
|
||||||
|
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type.rawType == Pair::class.java) {
|
||||||
|
val type = type.type as? ParameterizedType ?: return null
|
||||||
|
val (type0, type1) = type.actualTypeArguments
|
||||||
|
|
||||||
|
return object : TypeAdapter<Pair<Any?, Any?>>() {
|
||||||
|
private val adapter0 = gson.getAdapter(TypeToken.get(type0)) as TypeAdapter<Any?>
|
||||||
|
private val adapter1 = gson.getAdapter(TypeToken.get(type1)) as TypeAdapter<Any?>
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: Pair<Any?, Any?>?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
out.beginArray()
|
||||||
|
adapter0.write(out, value.first)
|
||||||
|
adapter1.write(out, value.second)
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Pair<Any?, Any?>? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
|
||||||
|
`in`.beginArray()
|
||||||
|
|
||||||
|
val value = try {
|
||||||
|
adapter0.read(`in`)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
throw JsonSyntaxException("Reading left side of pair", err)
|
||||||
|
} to try {
|
||||||
|
adapter1.read(`in`)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
throw JsonSyntaxException("Reading right side of pair", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endArray()
|
||||||
|
return value
|
||||||
|
} else if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
|
||||||
|
`in`.beginObject()
|
||||||
|
|
||||||
|
var left: Any? = null
|
||||||
|
var right: Any? = null
|
||||||
|
var leftPresent = false
|
||||||
|
var rightPresent = false
|
||||||
|
|
||||||
|
while (`in`.hasNext()) {
|
||||||
|
when (`in`.nextName().lowercase()) {
|
||||||
|
"left", "first", "a" -> {
|
||||||
|
left = adapter0.read(`in`)
|
||||||
|
leftPresent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
"right", "second", "b" -> {
|
||||||
|
right = adapter1.read(`in`)
|
||||||
|
rightPresent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> `in`.skipValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`in`.endObject()
|
||||||
|
|
||||||
|
if (!leftPresent && !rightPresent) {
|
||||||
|
throw JsonSyntaxException("Neither left or right side of pair is present")
|
||||||
|
} else if (!leftPresent) {
|
||||||
|
throw JsonSyntaxException("Left side of pair is missing")
|
||||||
|
} else if (!rightPresent) {
|
||||||
|
throw JsonSyntaxException("Right side of pair is missing")
|
||||||
|
} else {
|
||||||
|
return left to right
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw JsonSyntaxException("Expected either 2 value array or object for deserializing a pair, got ${`in`.peek()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
38
src/main/kotlin/ru/dbotthepony/kstarbound/math/LineF.kt
Normal file
38
src/main/kotlin/ru/dbotthepony/kstarbound/math/LineF.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
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.io.json.consumeNull
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
|
|
||||||
|
data class LineF(val start: Vector2f, val end: Vector2f) {
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<LineF>() {
|
||||||
|
private val vectors = gson.getAdapter(Vector2f::class.java)
|
||||||
|
|
||||||
|
override fun write(out: JsonWriter, value: LineF?) {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue()
|
||||||
|
} else {
|
||||||
|
out.beginArray()
|
||||||
|
vectors.write(out, value.start)
|
||||||
|
vectors.write(out, value.end)
|
||||||
|
out.endArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): LineF? {
|
||||||
|
if (`in`.consumeNull()) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
`in`.beginArray()
|
||||||
|
val start = vectors.read(`in`)
|
||||||
|
val end = vectors.read(`in`)
|
||||||
|
`in`.endArray()
|
||||||
|
return LineF(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.math
|
||||||
|
|
||||||
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||||
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
|
data class PeriodicFunction(
|
||||||
|
val period: Double = 1.0,
|
||||||
|
val min: Double = 0.0,
|
||||||
|
val max: Double = 1.0,
|
||||||
|
val periodVariance: Double = 0.0,
|
||||||
|
val magnitudeVariance: Double = 0.0
|
||||||
|
) {
|
||||||
|
fun interface Interpolator {
|
||||||
|
fun interpolate(t: Double, a: Double, b: Double): Double
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timer = 0.0
|
||||||
|
private var timerMax = 1.0
|
||||||
|
|
||||||
|
private var base = 0.0
|
||||||
|
private var target = 0.0
|
||||||
|
private var isDescending = false
|
||||||
|
|
||||||
|
private val halfPeriod = period * 0.5
|
||||||
|
private val halfVariance = periodVariance * 0.5
|
||||||
|
|
||||||
|
fun update(delta: Double, random: RandomGenerator) {
|
||||||
|
timer -= delta
|
||||||
|
|
||||||
|
// original code here explicitly ignore deltas bigger than max period time
|
||||||
|
// we, however, do not, if period time is big enough
|
||||||
|
while (timer <= 0.0) {
|
||||||
|
base = target
|
||||||
|
target = (if (isDescending) min else max) + random.nextDouble(-magnitudeVariance, magnitudeVariance)
|
||||||
|
isDescending = !isDescending
|
||||||
|
timerMax = (halfPeriod + random.nextDouble(-halfVariance, halfVariance)).coerceAtLeast(0.01)
|
||||||
|
timer += timerMax
|
||||||
|
|
||||||
|
if (timerMax <= 0.05 && timer <= 0.0) {
|
||||||
|
timer = 0.0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(interpolator: Interpolator): Double {
|
||||||
|
return interpolator.interpolate(1.0 - timer / timerMax, base, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(): Double {
|
||||||
|
return value(::linearInterpolation)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import kotlin.concurrent.getOrSet
|
|
||||||
|
|
||||||
class PathStack(private val interner: Interner<String> = Interner { it }) {
|
object AssetPathStack {
|
||||||
private val _stack = object : ThreadLocal<ArrayDeque<String>>() {
|
private val _stack = object : ThreadLocal<ArrayDeque<String>>() {
|
||||||
override fun initialValue(): ArrayDeque<String> {
|
override fun initialValue(): ArrayDeque<String> {
|
||||||
return ArrayDeque()
|
return ArrayDeque()
|
||||||
@ -40,14 +39,14 @@ class PathStack(private val interner: Interner<String> = Interner { it }) {
|
|||||||
if (b[0] == '/')
|
if (b[0] == '/')
|
||||||
return b
|
return b
|
||||||
|
|
||||||
return interner.intern("$a/$b")
|
return Starbound.strings.intern("$a/$b")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remap(path: String): String {
|
fun remap(path: String): String {
|
||||||
return remap(checkNotNull(last()) { "Not reading an asset on current thread" }, path)
|
return remap(checkNotNull(last()) { "Not reading an asset on current thread" }, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remapSafe(path: String): String? {
|
fun remapSafe(path: String): String {
|
||||||
return remap(last() ?: return null, path)
|
return remap(last() ?: return path, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,15 @@
|
|||||||
package ru.dbotthepony.kstarbound.util
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
|
||||||
|
|
||||||
operator fun JsonObject.set(key: String, value: JsonElement) { add(key, value) }
|
operator fun JsonObject.set(key: String, value: JsonElement?) { add(key, value) }
|
||||||
operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) }
|
operator fun JsonObject.set(key: String, value: String) { add(key, JsonPrimitive(value)) }
|
||||||
operator fun JsonObject.set(key: String, value: Int) { add(key, JsonPrimitive(value)) }
|
operator fun JsonObject.set(key: String, value: Int) { add(key, JsonPrimitive(value)) }
|
||||||
operator fun JsonObject.set(key: String, value: Long) { add(key, JsonPrimitive(value)) }
|
operator fun JsonObject.set(key: String, value: Long) { add(key, JsonPrimitive(value)) }
|
||||||
@ -14,3 +17,121 @@ operator fun JsonObject.set(key: String, value: Float) { add(key, JsonPrimitive(
|
|||||||
operator fun JsonObject.set(key: String, value: Double) { add(key, JsonPrimitive(value)) }
|
operator fun JsonObject.set(key: String, value: Double) { add(key, JsonPrimitive(value)) }
|
||||||
operator fun JsonObject.set(key: String, value: Boolean) { add(key, InternedJsonElementAdapter.of(value)) }
|
operator fun JsonObject.set(key: String, value: Boolean) { add(key, InternedJsonElementAdapter.of(value)) }
|
||||||
operator fun JsonObject.set(key: String, value: Nothing?) { add(key, JsonNull.INSTANCE) }
|
operator fun JsonObject.set(key: String, value: Nothing?) { add(key, JsonNull.INSTANCE) }
|
||||||
|
operator fun JsonObject.contains(key: String): Boolean = has(key)
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: Boolean): Boolean {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonPrimitive || !value.isBoolean) {
|
||||||
|
throw JsonSyntaxException("Expected boolean at $key; got $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.asBoolean
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: String): String {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonPrimitive || !value.isString) {
|
||||||
|
throw JsonSyntaxException("Expected string at $key; got $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.asString
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: Int): Int {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonPrimitive || !value.isNumber) {
|
||||||
|
throw JsonSyntaxException("Expected integer at $key; got $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.asInt
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: Long): Long {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonPrimitive || !value.isNumber) {
|
||||||
|
throw JsonSyntaxException("Expected long at $key; got $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.asLong
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: Double): Double {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonPrimitive || !value.isNumber) {
|
||||||
|
throw JsonSyntaxException("Expected double at $key; got $value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.asDouble
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: JsonObject): JsonObject {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonObject)
|
||||||
|
throw JsonSyntaxException("Expected json object at $key; got $value")
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: JsonArray): JsonArray {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
val value = this[key]
|
||||||
|
|
||||||
|
if (value !is JsonArray)
|
||||||
|
throw JsonSyntaxException("Expected json array at $key; got $value")
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.get(key: String, default: JsonPrimitive): JsonElement {
|
||||||
|
if (!has(key))
|
||||||
|
return default
|
||||||
|
|
||||||
|
return this[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> JsonObject.get(key: String, type: TypeAdapter<T>): T {
|
||||||
|
return get(key, type) { throw JsonSyntaxException("Expected value at $key, got nothing") }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> JsonObject.get(key: String, type: TypeAdapter<out T>, orElse: () -> T): T {
|
||||||
|
if (!has(key))
|
||||||
|
return orElse.invoke()
|
||||||
|
|
||||||
|
return type.fromJsonTree(this[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.getArray(key: String): JsonArray {
|
||||||
|
if (!has(key)) throw JsonSyntaxException("Expected array at $key, got nothing")
|
||||||
|
return this[key] as? JsonArray ?: throw JsonSyntaxException("Expected array at $key, got ${this[key]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.getObject(key: String): JsonObject {
|
||||||
|
if (!has(key)) throw JsonSyntaxException("Expected object at $key, got nothing")
|
||||||
|
return this[key] as? JsonObject ?: throw JsonSyntaxException("Expected object at $key, got ${this[key]}")
|
||||||
|
}
|
||||||
|
61
src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt
Normal file
61
src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
fun <T> KOptional(value: T) = KOptional.of(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [java.util.Optional] supporting nulls
|
||||||
|
*
|
||||||
|
* This is done for structures, where value can be absent,
|
||||||
|
* or can be present, including literal "null" as possible present value,
|
||||||
|
* in more elegant solution than handling nullable Optionals
|
||||||
|
*/
|
||||||
|
class KOptional<T> private constructor(private val _value: T, val isPresent: Boolean) {
|
||||||
|
val value: T get() {
|
||||||
|
if (isPresent) {
|
||||||
|
return _value
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NoSuchElementException("No value is present")
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun ifPresent(block: (T) -> Unit) {
|
||||||
|
if (isPresent) {
|
||||||
|
block.invoke(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return this === other || other is KOptional<*> && isPresent == other.isPresent && _value == other._value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return _value.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
if (isPresent) {
|
||||||
|
return "KOptional[value = $value]"
|
||||||
|
} else {
|
||||||
|
return "KOptional[empty]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val EMPTY = KOptional(null, false)
|
||||||
|
private val NULL = KOptional(null, true)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun <T> empty(): KOptional<T> {
|
||||||
|
return EMPTY as KOptional<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun <T> of(value: T): KOptional<T> {
|
||||||
|
if (value == null) {
|
||||||
|
return NULL as KOptional<T>
|
||||||
|
} else {
|
||||||
|
return KOptional(value, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/main/kotlin/ru/dbotthepony/kstarbound/util/OneOf.kt
Normal file
36
src/main/kotlin/ru/dbotthepony/kstarbound/util/OneOf.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Either] but with 3 values
|
||||||
|
*/
|
||||||
|
class OneOf<A, B, C> private constructor(
|
||||||
|
val v0: KOptional<A>,
|
||||||
|
val v1: KOptional<B>,
|
||||||
|
val v2: KOptional<C>,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other === this || other is OneOf<*, *, *> && v0 == other.v0 && v1 == other.v1 && v2 == other.v2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return v0.hashCode() + v1.hashCode() * 31 + v2.hashCode() * 31 * 31
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "OneOf[$v0, $v1, $v2]"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <A, B, C> first(value: A): OneOf<A, B, C> {
|
||||||
|
return OneOf(KOptional.of(value), KOptional.empty(), KOptional.empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <A, B, C> second(value: B): OneOf<A, B, C> {
|
||||||
|
return OneOf(KOptional.empty(), KOptional.of(value), KOptional.empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <A, B, C> third(value: C): OneOf<A, B, C> {
|
||||||
|
return OneOf(KOptional.empty(), KOptional.empty(), KOptional.of(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import com.google.gson.stream.JsonReader
|
|||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Шаблонизировання строка в стиле Starbound'а
|
* Шаблонизировання строка в стиле Starbound'а
|
||||||
@ -23,19 +24,26 @@ import ru.dbotthepony.kstarbound.Starbound
|
|||||||
*/
|
*/
|
||||||
class SBPattern private constructor(
|
class SBPattern private constructor(
|
||||||
val raw: String,
|
val raw: String,
|
||||||
val params: ImmutableMap<String, String>,
|
private val params: Array<String?>,
|
||||||
val pieces: ImmutableList<Piece>,
|
val pieces: ImmutableList<Piece>,
|
||||||
val names: ImmutableSet<String>
|
val namesSet: ImmutableSet<String>,
|
||||||
|
val namesList: ImmutableList<String>,
|
||||||
) {
|
) {
|
||||||
val isPlainString get() = names.isEmpty()
|
fun getParam(name: String): String? {
|
||||||
|
val index = namesList.indexOf(name)
|
||||||
|
if (index == -1) return null
|
||||||
|
return params[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPlainString get() = namesSet.isEmpty()
|
||||||
val value by lazy { resolve { null } }
|
val value by lazy { resolve { null } }
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "SBPattern[$raw, $params]"
|
return "SBPattern[$raw, ${params.withIndex().joinToString("; ") { "${namesList[it.index]}=${it.value}" }}]"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other === this || other is SBPattern && other.raw == raw && other.names == names && other.pieces == pieces && other.params == params
|
return other === this || other is SBPattern && other.raw == raw && other.namesSet == namesSet && other.pieces == pieces && other.params.contentEquals(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
@ -45,7 +53,7 @@ class SBPattern private constructor(
|
|||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
if (!calculatedHash) {
|
if (!calculatedHash) {
|
||||||
hash = raw.hashCode().xor(params.hashCode()).rotateLeft(12).and(pieces.hashCode()).rotateRight(8).xor(names.hashCode())
|
hash = raw.hashCode().xor(params.contentHashCode()).rotateLeft(12).and(pieces.hashCode()).rotateRight(8).xor(namesSet.hashCode())
|
||||||
calculatedHash = true
|
calculatedHash = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,16 +61,16 @@ class SBPattern private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resolve(values: (String) -> String?): String? {
|
fun resolve(values: (String) -> String?): String? {
|
||||||
if (names.isEmpty()) {
|
if (namesSet.isEmpty()) {
|
||||||
return raw
|
return raw
|
||||||
} else if (pieces.size == 1) {
|
} else if (pieces.size == 1) {
|
||||||
return pieces[0].resolve(values, params::get)
|
return pieces[0].resolve(values, this::getParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
val buffer = ArrayList<String>(pieces.size)
|
val buffer = ArrayList<String>(pieces.size)
|
||||||
|
|
||||||
for (piece in pieces) {
|
for (piece in pieces) {
|
||||||
buffer.add(piece.resolve(values, params::get) ?: return null)
|
buffer.add(piece.resolve(values, this::getParam) ?: return null)
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
@ -81,26 +89,58 @@ class SBPattern private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun with(params: (String) -> String?): SBPattern {
|
fun with(params: (String) -> String?): SBPattern {
|
||||||
if (names.isEmpty())
|
when (namesList.size) {
|
||||||
return this
|
0 -> return this
|
||||||
|
1 -> {
|
||||||
|
val get = params.invoke(namesList[0])
|
||||||
|
|
||||||
val map = Object2ObjectArrayMap<String, String>()
|
if (get == this.params[0]) {
|
||||||
map.putAll(this.params)
|
return this
|
||||||
var any = false
|
}
|
||||||
|
|
||||||
for (name in names) {
|
return SBPattern(raw, arrayOf(get), pieces, namesSet, namesList)
|
||||||
val get = params.invoke(name)
|
}
|
||||||
|
2 -> {
|
||||||
|
val get0 = params.invoke(namesList[0])
|
||||||
|
val get1 = params.invoke(namesList[1])
|
||||||
|
|
||||||
if (get != null && get != map[name]) {
|
if (get0 == this.params[0] && get1 == this.params[1]) {
|
||||||
map[name] = get
|
return this
|
||||||
any = true
|
}
|
||||||
|
|
||||||
|
return SBPattern(raw, arrayOf(get0, get1), pieces, namesSet, namesList)
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
val get0 = params.invoke(namesList[0])
|
||||||
|
val get1 = params.invoke(namesList[1])
|
||||||
|
val get2 = params.invoke(namesList[2])
|
||||||
|
|
||||||
|
if (get0 == this.params[0] && get1 == this.params[1] && get2 == this.params[2]) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return SBPattern(raw, arrayOf(get0, get1, get2), pieces, namesSet, namesList)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val newArray = this.params.copyOf()
|
||||||
|
var any = false
|
||||||
|
|
||||||
|
for ((i, name) in namesList.withIndex()) {
|
||||||
|
val value = this.params[i]
|
||||||
|
val get = params.invoke(name)
|
||||||
|
|
||||||
|
if (value != get) {
|
||||||
|
newArray[i] = value
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any)
|
||||||
|
return this
|
||||||
|
|
||||||
|
return SBPattern(raw, newArray, pieces, namesSet, namesList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!any)
|
|
||||||
return this
|
|
||||||
|
|
||||||
return SBPattern(raw, ImmutableMap.copyOf(map), pieces, names)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Piece(val name: String? = null, val contents: String? = null) {
|
data class Piece(val name: String? = null, val contents: String? = null) {
|
||||||
@ -160,6 +200,10 @@ class SBPattern private constructor(
|
|||||||
|
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
if (open != i) {
|
||||||
|
pieces.add(Piece(contents = raw.substring(i, open)))
|
||||||
|
}
|
||||||
|
|
||||||
val closing = raw.indexOf('>', startIndex = open + 1)
|
val closing = raw.indexOf('>', startIndex = open + 1)
|
||||||
|
|
||||||
if (closing == -1) {
|
if (closing == -1) {
|
||||||
@ -172,15 +216,27 @@ class SBPattern private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val built = pieces.build()
|
val built = pieces.build()
|
||||||
return interner.intern(SBPattern(raw, pieces = built, params = ImmutableMap.of(), names = built.stream().map { it.name }.filter { it != null }.collect(ImmutableSet.toImmutableSet())))
|
val names = built.stream().map { it.name }.filter { it != null }.collect(ImmutableSet.toImmutableSet())
|
||||||
|
|
||||||
|
val result = SBPattern(
|
||||||
|
raw,
|
||||||
|
pieces = built,
|
||||||
|
params = arrayOfNulls(names.size),
|
||||||
|
namesSet = names as ImmutableSet<String>,
|
||||||
|
namesList = ImmutableList.copyOf(names)
|
||||||
|
)
|
||||||
|
|
||||||
|
return interner.intern(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val emptyParams = arrayOfNulls<String>(0)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun raw(raw: String): SBPattern {
|
fun raw(raw: String): SBPattern {
|
||||||
if (raw == "")
|
if (raw == "")
|
||||||
return EMPTY
|
return EMPTY
|
||||||
|
|
||||||
return SBPattern(raw, ImmutableMap.of(), ImmutableList.of(), ImmutableSet.of())
|
return SBPattern(raw, emptyParams, ImmutableList.of(), ImmutableSet.of(), ImmutableList.of())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.world
|
|||||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
@ -165,7 +166,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var material: TileDefinition? = null
|
override var material: TileDefinition = BuiltinMetaMaterials.NULL
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field !== value) {
|
if (field !== value) {
|
||||||
field = value
|
field = value
|
||||||
|
@ -56,13 +56,13 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
private inner class Grid {
|
private inner class Grid {
|
||||||
inner class LightCell(val x: Int, val y: Int) : ICell {
|
inner class LightCell(val x: Int, val y: Int) : ICell {
|
||||||
val actualDropoff by lazy(LazyThreadSafetyMode.NONE) {
|
val actualDropoff by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
val parent = this@LightCalculator.parent.getCell(x, y)
|
val parent = this@LightCalculator.parent.getCell(x, y) ?: return@lazy 0f
|
||||||
val lightBlockStrength: Float
|
val lightBlockStrength: Float
|
||||||
|
|
||||||
if (parent?.foreground?.material != null) {
|
if (parent.foreground.material.renderParameters.lightTransparent) {
|
||||||
lightBlockStrength = 1f
|
|
||||||
} else {
|
|
||||||
lightBlockStrength = 0f
|
lightBlockStrength = 0f
|
||||||
|
} else {
|
||||||
|
lightBlockStrength = 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
|
linearInterpolation(lightBlockStrength, invMaxAirSpread, invMaxObstacleSpread)
|
||||||
@ -73,14 +73,23 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
override var green: Float = 0f
|
override var green: Float = 0f
|
||||||
override var blue: Float = 0f
|
override var blue: Float = 0f
|
||||||
|
|
||||||
fun spreadInto(target: LightCell, drop: Float) {
|
fun spreadInto(target: LightCell, drop: Float, alreadyHadChanges: Boolean): Boolean {
|
||||||
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
|
val max = red.coerceAtLeast(green).coerceAtLeast(blue)
|
||||||
if (max <= 0f) return
|
if (max <= 0f) return alreadyHadChanges
|
||||||
val newDrop = 1f - actualDropoff / max * drop
|
val newDrop = 1f - actualDropoff / max * drop
|
||||||
|
|
||||||
target.red = target.red.coerceAtLeast(red * newDrop)
|
@Suppress("name_shadowing")
|
||||||
target.green = target.green.coerceAtLeast(green * newDrop)
|
var alreadyHadChanges = alreadyHadChanges
|
||||||
target.blue = target.blue.coerceAtLeast(blue * newDrop)
|
val nred = target.red.coerceAtLeast(red * newDrop)
|
||||||
|
val ngreen = target.green.coerceAtLeast(green * newDrop)
|
||||||
|
val nblue = target.blue.coerceAtLeast(blue * newDrop)
|
||||||
|
|
||||||
|
if (!alreadyHadChanges)
|
||||||
|
alreadyHadChanges = nred != target.red || ngreen != target.green || nblue != target.blue
|
||||||
|
|
||||||
|
target.red = nred
|
||||||
|
target.green = ngreen
|
||||||
|
target.blue = nblue
|
||||||
|
|
||||||
if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) {
|
if (target.empty && (target.red >= epsilon || target.blue >= epsilon || target.green >= epsilon)) {
|
||||||
minX = minX.coerceAtMost(target.x - 1)
|
minX = minX.coerceAtMost(target.x - 1)
|
||||||
@ -90,6 +99,8 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
target.empty = false
|
target.empty = false
|
||||||
clampRect()
|
clampRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return alreadyHadChanges
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +197,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
val maxX = maxX
|
val maxX = maxX
|
||||||
val minY = minY
|
val minY = minY
|
||||||
val maxY = maxY
|
val maxY = maxY
|
||||||
|
var changes = false
|
||||||
|
|
||||||
if (copy == null) {
|
if (copy == null) {
|
||||||
copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) {
|
copy = if (maxX - minX >= width / 2 || maxY - minY >= height / 2) {
|
||||||
@ -201,14 +213,14 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (x in minX .. maxX) {
|
for (x in minX .. maxX) {
|
||||||
val current = copy[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
current.spreadInto(copy[x, y + 1], 1f)
|
changes = current.spreadInto(copy[x, y + 1], 1f, changes)
|
||||||
current.spreadInto(copy[x + 1, y], 1f)
|
changes = current.spreadInto(copy[x + 1, y], 1f, changes)
|
||||||
current.spreadInto(copy[x + 1, y + 1], 1.4142135f)
|
changes = current.spreadInto(copy[x + 1, y + 1], 1.4142135f, changes)
|
||||||
|
|
||||||
// original code performs this spread to camouflage prism shape of light spreading
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
// we instead gonna do light pass on different diagonal
|
// we instead gonna do light pass on different diagonal
|
||||||
if (quality.extraCell)
|
if (quality.extraCell)
|
||||||
current.spreadInto(copy[x + 1, y - 1], 1.4142135f)
|
changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// right to left
|
// right to left
|
||||||
@ -216,9 +228,9 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (x in maxX downTo minX) {
|
for (x in maxX downTo minX) {
|
||||||
val current = copy[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
current.spreadInto(copy[x, y + 1], 1f)
|
changes = current.spreadInto(copy[x, y + 1], 1f, changes)
|
||||||
current.spreadInto(copy[x - 1, y], 1f)
|
changes = current.spreadInto(copy[x - 1, y], 1f, changes)
|
||||||
current.spreadInto(copy[x - 1, y + 1], 1.4142135f)
|
changes = current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,16 +239,16 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (y in maxY downTo minY) {
|
for (y in maxY downTo minY) {
|
||||||
// right to left
|
// right to left
|
||||||
for (x in maxX downTo minX) {
|
for (x in maxX downTo minX) {
|
||||||
val current = this[x, y]
|
val current = copy[x, y]
|
||||||
|
|
||||||
current.spreadInto(this[x, y - 1], 1f)
|
changes = current.spreadInto(copy[x, y - 1], 1f, changes)
|
||||||
current.spreadInto(this[x - 1, y], 1f)
|
changes = current.spreadInto(copy[x - 1, y], 1f, changes)
|
||||||
current.spreadInto(this[x - 1, y - 1], 1.4142135f)
|
changes = current.spreadInto(copy[x - 1, y - 1], 1.4142135f, changes)
|
||||||
|
|
||||||
// original code performs this spread to camouflage prism shape of light spreading
|
// original code performs this spread to camouflage prism shape of light spreading
|
||||||
// we instead gonna do light pass on different diagonal
|
// we instead gonna do light pass on different diagonal
|
||||||
if (quality.extraCell)
|
if (quality.extraCell)
|
||||||
current.spreadInto(this[x - 1, y + 1], 1.4142135f)
|
current.spreadInto(copy[x - 1, y + 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// left to right
|
// left to right
|
||||||
@ -244,9 +256,9 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
for (x in minX .. maxX) {
|
for (x in minX .. maxX) {
|
||||||
val current = this[x, y]
|
val current = this[x, y]
|
||||||
|
|
||||||
current.spreadInto(this[x, y - 1], 1f)
|
changes = current.spreadInto(copy[x, y - 1], 1f, changes)
|
||||||
current.spreadInto(this[x + 1, y], 1f)
|
changes = current.spreadInto(copy[x + 1, y], 1f, changes)
|
||||||
current.spreadInto(this[x + 1, y - 1], 1.4142135f)
|
changes = current.spreadInto(copy[x + 1, y - 1], 1.4142135f, changes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +277,8 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
} else {
|
} else {
|
||||||
Copy()
|
Copy()
|
||||||
}
|
}
|
||||||
}
|
} else if (!changes)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,7 +293,7 @@ class LightCalculator(val parent: ICellAccess, val width: Int, val height: Int)
|
|||||||
// Number of ambient spread passes. Needs to be at least spreadMaxAir /
|
// Number of ambient spread passes. Needs to be at least spreadMaxAir /
|
||||||
// spreadMaxObstacle big, but sometimes it can stand to be a bit less and
|
// spreadMaxObstacle big, but sometimes it can stand to be a bit less and
|
||||||
// you won't notice.
|
// you won't notice.
|
||||||
var passes = 3
|
var passes = 4
|
||||||
|
|
||||||
// Maximum distance through empty space that 100% ambient light can pass through
|
// Maximum distance through empty space that 100% ambient light can pass through
|
||||||
var maxAirSpread = 32f
|
var maxAirSpread = 32f
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.RegistryObject
|
import ru.dbotthepony.kstarbound.RegistryObject
|
||||||
|
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
|
||||||
interface ITileState {
|
interface ITileState {
|
||||||
var material: TileDefinition?
|
var material: TileDefinition
|
||||||
var modifier: MaterialModifier?
|
var modifier: MaterialModifier?
|
||||||
var color: TileColor
|
var color: TileColor
|
||||||
var hueShift: Float
|
var hueShift: Float
|
||||||
@ -37,7 +38,7 @@ interface ITileState {
|
|||||||
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
|
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
|
||||||
stream: DataInputStream
|
stream: DataInputStream
|
||||||
) {
|
) {
|
||||||
material = materialAccess(stream.readUnsignedShort())?.value
|
material = materialAccess(stream.readUnsignedShort())?.value ?: BuiltinMetaMaterials.EMPTY
|
||||||
setHueShift(stream.read())
|
setHueShift(stream.read())
|
||||||
color = TileColor.of(stream.read())
|
color = TileColor.of(stream.read())
|
||||||
modifier = modifierAccess(stream.readUnsignedShort())?.value
|
modifier = modifierAccess(stream.readUnsignedShort())?.value
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.`object`
|
package ru.dbotthepony.kstarbound.world.`object`
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonNull
|
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import ru.dbotthepony.kstarbound.RegistryObject
|
import ru.dbotthepony.kstarbound.RegistryObject
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||||
|
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
||||||
import ru.dbotthepony.kstarbound.util.set
|
import ru.dbotthepony.kstarbound.util.set
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||||
@ -23,8 +21,8 @@ abstract class WorldObject(
|
|||||||
val prototype: RegistryObject<ObjectDefinition>,
|
val prototype: RegistryObject<ObjectDefinition>,
|
||||||
val pos: Vector2i,
|
val pos: Vector2i,
|
||||||
) : JsonDriven(prototype.file.computeDirectory()) {
|
) : JsonDriven(prototype.file.computeDirectory()) {
|
||||||
val orientations: JsonArray = prototype.jsonObject["orientations"].asJsonArray
|
val orientations = prototype.value.orientations
|
||||||
val validOrientations = orientations.size()
|
val validOrientations = orientations.size
|
||||||
|
|
||||||
// scriptStorage - json object
|
// scriptStorage - json object
|
||||||
var uniqueId: String? = null
|
var uniqueId: String? = null
|
||||||
@ -43,7 +41,7 @@ abstract class WorldObject(
|
|||||||
check(world.objects.remove(this))
|
check(world.objects.remove(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
var orientation = -1
|
var orientationIndex = -1
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field != value) {
|
if (field != value) {
|
||||||
field = value
|
field = value
|
||||||
@ -51,9 +49,13 @@ abstract class WorldObject(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val orientation: ObjectOrientation? get() {
|
||||||
|
return orientations.getOrNull(orientationIndex)
|
||||||
|
}
|
||||||
|
|
||||||
override fun defs(): Collection<JsonObject> {
|
override fun defs(): Collection<JsonObject> {
|
||||||
if (orientation in 0 until validOrientations) {
|
if (orientationIndex in 0 until validOrientations) {
|
||||||
return listOf(orientations[orientation] as JsonObject, prototype.jsonObject)
|
return listOf(orientations[orientationIndex].json, prototype.jsonObject)
|
||||||
} else {
|
} else {
|
||||||
return listOf(prototype.jsonObject)
|
return listOf(prototype.jsonObject)
|
||||||
}
|
}
|
||||||
@ -87,6 +89,10 @@ abstract class WorldObject(
|
|||||||
obj.direction = directions.fromJsonTree(it)
|
obj.direction = directions.fromJsonTree(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data["orientationIndex"]?.let {
|
||||||
|
obj.orientationIndex = it.asInt
|
||||||
|
}
|
||||||
|
|
||||||
data["interactive"]?.let {
|
data["interactive"]?.let {
|
||||||
obj.interactive = it.asBoolean
|
obj.interactive = it.asBoolean
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user