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() {
|
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
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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 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)
|
||||||
|
@ -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()
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
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)
|
||||||
|
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.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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Предоставляет доступ к чанку и его соседям
|
* Предоставляет доступ к чанку и его соседям
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user