Move tile definition and render template to KConcreteTypeAdapter

This commit is contained in:
DBotThePony 2022-08-26 22:30:32 +07:00
parent d3396ddb7c
commit 7c318966d5
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 351 additions and 755 deletions

View File

@ -19,61 +19,9 @@ private val LOGGER = LogManager.getLogger()
fun main() { fun main() {
LOGGER.info("Running LWJGL ${Version.getVersion()}") LOGGER.info("Running LWJGL ${Version.getVersion()}")
if (true) {
//val pak = StarboundPak(File("J:\\SteamLibrary\\steamapps\\common\\Starbound\\assets\\packed.pak"))
//val json = JsonParser.parseReader(pak.getReader("/projectiles/traps/lowgravboostergas/lowgravboostergas.projectile"))
//val obj = Gson().fromJson(json, ProjectileDefinitionBuilder::class.java)
//println(obj.build())
//return
}
val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world")) val db = BTreeDB(File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\389760395_938904237_-238610574_5.world"))
//val db = BTreeDB(File("world.world")) //val db = BTreeDB(File("world.world"))
/*if (true) {
val a = System.currentTimeMillis()
val worldMeta = db.read(byteArrayOf(0, 0, 0, 0, 0))
println(System.currentTimeMillis() - a)
val inflater = Inflater()
inflater.setInput(worldMeta!!)
val output = ByteArray(1_000_000)
inflater.inflate(output)
val stream = DataInputStream(ByteArrayInputStream(output))
println("X tiles ${stream.readInt()}")
println("Y tiles ${stream.readInt()}")
val metadata = VersionedJSON(stream)
println(metadata.data)
return
}*/
/*if (true) {
val data = db.read(byteArrayOf(1, 0, 61, 0, 23))
val inflater = Inflater()
inflater.setInput(data!!)
val output = ByteArray(64_000)
val actual = inflater.inflate(output)
File("F:\\SteamLibrary\\steamapps\\common\\Starbound - Unstable\\storage\\universe\\tiles.dat").writeBytes(output)
val reader = DataInputStream(ByteArrayInputStream(output))
reader.skipBytes(3)
for (y in 0 .. 31) {
for (x in 0 .. 31) {
println("$x $y ${reader.readShort()}")
reader.skipBytes(29)
}
}
return
}*/
val client = StarboundClient() val client = StarboundClient()
//Starbound.addFilePath(File("./unpacked_assets/")) //Starbound.addFilePath(File("./unpacked_assets/"))
@ -91,34 +39,6 @@ fun main() {
val ent = PlayerEntity(client.world!!) val ent = PlayerEntity(client.world!!)
Starbound.onInitialize { Starbound.onInitialize {
if (true) {
val input = "{\n" +
" \"modId\" : 26,\n" +
" \"modName\" : \"aegisalt\",\n" +
" \"itemDrop\" : \"aegisaltore\",\n" +
" \"description\" : \"Aegisalt.\",\n" +
" \"health\" : 5,\n" +
" \"harvestLevel\" : 5,\n" +
" \"breaksWithTile\" : true,\n" +
"\n" +
" \"miningSounds\" : [ \"/sfx/tools/pickaxe_ore.ogg\", \"/sfx/tools/pickaxe_ore2.ogg\" ],\n" +
" \"miningParticle\" : \"orespark\",\n" +
"\n" +
" \"renderTemplate\" : \"/tiles/classicmaterialtemplate.config\",\n" +
" \"renderParameters\" : {\n" +
" \"texture\" : \"aegisalt.png\",\n" +
" \"variants\" : 8,\n" +
" \"multiColored\" : false,\n" +
" \"zLevel\" : 0\n" +
" }\n" +
"}\n"
val json = Starbound.gson.fromJson(input, MaterialModifier::class.java)
println(json)
return@onInitialize
}
var find = 0L var find = 0L
var set = 0L var set = 0L
var parse = 0L var parse = 0L

View File

@ -12,6 +12,7 @@ import ru.dbotthepony.kstarbound.defs.projectile.*
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.world.SkyParameters import ru.dbotthepony.kstarbound.defs.world.SkyParameters
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
@ -41,6 +42,15 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f
object Starbound : IVFS { object Starbound : IVFS {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val _readingFolder = ThreadLocal<String>()
/**
* Случит переменной для указания из какой папки происходит чтение asset'а в данном потоке
*/
var readingFolder: String?
get() = _readingFolder.get()
set(value) { _readingFolder.set(value) }
private val tiles = HashMap<String, TileDefinition>() private val tiles = HashMap<String, TileDefinition>()
private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>() private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
private val projectiles = HashMap<String, ConfiguredProjectile>() private val projectiles = HashMap<String, ConfiguredProjectile>()
@ -77,6 +87,7 @@ object Starbound : IVFS {
.also(MaterialModifier::registerGson) .also(MaterialModifier::registerGson)
.also(RenderParameters::registerGson) .also(RenderParameters::registerGson)
.also(RenderTemplate::registerGson) .also(RenderTemplate::registerGson)
.also(TileDefinition::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
@ -231,12 +242,15 @@ object Starbound : IVFS {
} }
private fun loadTileMaterials(callback: (String) -> Unit) { private fun loadTileMaterials(callback: (String) -> Unit) {
readingFolder = "/tiles/materials"
for (fs in fileSystems) { for (fs in fileSystems) {
for (listedFile in fs.listAllFilesWithExtension("material")) { for (listedFile in fs.listAllFilesWithExtension("material")) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val tileDef = TileDefinitionBuilder.fromJson(JsonParser.parseReader(getReader(listedFile)) as JsonObject).build("/tiles/materials") readingFolder = getPathFolder(listedFile)
val tileDef = gson.fromJson(getReader(listedFile), TileDefinition::class.java)
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" } check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
@ -248,6 +262,8 @@ object Starbound : IVFS {
} }
} }
} }
readingFolder = null
} }
private fun loadProjectiles(callback: (String) -> Unit) { private fun loadProjectiles(callback: (String) -> Unit) {

View File

@ -5,9 +5,9 @@ import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.* import ru.dbotthepony.kstarbound.client.gl.*
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.RenderMatch
import ru.dbotthepony.kstarbound.defs.TileRenderMatchPiece import ru.dbotthepony.kstarbound.defs.tile.RenderPiece
import ru.dbotthepony.kstarbound.defs.TileRenderPiece import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.ITileChunk import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
@ -152,7 +152,7 @@ private enum class TileRenderTesselateResult {
} }
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) { class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
val texture = state.loadNamedTexture(tile.render.texture).also { val texture = state.loadNamedTexture(tile.renderParameters.absoluteTexturePath).also {
it.textureMagFilter = GL_NEAREST it.textureMagFilter = GL_NEAREST
} }
@ -160,7 +160,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
val bakedBackgroundProgramState = state.tileRenderers.background(texture) val bakedBackgroundProgramState = state.tileRenderers.background(texture)
// private var notifiedDepth = false // private var notifiedDepth = false
private fun tesselateAt(piece: TileRenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) { private fun tesselateAt(piece: RenderPiece, getter: ITileChunk, builder: DynamicVertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) {
val fx = pos.x.toFloat() val fx = pos.x.toFloat()
val fy = pos.y.toFloat() val fy = pos.y.toFloat()
@ -173,7 +173,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
if (offset != Vector2i.ZERO) { if (offset != Vector2i.ZERO) {
a += offset.x / PIXELS_IN_STARBOUND_UNITf a += offset.x / PIXELS_IN_STARBOUND_UNITf
// в json файлах y указан как положительный вверх, // в json файлах Y указан как положительный вверх,
// что соответствует нашему миру // что соответствует нашему миру
b += offset.y / PIXELS_IN_STARBOUND_UNITf b += offset.y / PIXELS_IN_STARBOUND_UNITf
@ -181,7 +181,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
d += offset.y / PIXELS_IN_STARBOUND_UNITf d += offset.y / PIXELS_IN_STARBOUND_UNITf
} }
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) { if (tile.renderParameters.variants == 0 || piece.texture != null || piece.variantStride == null) {
val (u0, v0) = texture.pixelToUV(piece.texturePosition) val (u0, v0) = texture.pixelToUV(piece.texturePosition)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
@ -192,7 +192,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
d, d,
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0)) Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
} else { } else {
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt() val variant = (getter.randomDoubleFor(pos) * tile.renderParameters.variants).toInt()
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant) val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant) val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
@ -206,19 +206,19 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
} }
} }
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult { private fun tesselatePiece(matchPiece: RenderMatch, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, thisBuilder: DynamicVertexBuilder, background: Boolean): TileRenderTesselateResult {
if (matchPiece.test(getter, tile, pos)) { if (matchPiece.test(getter, tile, pos)) {
for (renderPiece in matchPiece.pieces) { for (renderPiece in matchPiece.pieces) {
if (renderPiece.piece.texture != null) { if (renderPiece.piece.texture != null) {
val program: BakedProgramState val program: BakedProgramState
if (background) { if (background) {
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture)) program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
} else { } else {
program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture)) program = state.tileRenderers.foreground(state.loadNamedTexture(renderPiece.piece.texture!!))
} }
tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.render.zLevel) { tesselateAt(renderPiece.piece, getter, layers.getLayer(program, tile.renderParameters.zLevel) {
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS) return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
}, pos, renderPiece.offset) }, pos, renderPiece.offset)
} else { } else {
@ -256,14 +256,14 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) { fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
// если у нас нет renderTemplate // если у нас нет renderTemplate
// то мы просто не можем его отрисовать // то мы просто не можем его отрисовать
tile.render.renderTemplate ?: return val template = tile.renderTemplate
val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.render.zLevel) { val builder = layers.getLayer(if (background) bakedBackgroundProgramState else bakedProgramState, tile.renderParameters.zLevel) {
return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS) return@getLayer DynamicVertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
} }
for ((_, matcher) in tile.render.renderTemplate.matches) { for ((_, matcher) in template.matches) {
for (matchPiece in matcher.pieces) { for (matchPiece in matcher) {
val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background) val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background)
if (matched == TileRenderTesselateResult.HALT) { if (matched == TileRenderTesselateResult.HALT) {

View File

@ -1,587 +0,0 @@
package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.nint.Vector2i
data class TileDefinition(
val materialId: Int,
val materialName: String,
val particleColor: Color,
val itemDrop: String?,
val description: String,
val shortdescription: String,
val blocksLiquidFlow: Boolean,
val soil: Boolean,
val tillableMod: Int,
val racialDescription: ImmutableMap<String, String>,
val footstepSound: String?,
val health: Int,
val category: String,
val render: TileRenderDefinition
) {
init {
require(materialId >= 0) { "Material ID must be positive ($materialId given) ($materialName)" }
require(materialId != 0) { "Material ID 0 is reserved ($materialName)" }
}
}
class TileDefinitionBuilder {
var materialId = 0
var materialName = "unknown_tile"
var particleColor = Color.WHITE
var itemDrop: String? = "unknown"
var description = "..."
var shortdescription = "..."
var blocksLiquidFlow = true
var soil = false
var tillableMod = 0
val racialDescription = ArrayList<Pair<String, String>>()
var footstepSound: String? = null
var health = 0
var category = "generic"
val render = TileRenderDefinitionBuilder()
fun build(directory: String? = null): TileDefinition {
return TileDefinition(
racialDescription = ImmutableMap.builder<String, String>().also {
for ((k, v) in this.racialDescription) {
it.put(k, v)
}
}.build(),
materialId = materialId,
materialName = materialName,
particleColor = particleColor,
itemDrop = itemDrop,
description = description,
shortdescription = shortdescription,
blocksLiquidFlow = blocksLiquidFlow,
soil = soil,
tillableMod = tillableMod,
footstepSound = footstepSound,
health = health,
category = category,
render = render.build(directory)
)
}
companion object {
private val LOGGER = LogManager.getLogger()
fun fromJson(input: JsonObject): TileDefinitionBuilder {
val builder = TileDefinitionBuilder()
try {
builder.materialName = input["materialName"].asString
builder.materialId = input["materialId"].asInt
require(builder.materialId >= 0) { "Invalid materialId ${builder.materialId}" }
builder.particleColor = Starbound.gson.fromJson(input["particleColor"], Color::class.java)
builder.itemDrop = input["itemDrop"]?.asString
builder.description = input["description"]?.asString ?: builder.description
builder.shortdescription = input["shortdescription"]?.asString ?: builder.shortdescription
builder.footstepSound = input["footstepSound"]?.asString
builder.blocksLiquidFlow = input["footstepSound"]?.asBoolean ?: builder.blocksLiquidFlow
builder.soil = input["footstepSound"]?.asBoolean ?: builder.soil
builder.health = input["health"].asInt
builder.tillableMod = input["health"]?.asInt ?: builder.tillableMod
builder.category = input["category"].asString
if (input["variants"] != null) {
LOGGER.warn("Tile {} has `variants` ({}) defined as top level property (expected to be under `renderParameters`)", builder.materialName, input["variants"].asString)
}
for (key in input.keySet()) {
if (key.endsWith("Description") && key.length != "Description".length) {
builder.racialDescription.add(key.substring(0, key.length - "Description".length) to input[key].asString)
}
}
input["renderParameters"]?.asJsonObject?.let {
builder.render.texture = it["texture"].asString
builder.render.variants = it["variants"].asInt
builder.render.lightTransparent = it["lightTransparent"]?.asBoolean ?: builder.render.lightTransparent
builder.render.occludesBelow = it["occludesBelow"]?.asBoolean ?: builder.render.occludesBelow
builder.render.multiColored = it["multiColored"]?.asBoolean ?: builder.render.multiColored
builder.render.zLevel = it["zLevel"].asInt
}
builder.render.renderTemplate = input["renderTemplate"]?.asString?.let renderTemplate@{
return@renderTemplate TileRenderTemplate.load(it)
}
} catch(err: Throwable) {
throw IllegalArgumentException("Failed reading tile definition ${builder.materialName}", err)
}
return builder
}
}
}
/**
* Кусочек рендера тайла
*
* root.pieces[]
*/
data class TileRenderPiece(
val name: String,
val texture: String?,
val textureSize: Vector2i,
val texturePosition: Vector2i,
val colorStride: Vector2i?,
val variantStride: Vector2i?,
) {
companion object {
fun fromJson(name: String, input: JsonObject): TileRenderPiece {
val texture = input["texture"]?.asString?.let {
if (it[0] != '/') {
throw UnsupportedOperationException("Render piece has not absolute texture path: $it")
}
return@let it
}
val textureSize = Starbound.gson.fromJson(input["textureSize"], Vector2i::class.java)
val texturePosition = Starbound.gson.fromJson(input["texturePosition"], Vector2i::class.java)
val colorStride = input["colorStride"]?.let { Starbound.gson.fromJson(it, Vector2i::class.java) }
val variantStride = input["variantStride"]?.let { Starbound.gson.fromJson(it, Vector2i::class.java) }
return TileRenderPiece(name, texture, textureSize, texturePosition, colorStride, variantStride)
}
}
}
/**
* Кусочек правила рендера тайла
*
* root.rules.`name`.entries[]
*/
sealed class RenderRule(params: Map<String, Any>) {
val matchHue = params["matchHue"] as? Boolean ?: false
val inverse = params["inverse"] as? Boolean ?: false
abstract fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean
companion object {
fun factory(name: String, params: Map<String, Any>): RenderRule {
return when (name) {
"EqualsSelf" -> RenderRuleEqualsSelf(params)
"Shadows" -> RenderRuleShadows(params)
// неизвестно что оно делает, но вероятнее всего, есть ли там что либо в принципе
"Connects" -> RenderRuleConnects(params)
else -> throw IllegalArgumentException("Unknown render rule '$name'")
}
}
fun fromJson(input: JsonObject): RenderRule {
val params = ImmutableMap.builder<String, Any>()
for (key in input.keySet()) {
if (key != "type") {
val value = input[key] as? JsonPrimitive
if (value != null) {
if (value.isBoolean) {
params.put(key, value.asBoolean)
} else if (value.isNumber) {
params.put(key, value.asDouble)
} else {
params.put(key, value.asString)
}
}
}
}
return factory(input["type"].asString, params.build())
}
}
}
class RenderRuleEqualsSelf(params: Map<String, Any>) : RenderRule(params) {
override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
val otherTile = getter[thisPos + offsetPos] ?: return inverse
if (inverse)
return otherTile.def != thisRef
return otherTile.def == thisRef
}
}
class RenderRuleShadows(params: Map<String, Any>) : RenderRule(params) {
override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return false // TODO
}
}
class RenderRuleConnects(params: Map<String, Any>) : RenderRule(params) {
override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse)
return getter[thisPos + offsetPos] == null
return getter[thisPos + offsetPos] != null
}
}
class AlwaysPassingRenderRule(params: Map<String, Any>) : RenderRule(params) {
override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return inverse
}
}
class AlwaysFailingRenderRule(params: Map<String, Any>) : RenderRule(params) {
override fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return !inverse
}
}
enum class RenderRuleCombination {
ALL,
ANY
}
/**
* Правило рендера тайла
*
* root.rules[]
*/
data class TileRenderRule(
val name: String,
val join: RenderRuleCombination,
val pieces: List<RenderRule>
) {
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (join == RenderRuleCombination.ANY) {
for (piece in pieces) {
if (piece.test(getter, thisRef, thisPos, offsetPos)) {
return true
}
}
return false
} else {
for (piece in pieces) {
if (!piece.test(getter, thisRef, thisPos, offsetPos)) {
return false
}
}
return true
}
}
companion object {
fun fromJson(name: String, input: JsonObject): TileRenderRule {
val join = input["join"]?.asString?.let {
when (it) {
"any" -> RenderRuleCombination.ANY
else -> RenderRuleCombination.ALL
}
} ?: RenderRuleCombination.ALL
val jEntries = input["entries"] as JsonArray
val pieces = ArrayList<RenderRule>(jEntries.size())
for (elem in jEntries) {
pieces.add(RenderRule.fromJson(elem.asJsonObject))
}
return TileRenderRule(name, join, ImmutableList.copyOf(pieces))
}
}
}
data class TileRenderMatchedPiece(
val piece: TileRenderPiece,
val offset: Vector2i
) {
companion object {
fun fromJson(input: JsonArray, tilePieces: Map<String, TileRenderPiece>): TileRenderMatchedPiece {
val piece = input[0].asString.let {
return@let tilePieces[it] ?: throw IllegalArgumentException("Unable to find render piece $it")
}
val offset = Starbound.gson.fromJson(input[1], Vector2i::class.java)
return TileRenderMatchedPiece(piece, offset)
}
}
}
data class TileRenderMatchPositioned(
val condition: TileRenderRule,
val offset: Vector2i
) {
/**
* Состояние [condition] на [thisPos] с [offset]
*/
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
return condition.test(getter, thisRef, thisPos, offset)
}
companion object {
fun fromJson(input: JsonArray, rulePieces: Map<String, TileRenderRule>): TileRenderMatchPositioned {
val offset = Starbound.gson.fromJson(input[0], Vector2i::class.java)
val condition = rulePieces[input[1].asString] ?: throw IllegalArgumentException("Rule ${input[1].asString} is missing!")
return TileRenderMatchPositioned(condition, offset)
}
}
}
data class TileRenderMatchPiece(
val pieces: List<TileRenderMatchedPiece>,
val matchAllPoints: List<TileRenderMatchPositioned>,
val matchAnyPoints: List<TileRenderMatchPositioned>,
val subMatches: List<TileRenderMatchPiece>,
val haltOnSubMatch: Boolean,
val haltOnMatch: Boolean
) {
init {
if (matchAnyPoints.isNotEmpty() || matchAllPoints.isNotEmpty()) {
require(matchAnyPoints.isEmpty() || matchAllPoints.isEmpty()) { "Both matchAllPoints and matchAnyPoints are present, this is not valid." }
}
}
/**
* Возвращает, сработали ли ВСЕ [matchAllPoints] или ЛЮБОЙ ИЗ [matchAnyPoints]
*
* Если хотя бы один из них вернул false, весь тест возвращает false
*
* [subMatches] стоит итерировать только если это вернуло true
*/
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
for (matcher in matchAllPoints) {
if (!matcher.test(getter, thisRef, thisPos)) {
return false
}
}
if (matchAnyPoints.isEmpty()) {
return true
}
for (matcher in matchAnyPoints) {
if (matcher.test(getter, thisRef, thisPos)) {
return true
}
}
return false
}
companion object {
fun fromJson(input: JsonObject, tilePieces: Map<String, TileRenderPiece>, rulePieces: Map<String, TileRenderRule>): TileRenderMatchPiece {
val pieces = input["pieces"]?.asJsonArray?.let {
val list = ArrayList<TileRenderMatchedPiece>()
for (thisPiece in it) {
list.add(TileRenderMatchedPiece.fromJson(thisPiece.asJsonArray, tilePieces))
}
return@let ImmutableList.copyOf(list)
} ?: listOf()
val matchAllPoints = input["matchAllPoints"]?.asJsonArray?.let {
val list = ArrayList<TileRenderMatchPositioned>()
for (thisPiece in it) {
list.add(TileRenderMatchPositioned.fromJson(thisPiece.asJsonArray, rulePieces))
}
return@let ImmutableList.copyOf(list)
} ?: listOf()
val matchAnyPoints = input["matchAnyPoints"]?.asJsonArray?.let {
val list = ArrayList<TileRenderMatchPositioned>()
for (thisPiece in it) {
list.add(TileRenderMatchPositioned.fromJson(thisPiece.asJsonArray, rulePieces))
}
return@let ImmutableList.copyOf(list)
} ?: listOf()
val subMatches = input["subMatches"]?.asJsonArray?.let {
val list = ArrayList<TileRenderMatchPiece>()
for (thisPiece in it) {
list.add(fromJson(thisPiece.asJsonObject, tilePieces, rulePieces))
}
return@let ImmutableList.copyOf(list)
} ?: listOf()
val haltOnSubMatch = input["haltOnSubMatch"]?.asBoolean ?: false
val haltOnMatch = input["haltOnMatch"]?.asBoolean ?: false
return TileRenderMatchPiece(
pieces = pieces,
matchAllPoints = matchAllPoints,
matchAnyPoints = matchAnyPoints,
subMatches = subMatches,
haltOnSubMatch = haltOnSubMatch,
haltOnMatch = haltOnMatch,
)
}
}
}
data class TileRenderMatch(
val name: String,
val pieces: List<TileRenderMatchPiece>,
) {
companion object {
fun fromJson(input: JsonArray, tilePieces: Map<String, TileRenderPiece>, rulePieces: Map<String, TileRenderRule>): TileRenderMatch {
val name = input[0].asString
val pieces = ArrayList<TileRenderMatchPiece>()
try {
for (elem in input[1].asJsonArray) {
pieces.add(TileRenderMatchPiece.fromJson(elem.asJsonObject, tilePieces, rulePieces))
}
} catch(err: Throwable) {
throw IllegalArgumentException("Failed to deserialize render match rule $name", err)
}
return TileRenderMatch(name, ImmutableList.copyOf(pieces))
}
}
}
class TileRenderTemplateParseException(message: String, cause: Throwable?) : IllegalStateException(message, cause)
data class TileRenderTemplate(
val representativePiece: String,
val pieces: Map<String, TileRenderPiece>,
val rules: Map<String, TileRenderRule>,
val matches: Map<String, TileRenderMatch>,
) {
companion object {
val map = HashMap<String, TileRenderTemplate>()
fun register(builder: GsonBuilder) {
builder.registerTypeAdapter(TileRenderTemplate::class.java, object : TypeAdapter<TileRenderTemplate>() {
override fun write(out: JsonWriter?, value: TileRenderTemplate?) {
TODO("Not yet implemented")
}
override fun read(`in`: JsonReader): TileRenderTemplate {
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`) as JsonObject)
}
})
}
fun load(path: String): TileRenderTemplate {
return map.computeIfAbsent(path) {
try {
val json = Starbound.loadJson(path).asJsonObject
return@computeIfAbsent fromJson(json)
} catch (err: Throwable) {
throw TileRenderTemplateParseException("Failed to load tile render definition from $path", err)
}
}
}
fun fromJson(input: JsonObject): TileRenderTemplate {
val representativePiece = input["representativePiece"].asString
val pieces = HashMap<String, TileRenderPiece>()
val rules = HashMap<String, TileRenderRule>()
val matches = HashMap<String, TileRenderMatch>()
val jPieces = input["pieces"] as JsonObject
for (key in jPieces.keySet()) {
pieces[key] = TileRenderPiece.fromJson(key, jPieces[key] as JsonObject)
}
val jRules = input["rules"] as JsonObject
for (key in jRules.keySet()) {
rules[key] = TileRenderRule.fromJson(key, jRules[key] as JsonObject)
}
val jMatches = input["matches"] as JsonArray
for (instance in jMatches) {
val deserialized = TileRenderMatch.fromJson(instance.asJsonArray, pieces, rules)
matches[deserialized.name] = deserialized
}
return TileRenderTemplate(representativePiece, ImmutableMap.copyOf(pieces), ImmutableMap.copyOf(rules), ImmutableMap.copyOf(matches))
}
}
}
data class TileRenderDefinition(
val texture: String,
val variants: Int,
val lightTransparent: Boolean,
val occludesBelow: Boolean,
val multiColored: Boolean,
val zLevel: Int,
val renderTemplate: TileRenderTemplate?
)
class TileRenderDefinitionBuilder {
var texture = ""
var variants = 1
var lightTransparent = false
var occludesBelow = false
var multiColored = false
var zLevel = 0
var renderTemplate: TileRenderTemplate? = null
fun build(directory: String? = null): TileRenderDefinition {
val newtexture: String
if (texture[0] == '/') {
// путь абсолютен
newtexture = texture
} else {
if (directory != null) {
newtexture = "$directory/$texture"
} else {
newtexture = texture
}
}
return TileRenderDefinition(
texture = newtexture,
variants = variants,
lightTransparent = lightTransparent,
occludesBelow = occludesBelow,
multiColored = multiColored,
zLevel = zLevel,
renderTemplate = renderTemplate,
)
}
}

View File

@ -16,6 +16,10 @@ data class MaterialModifier(
val renderTemplate: RenderTemplate, val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters val renderParameters: RenderParameters
) { ) {
init {
require(modId > 0) { "Invalid material modifier ID $modId" }
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class) val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
.plain(MaterialModifier::modId) .plain(MaterialModifier::modId)
@ -25,7 +29,7 @@ data class MaterialModifier(
.plain(MaterialModifier::health) .plain(MaterialModifier::health)
.plain(MaterialModifier::harvestLevel) .plain(MaterialModifier::harvestLevel)
.plain(MaterialModifier::breaksWithTile) .plain(MaterialModifier::breaksWithTile)
.list(MaterialModifier::miningSounds, String::class.java) .list(MaterialModifier::miningSounds, String::class)
.plain(MaterialModifier::miningParticle) .plain(MaterialModifier::miningParticle)
.plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE) .plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE)
.plain(MaterialModifier::renderParameters) .plain(MaterialModifier::renderParameters)

View File

@ -1,20 +1,37 @@
package ru.dbotthepony.kstarbound.defs.tile package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
data class RenderParameters( data class RenderParameters(
val texture: String, val texture: String,
val variants: Int, val variants: Int = 0,
val multiColored: Boolean, val multiColored: Boolean = false,
val occludesBelow: Boolean = false,
val lightTransparent: Boolean = false,
val zLevel: Int, val zLevel: Int,
) { ) {
val absoluteTexturePath: String
init {
val dir = Starbound.readingFolder
if (dir == null || texture[0] == '/') {
absoluteTexturePath = texture
} else {
absoluteTexturePath = "$dir/$texture"
}
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class) val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class)
.plain( .plain(
RenderParameters::texture, RenderParameters::texture,
RenderParameters::variants, RenderParameters::variants,
RenderParameters::multiColored, RenderParameters::multiColored,
RenderParameters::occludesBelow,
RenderParameters::lightTransparent,
RenderParameters::zLevel, RenderParameters::zLevel,
) )
.build() .build()

View File

@ -6,9 +6,13 @@ import com.google.gson.TypeAdapter
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.ObjectArraySet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
import ru.dbotthepony.kstarbound.io.EnumAdapter
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -33,80 +37,184 @@ data class RenderPiece(
} }
} }
data class RenderRule(
val type: String,
val matchHue: Boolean = false,
val inverse: Boolean = false,
) {
private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) {
"EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef
"Connects" -> getter[thisPos + offsetPos] != null
else -> false
}
}
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) {
return !doTest(getter, thisRef, thisPos, offsetPos)
}
return doTest(getter, thisRef, thisPos, offsetPos)
}
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderRule::class)
.plain(
RenderRule::type,
RenderRule::matchHue,
RenderRule::inverse,
)
.build()
}
}
data class RenderRuleList( data class RenderRuleList(
val entries: List<RenderRule> val entries: List<Entry>,
val join: Combination = Combination.ALL
) { ) {
enum class Combination {
ALL, ANY;
}
data class Entry(
val type: String,
val matchHue: Boolean = false,
val inverse: Boolean = false,
) {
private fun doTest(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) {
"EqualsSelf" -> getter[thisPos + offsetPos]?.def == thisRef
"Connects" -> getter[thisPos + offsetPos] != null
else -> {
if (LOGGED.add(type)) {
LOGGER.error("Unknown render rule test $type!")
}
false
}
}
}
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) {
return !doTest(getter, thisRef, thisPos, offsetPos)
}
return doTest(getter, thisRef, thisPos, offsetPos)
}
companion object {
private val LOGGER = LogManager.getLogger()
private val LOGGED = ObjectArraySet<String>()
val ADAPTER = KConcreteTypeAdapter.Builder(Entry::class)
.plain(
Entry::type,
Entry::matchHue,
Entry::inverse,
)
.build()
}
}
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i, offset: Vector2i): Boolean {
when (join) {
Combination.ALL -> {
for (entry in entries) {
if (!entry.test(getter, thisRef, thisPos, offset)) {
return false
}
}
return true
}
Combination.ANY -> {
for (entry in entries) {
if (entry.test(getter, thisRef, thisPos, offset)) {
return true
}
}
return false
}
}
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class) val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class)
.list(RenderRuleList::entries, RenderRule::class) .list(RenderRuleList::entries, Entry::class)
.plain(RenderRuleList::join)
.build() .build()
} }
} }
data class RenderMatch( data class RenderMatch(
val pieces: List<Piece>? = null, val pieces: List<Piece> = listOf(),
val matchAllPoints: List<Matcher>? = null, val matchAllPoints: List<Matcher> = listOf(),
val subMatches: List<RenderMatch>? = null, val matchAnyPoints: List<Matcher> = listOf(),
val haltOnMatch: Boolean = false val subMatches: List<RenderMatch> = listOf(),
val haltOnMatch: Boolean = false,
val haltOnSubMatch: Boolean = false,
) { ) {
data class Piece( data class Piece(
val name: String, val name: String,
val point: Vector2i val offset: Vector2i
) ) {
var piece by WriteOnce<RenderPiece>()
fun resolve(template: RenderTemplate) {
piece = template.pieces[name] ?: throw IllegalStateException("Unable to find render piece $name")
}
}
data class Matcher( data class Matcher(
val point: Vector2i, val offset: Vector2i,
val ruleName: String val ruleName: String
) ) {
var rule by WriteOnce<RenderRuleList>()
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
return rule.test(getter, thisRef, thisPos, offset)
}
fun resolve(template: RenderTemplate) {
rule = template.rules[ruleName] ?: throw IllegalStateException("Unable to find render rule $ruleName")
}
}
fun resolve(template: RenderTemplate) {
for (value in matchAllPoints) {
value.resolve(template)
}
for (value in pieces) {
value.resolve(template)
}
for (value in matchAnyPoints) {
value.resolve(template)
}
for (value in subMatches) {
value.resolve(template)
}
}
/**
* Возвращает, сработали ли ВСЕ [matchAllPoints] или ЛЮБОЙ ИЗ [matchAnyPoints]
*
* Если хотя бы один из них вернул false, весь тест возвращает false
*
* [subMatches] стоит итерировать только если это вернуло true
*/
fun test(getter: ITileGetter, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
for (matcher in matchAllPoints) {
if (!matcher.test(getter, thisRef, thisPos)) {
return false
}
}
if (matchAnyPoints.isEmpty()) {
return true
}
for (matcher in matchAnyPoints) {
if (matcher.test(getter, thisRef, thisPos)) {
return true
}
}
return false
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class) val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class)
.list(RenderMatch::pieces, Piece::class) .list(RenderMatch::pieces, Piece::class)
.list(RenderMatch::matchAllPoints, Matcher::class) .list(RenderMatch::matchAllPoints, Matcher::class)
.list(RenderMatch::matchAnyPoints, Matcher::class)
.list(RenderMatch::subMatches, RenderMatch::class) .list(RenderMatch::subMatches, RenderMatch::class)
.plain(RenderMatch::haltOnMatch) .plain(RenderMatch::haltOnMatch)
.plain(RenderMatch::haltOnSubMatch)
.build() .build()
val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class) val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class)
.plain(Piece::name) .plain(Piece::name)
.plain(Piece::point) .plain(Piece::offset)
.build(asList = true) .build(asList = true)
val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class) val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class)
.plain(Matcher::point) .plain(Matcher::offset)
.plain(Matcher::ruleName) .plain(Matcher::ruleName)
.build(asList = true) .build(asList = true)
} }
@ -116,6 +224,12 @@ data class RenderMatchList(
val name: String, val name: String,
val list: List<RenderMatch> val list: List<RenderMatch>
) { ) {
fun resolve(template: RenderTemplate) {
for (value in list) {
value.resolve(template)
}
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class) val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class)
.plain(RenderMatchList::name) .plain(RenderMatchList::name)
@ -127,18 +241,26 @@ data class RenderMatchList(
data class RenderTemplate( data class RenderTemplate(
val pieces: Map<String, RenderPiece>, val pieces: Map<String, RenderPiece>,
val representativePiece: String, val representativePiece: String,
val matches: List<RenderMatchList> val matches: List<RenderMatchList>,
val rules: Map<String, RenderRuleList>,
) { ) {
init {
for (value in matches) {
value.resolve(this)
}
}
companion object { companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class) val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class)
.map(RenderTemplate::pieces, RenderPiece::class.java) .map(RenderTemplate::pieces, RenderPiece::class.java)
.plain(RenderTemplate::representativePiece) .plain(RenderTemplate::representativePiece)
.list(RenderTemplate::matches, RenderMatchList::class.java) .list(RenderTemplate::matches, RenderMatchList::class.java)
.map(RenderTemplate::rules, RenderRuleList::class.java)
.build() .build()
fun registerGson(gsonBuilder: GsonBuilder) { fun registerGson(gsonBuilder: GsonBuilder) {
RenderPiece.ADAPTER.register(gsonBuilder) RenderPiece.ADAPTER.register(gsonBuilder)
RenderRule.ADAPTER.register(gsonBuilder) RenderRuleList.Entry.ADAPTER.register(gsonBuilder)
RenderRuleList.ADAPTER.register(gsonBuilder) RenderRuleList.ADAPTER.register(gsonBuilder)
RenderMatch.ADAPTER.register(gsonBuilder) RenderMatch.ADAPTER.register(gsonBuilder)
RenderMatch.PIECE_ADAPTER.register(gsonBuilder) RenderMatch.PIECE_ADAPTER.register(gsonBuilder)
@ -146,17 +268,11 @@ data class RenderTemplate(
RenderMatchList.ADAPTER.register(gsonBuilder) RenderMatchList.ADAPTER.register(gsonBuilder)
ADAPTER.register(gsonBuilder) ADAPTER.register(gsonBuilder)
gsonBuilder.registerTypeAdapter(RenderRuleList.Combination::class.java, EnumAdapter(RenderRuleList.Combination::class.java))
} }
private val cache = ConcurrentHashMap<String, RenderTemplate>() private val cache = ConcurrentHashMap<String, RenderTemplate>()
private val _readingFolder = ThreadLocal<String>()
/**
* Случит переменной для указания из какой папки происходит чтение в данном потоке
*/
var readingFolder: String?
get() = _readingFolder.get()
set(value) { _readingFolder.set(value) }
val CACHE = object : TypeAdapter<RenderTemplate>() { val CACHE = object : TypeAdapter<RenderTemplate>() {
override fun write(out: JsonWriter, value: RenderTemplate) { override fun write(out: JsonWriter, value: RenderTemplate) {
@ -173,7 +289,7 @@ data class RenderTemplate(
if (path[0] != '/') { if (path[0] != '/') {
// относительный путь // относительный путь
val readingFolder = readingFolder ?: throw NullPointerException("Currently read folder is not specified") val readingFolder = Starbound.readingFolder ?: throw NullPointerException("Currently read folder is not specified")
path = "$readingFolder/$path" path = "$readingFolder/$path"
} }

View File

@ -0,0 +1,50 @@
package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kvector.vector.Color
data class TileDefinition(
val materialId: Int,
val materialName: String,
val particleColor: Color? = null,
val itemDrop: String? = null,
val description: String = "...",
val shortdescription: String = "...",
val footstepSound: String? = null,
val blocksLiquidFlow: Boolean = true,
val soil: Boolean = false,
val health: Double = 0.0,
val category: String,
val renderTemplate: RenderTemplate,
val renderParameters: RenderParameters,
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(TileDefinition::class)
.plain(
TileDefinition::materialId,
TileDefinition::materialName,
TileDefinition::particleColor,
TileDefinition::itemDrop,
TileDefinition::description,
TileDefinition::shortdescription,
TileDefinition::footstepSound,
TileDefinition::blocksLiquidFlow,
TileDefinition::soil,
TileDefinition::health,
TileDefinition::category
)
.plain(TileDefinition::renderTemplate, RenderTemplate.CACHE)
.plain(
TileDefinition::renderParameters,
)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
ADAPTER.register(gsonBuilder)
}
}
}

View File

@ -0,0 +1,40 @@
package ru.dbotthepony.kstarbound.io
import com.google.common.collect.ImmutableMap
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
class EnumAdapter<T : Enum<T>>(private val enum: Class<T>) : TypeAdapter<T>() {
private val mapping: ImmutableMap<String, T> = Object2ObjectArrayMap<String, T>().let {
for (value in enum.enumConstants) {
it[value.name] = value
it[value.name.uppercase()] = value
it[value.name.lowercase()] = value
val spaced = value.name.replace('_', ' ')
val stitched = value.name.replace("_", "")
it[spaced] = value
it[spaced.uppercase()] = value
it[spaced.lowercase()] = value
it[stitched] = value
it[stitched.uppercase()] = value
it[stitched.lowercase()] = value
}
ImmutableMap.copyOf(it)
}
override fun write(out: JsonWriter, value: T) {
out.value(value.name)
}
override fun read(`in`: JsonReader): T {
val key = `in`.nextString()
return mapping[key] ?: throw JsonSyntaxException("Unable to match '$key' against ${enum.canonicalName}")
}
}

View File

@ -294,7 +294,7 @@ class KConcreteTypeAdapter<T : Any>(
readValues[fieldId] = adapter.read(reader) readValues[fieldId] = adapter.read(reader)
presentValues[fieldId] = true presentValues[fieldId] = true
} catch(err: Throwable) { } catch(err: Throwable) {
throw JsonSyntaxException("Exception reading field ${field.name}", err) throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err)
} }
fieldId++ fieldId++
@ -317,7 +317,7 @@ class KConcreteTypeAdapter<T : Any>(
readValues[fieldId] = adapter.read(reader) readValues[fieldId] = adapter.read(reader)
presentValues[fieldId] = true presentValues[fieldId] = true
} catch(err: Throwable) { } catch(err: Throwable) {
throw JsonSyntaxException("Exception reading field ${field.name}", err) throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err)
} }
} }
} }
@ -341,11 +341,11 @@ class KConcreteTypeAdapter<T : Any>(
if (readValues[i] == null) { if (readValues[i] == null) {
if (!returnTypeCache[field]!!.isMarkedNullable) { if (!returnTypeCache[field]!!.isMarkedNullable) {
throw JsonSyntaxException("Field ${field.name} does not accept nulls") throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls")
} }
if (!regularFactory.parameters[i].isOptional && !presentValues[i]) { if (!regularFactory.parameters[i].isOptional && !presentValues[i]) {
throw JsonSyntaxException("Field ${field.name} must be defined (even just as null)") throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} must be defined (even just as null)")
} }
} }
} }
@ -388,7 +388,7 @@ class KConcreteTypeAdapter<T : Any>(
val param = regularFactory.parameters[i] val param = regularFactory.parameters[i]
if (!param.isOptional && !presentValues[i]) { if (!param.isOptional && !presentValues[i]) {
throw JsonSyntaxException("Field ${field.name} is missing") throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} is missing")
} }
if (returnTypeCache[field]!!.isMarkedNullable) { if (returnTypeCache[field]!!.isMarkedNullable) {
@ -396,11 +396,11 @@ class KConcreteTypeAdapter<T : Any>(
} }
if (param.isOptional && !presentValues[i]) { if (param.isOptional && !presentValues[i]) {
copied[i] = syntheticPrimitives[i] ?: throw NullPointerException("HOW $i") copied[i] = syntheticPrimitives[i]
continue continue
} }
throw JsonSyntaxException("Field ${field.name} does not accept nulls") throw JsonSyntaxException("Field ${field.name} of ${bound.qualifiedName} does not accept nulls")
} }
return syntheticFactory.newInstance(*copied) return syntheticFactory.newInstance(*copied)

View File

@ -0,0 +1,20 @@
package ru.dbotthepony.kstarbound.util
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class WriteOnce<V> : ReadWriteProperty<Any, V> {
private var value: V? = null
override fun getValue(thisRef: Any, property: KProperty<*>): V {
return value ?: throw IllegalStateException("Property ${property.name} is not initialized")
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: V) {
if (this.value != null) {
throw IllegalStateException("Already initialized")
}
this.value = value
}
}

View File

@ -4,7 +4,7 @@ import ru.dbotthepony.kbox2d.api.BodyDef
import ru.dbotthepony.kbox2d.api.FixtureDef import ru.dbotthepony.kbox2d.api.FixtureDef
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2Fixture import ru.dbotthepony.kbox2d.dynamics.B2Fixture
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst

View File

@ -1,6 +1,6 @@
package ru.dbotthepony.kstarbound.world package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
const val CHUNK_SHIFT = 5 const val CHUNK_SHIFT = 5

View File

@ -1,6 +1,6 @@
package ru.dbotthepony.kstarbound.world package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
/** /**
* Предоставляет доступ к чанку и его соседям * Предоставляет доступ к чанку и его соседям

View File

@ -10,7 +10,7 @@ import ru.dbotthepony.kbox2d.dynamics.B2Fixture
import ru.dbotthepony.kbox2d.dynamics.B2World import ru.dbotthepony.kbox2d.dynamics.B2World
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.Timer import ru.dbotthepony.kstarbound.util.Timer
import ru.dbotthepony.kstarbound.world.entities.CollisionResolution import ru.dbotthepony.kstarbound.world.entities.CollisionResolution