Move tile definition and render template to KConcreteTypeAdapter
This commit is contained in:
parent
d3396ddb7c
commit
7c318966d5
@ -19,61 +19,9 @@ private val LOGGER = LogManager.getLogger()
|
||||
fun main() {
|
||||
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("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()
|
||||
|
||||
//Starbound.addFilePath(File("./unpacked_assets/"))
|
||||
@ -91,34 +39,6 @@ fun main() {
|
||||
val ent = PlayerEntity(client.world!!)
|
||||
|
||||
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 set = 0L
|
||||
var parse = 0L
|
||||
|
@ -12,6 +12,7 @@ import ru.dbotthepony.kstarbound.defs.projectile.*
|
||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
||||
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.dungeon.DungeonWorldDef
|
||||
import ru.dbotthepony.kstarbound.io.*
|
||||
@ -41,6 +42,15 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f
|
||||
object Starbound : IVFS {
|
||||
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 tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
|
||||
private val projectiles = HashMap<String, ConfiguredProjectile>()
|
||||
@ -77,6 +87,7 @@ object Starbound : IVFS {
|
||||
.also(MaterialModifier::registerGson)
|
||||
.also(RenderParameters::registerGson)
|
||||
.also(RenderTemplate::registerGson)
|
||||
.also(TileDefinition::registerGson)
|
||||
|
||||
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
|
||||
|
||||
@ -231,12 +242,15 @@ object Starbound : IVFS {
|
||||
}
|
||||
|
||||
private fun loadTileMaterials(callback: (String) -> Unit) {
|
||||
readingFolder = "/tiles/materials"
|
||||
|
||||
for (fs in fileSystems) {
|
||||
for (listedFile in fs.listAllFilesWithExtension("material")) {
|
||||
try {
|
||||
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(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) {
|
||||
|
@ -5,9 +5,9 @@ import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.client.gl.*
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderMatchPiece
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderPiece
|
||||
import ru.dbotthepony.kstarbound.defs.tile.RenderMatch
|
||||
import ru.dbotthepony.kstarbound.defs.tile.RenderPiece
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.world.ITileChunk
|
||||
import ru.dbotthepony.kvector.vector.Color
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||
@ -152,7 +152,7 @@ private enum class TileRenderTesselateResult {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
||||
val bakedBackgroundProgramState = state.tileRenderers.background(texture)
|
||||
// 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 fy = pos.y.toFloat()
|
||||
|
||||
@ -173,7 +173,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
||||
if (offset != Vector2i.ZERO) {
|
||||
a += offset.x / PIXELS_IN_STARBOUND_UNITf
|
||||
|
||||
// в json файлах y указан как положительный вверх,
|
||||
// в json файлах Y указан как положительный вверх,
|
||||
// что соответствует нашему миру
|
||||
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
|
||||
}
|
||||
|
||||
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 (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
||||
|
||||
@ -192,7 +192,7 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
||||
d,
|
||||
Z_LEVEL, VertexTransformers.uv(u0, v1, u1, v0))
|
||||
} 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 (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)) {
|
||||
for (renderPiece in matchPiece.pieces) {
|
||||
if (renderPiece.piece.texture != null) {
|
||||
val program: BakedProgramState
|
||||
|
||||
if (background) {
|
||||
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture))
|
||||
program = state.tileRenderers.background(state.loadNamedTexture(renderPiece.piece.texture!!))
|
||||
} 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)
|
||||
}, pos, renderPiece.offset)
|
||||
} else {
|
||||
@ -256,14 +256,14 @@ class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
||||
fun tesselate(getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false) {
|
||||
// если у нас нет 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)
|
||||
}
|
||||
|
||||
for ((_, matcher) in tile.render.renderTemplate.matches) {
|
||||
for (matchPiece in matcher.pieces) {
|
||||
for ((_, matcher) in template.matches) {
|
||||
for (matchPiece in matcher) {
|
||||
val matched = tesselatePiece(matchPiece, getter, layers, pos, builder, background)
|
||||
|
||||
if (matched == TileRenderTesselateResult.HALT) {
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@ data class MaterialModifier(
|
||||
val renderTemplate: RenderTemplate,
|
||||
val renderParameters: RenderParameters
|
||||
) {
|
||||
init {
|
||||
require(modId > 0) { "Invalid material modifier ID $modId" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(MaterialModifier::class)
|
||||
.plain(MaterialModifier::modId)
|
||||
@ -25,7 +29,7 @@ data class MaterialModifier(
|
||||
.plain(MaterialModifier::health)
|
||||
.plain(MaterialModifier::harvestLevel)
|
||||
.plain(MaterialModifier::breaksWithTile)
|
||||
.list(MaterialModifier::miningSounds, String::class.java)
|
||||
.list(MaterialModifier::miningSounds, String::class)
|
||||
.plain(MaterialModifier::miningParticle)
|
||||
.plain(MaterialModifier::renderTemplate, RenderTemplate.CACHE)
|
||||
.plain(MaterialModifier::renderParameters)
|
||||
|
@ -1,20 +1,37 @@
|
||||
package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
|
||||
|
||||
data class RenderParameters(
|
||||
val texture: String,
|
||||
val variants: Int,
|
||||
val multiColored: Boolean,
|
||||
val variants: Int = 0,
|
||||
val multiColored: Boolean = false,
|
||||
val occludesBelow: Boolean = false,
|
||||
val lightTransparent: Boolean = false,
|
||||
val zLevel: Int,
|
||||
) {
|
||||
val absoluteTexturePath: String
|
||||
|
||||
init {
|
||||
val dir = Starbound.readingFolder
|
||||
|
||||
if (dir == null || texture[0] == '/') {
|
||||
absoluteTexturePath = texture
|
||||
} else {
|
||||
absoluteTexturePath = "$dir/$texture"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(RenderParameters::class)
|
||||
.plain(
|
||||
RenderParameters::texture,
|
||||
RenderParameters::variants,
|
||||
RenderParameters::multiColored,
|
||||
RenderParameters::occludesBelow,
|
||||
RenderParameters::lightTransparent,
|
||||
RenderParameters::zLevel,
|
||||
)
|
||||
.build()
|
||||
|
@ -6,9 +6,13 @@ import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
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.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.EnumAdapter
|
||||
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.util.WriteOnce
|
||||
import ru.dbotthepony.kstarbound.world.ITileGetter
|
||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||
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(
|
||||
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 {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(RenderRuleList::class)
|
||||
.list(RenderRuleList::entries, RenderRule::class)
|
||||
.list(RenderRuleList::entries, Entry::class)
|
||||
.plain(RenderRuleList::join)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
data class RenderMatch(
|
||||
val pieces: List<Piece>? = null,
|
||||
val matchAllPoints: List<Matcher>? = null,
|
||||
val subMatches: List<RenderMatch>? = null,
|
||||
val haltOnMatch: Boolean = false
|
||||
val pieces: List<Piece> = listOf(),
|
||||
val matchAllPoints: List<Matcher> = listOf(),
|
||||
val matchAnyPoints: List<Matcher> = listOf(),
|
||||
val subMatches: List<RenderMatch> = listOf(),
|
||||
val haltOnMatch: Boolean = false,
|
||||
val haltOnSubMatch: Boolean = false,
|
||||
) {
|
||||
data class Piece(
|
||||
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(
|
||||
val point: Vector2i,
|
||||
val offset: Vector2i,
|
||||
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 {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatch::class)
|
||||
.list(RenderMatch::pieces, Piece::class)
|
||||
.list(RenderMatch::matchAllPoints, Matcher::class)
|
||||
.list(RenderMatch::matchAnyPoints, Matcher::class)
|
||||
.list(RenderMatch::subMatches, RenderMatch::class)
|
||||
.plain(RenderMatch::haltOnMatch)
|
||||
.plain(RenderMatch::haltOnSubMatch)
|
||||
.build()
|
||||
|
||||
val PIECE_ADAPTER = KConcreteTypeAdapter.Builder(Piece::class)
|
||||
.plain(Piece::name)
|
||||
.plain(Piece::point)
|
||||
.plain(Piece::offset)
|
||||
.build(asList = true)
|
||||
|
||||
val MATCHER_ADAPTER = KConcreteTypeAdapter.Builder(Matcher::class)
|
||||
.plain(Matcher::point)
|
||||
.plain(Matcher::offset)
|
||||
.plain(Matcher::ruleName)
|
||||
.build(asList = true)
|
||||
}
|
||||
@ -116,6 +224,12 @@ data class RenderMatchList(
|
||||
val name: String,
|
||||
val list: List<RenderMatch>
|
||||
) {
|
||||
fun resolve(template: RenderTemplate) {
|
||||
for (value in list) {
|
||||
value.resolve(template)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(RenderMatchList::class)
|
||||
.plain(RenderMatchList::name)
|
||||
@ -127,18 +241,26 @@ data class RenderMatchList(
|
||||
data class RenderTemplate(
|
||||
val pieces: Map<String, RenderPiece>,
|
||||
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 {
|
||||
val ADAPTER = KConcreteTypeAdapter.Builder(RenderTemplate::class)
|
||||
.map(RenderTemplate::pieces, RenderPiece::class.java)
|
||||
.plain(RenderTemplate::representativePiece)
|
||||
.list(RenderTemplate::matches, RenderMatchList::class.java)
|
||||
.map(RenderTemplate::rules, RenderRuleList::class.java)
|
||||
.build()
|
||||
|
||||
fun registerGson(gsonBuilder: GsonBuilder) {
|
||||
RenderPiece.ADAPTER.register(gsonBuilder)
|
||||
RenderRule.ADAPTER.register(gsonBuilder)
|
||||
RenderRuleList.Entry.ADAPTER.register(gsonBuilder)
|
||||
RenderRuleList.ADAPTER.register(gsonBuilder)
|
||||
RenderMatch.ADAPTER.register(gsonBuilder)
|
||||
RenderMatch.PIECE_ADAPTER.register(gsonBuilder)
|
||||
@ -146,17 +268,11 @@ data class RenderTemplate(
|
||||
RenderMatchList.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 _readingFolder = ThreadLocal<String>()
|
||||
|
||||
/**
|
||||
* Случит переменной для указания из какой папки происходит чтение в данном потоке
|
||||
*/
|
||||
var readingFolder: String?
|
||||
get() = _readingFolder.get()
|
||||
set(value) { _readingFolder.set(value) }
|
||||
|
||||
val CACHE = object : TypeAdapter<RenderTemplate>() {
|
||||
override fun write(out: JsonWriter, value: RenderTemplate) {
|
||||
@ -173,7 +289,7 @@ data class RenderTemplate(
|
||||
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"
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
40
src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt
Normal file
40
src/main/kotlin/ru/dbotthepony/kstarbound/io/EnumAdapter.kt
Normal 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}")
|
||||
}
|
||||
}
|
@ -294,7 +294,7 @@ class KConcreteTypeAdapter<T : Any>(
|
||||
readValues[fieldId] = adapter.read(reader)
|
||||
presentValues[fieldId] = true
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Exception reading field ${field.name}", err)
|
||||
throw JsonSyntaxException("Exception reading field ${field.name} for ${bound.qualifiedName}", err)
|
||||
}
|
||||
|
||||
fieldId++
|
||||
@ -317,7 +317,7 @@ class KConcreteTypeAdapter<T : Any>(
|
||||
readValues[fieldId] = adapter.read(reader)
|
||||
presentValues[fieldId] = true
|
||||
} 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 (!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]) {
|
||||
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]
|
||||
|
||||
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) {
|
||||
@ -396,11 +396,11 @@ class KConcreteTypeAdapter<T : Any>(
|
||||
}
|
||||
|
||||
if (param.isOptional && !presentValues[i]) {
|
||||
copied[i] = syntheticPrimitives[i] ?: throw NullPointerException("HOW $i")
|
||||
copied[i] = syntheticPrimitives[i]
|
||||
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)
|
||||
|
20
src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt
Normal file
20
src/main/kotlin/ru/dbotthepony/kstarbound/util/WriteOnce.kt
Normal 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
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import ru.dbotthepony.kbox2d.api.BodyDef
|
||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||
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.phys.RectTileFlooderDepthFirst
|
||||
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst
|
||||
|
@ -1,6 +1,6 @@
|
||||
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
|
||||
|
||||
const val CHUNK_SHIFT = 5
|
||||
|
@ -1,6 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
|
||||
/**
|
||||
* Предоставляет доступ к чанку и его соседям
|
||||
|
@ -10,7 +10,7 @@ import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2World
|
||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||
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.util.Timer
|
||||
import ru.dbotthepony.kstarbound.world.entities.CollisionResolution
|
||||
|
Loading…
Reference in New Issue
Block a user