KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt

314 lines
8.3 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
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.io.json.builder.EnumAdapter
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.registerTypeAdapter
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kstarbound.world.ITileState
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap
data class RenderPiece(
val texture: String? = null,
val textureSize: Vector2i,
val texturePosition: Vector2i,
val colorStride: Vector2i? = null,
val variantStride: Vector2i? = null,
) {
companion object {
val ADAPTER = FactoryAdapter.Builder(RenderPiece::class)
.auto(
RenderPiece::texture,
RenderPiece::textureSize,
RenderPiece::texturePosition,
RenderPiece::colorStride,
RenderPiece::variantStride,
)
.build()
}
}
fun interface EqualityRuleTester {
fun test(thisTile: ITileState, otherTile: ITileState): Boolean
}
data class RenderRuleList(
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, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) {
"EqualsSelf" -> equalityTester.test(getter[thisPos], getter[thisPos + offsetPos])
"Connects" -> getter[thisPos + offsetPos].material != null
else -> {
if (LOGGED.add(type)) {
LOGGER.error("Unknown render rule test $type!")
}
false
}
}
}
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) {
return !doTest(getter, equalityTester, thisPos, offsetPos)
}
return doTest(getter, equalityTester, thisPos, offsetPos)
}
companion object {
private val LOGGER = LogManager.getLogger()
private val LOGGED = ObjectArraySet<String>()
val ADAPTER = FactoryAdapter.Builder(Entry::class)
.auto(
Entry::type,
Entry::matchHue,
Entry::inverse,
)
.build()
}
}
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
when (join) {
Combination.ALL -> {
for (entry in entries) {
if (!entry.test(getter, equalityTester, thisPos, offset)) {
return false
}
}
return true
}
Combination.ANY -> {
for (entry in entries) {
if (entry.test(getter, equalityTester, thisPos, offset)) {
return true
}
}
return false
}
}
}
companion object {
val ADAPTER = FactoryAdapter.Builder(RenderRuleList::class)
.autoList(RenderRuleList::entries)
.auto(RenderRuleList::join)
.build()
}
}
data class RenderMatch(
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 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 offset: Vector2i,
val ruleName: String
) {
var rule by WriteOnce<RenderRuleList>()
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
return rule.test(getter, equalityTester, 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
*
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
*/
fun test(tileAccess: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
for (matcher in matchAllPoints) {
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
return false
}
}
if (matchAnyPoints.isEmpty()) {
return true
}
for (matcher in matchAnyPoints) {
if (matcher.test(tileAccess, equalityTester, thisPos)) {
return true
}
}
return false
}
companion object {
val ADAPTER = FactoryAdapter.Builder(RenderMatch::class)
.autoList(RenderMatch::pieces)
.autoList(RenderMatch::matchAllPoints)
.autoList(RenderMatch::matchAnyPoints)
.autoList(RenderMatch::subMatches)
.auto(RenderMatch::haltOnMatch)
.auto(RenderMatch::haltOnSubMatch)
.build()
val PIECE_ADAPTER = FactoryAdapter.Builder(Piece::class)
.auto(Piece::name)
.auto(Piece::offset)
.inputAsList()
.build()
val MATCHER_ADAPTER = FactoryAdapter.Builder(Matcher::class)
.auto(Matcher::offset)
.auto(Matcher::ruleName)
.inputAsList()
.build()
}
}
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 = FactoryAdapter.Builder(RenderMatchList::class)
.auto(RenderMatchList::name)
.autoList(RenderMatchList::list)
.inputAsList()
.build()
}
}
data class RenderTemplate(
val pieces: Map<String, RenderPiece>,
val representativePiece: String,
val matches: List<RenderMatchList>,
val rules: Map<String, RenderRuleList>,
) {
init {
for (value in matches) {
value.resolve(this)
}
}
companion object {
val ADAPTER = FactoryAdapter.Builder(RenderTemplate::class)
.mapAsObject(RenderTemplate::pieces, RenderPiece::class.java)
.auto(RenderTemplate::representativePiece)
.list(RenderTemplate::matches, RenderMatchList::class.java)
.mapAsObject(RenderTemplate::rules, RenderRuleList::class.java)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(RenderPiece.ADAPTER)
gsonBuilder.registerTypeAdapter(RenderRuleList.ADAPTER)
gsonBuilder.registerTypeAdapter(RenderMatch.ADAPTER)
gsonBuilder.registerTypeAdapter(RenderMatch.PIECE_ADAPTER)
gsonBuilder.registerTypeAdapter(RenderMatch.MATCHER_ADAPTER)
gsonBuilder.registerTypeAdapter(RenderMatchList.ADAPTER)
gsonBuilder.registerTypeAdapter(RenderRuleList.Entry.ADAPTER)
gsonBuilder.registerTypeAdapter(ADAPTER)
gsonBuilder.registerTypeAdapter(EnumAdapter(RenderRuleList.Combination::class.java))
}
private val cache = ConcurrentHashMap<String, RenderTemplate>()
val CACHE = object : TypeAdapter<RenderTemplate>() {
override fun write(out: JsonWriter, value: RenderTemplate) {
ADAPTER.write(out, value)
}
override fun read(reader: JsonReader): RenderTemplate {
if (reader.peek() != JsonToken.STRING) {
throw JsonSyntaxException("Expected string as input for render template cache retriever")
}
var path = reader.nextString()
if (path[0] != '/') {
// относительный путь
val readingFolder = Starbound.assetFolder ?: throw NullPointerException("Currently read folder is not specified")
path = "$readingFolder/$path"
}
return cache.computeIfAbsent(path) {
return@computeIfAbsent Starbound.GSON.fromJson(Starbound.locate(it).reader(), RenderTemplate::class.java)
}
}
}
}
}