[WIP] Config parser
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ build/
|
||||
classes/
|
||||
temp/
|
||||
logs
|
||||
src/main/javacc/mods
|
||||
|
||||
@@ -2,6 +2,7 @@ plugins {
|
||||
kotlin("jvm").version("1.4.20")
|
||||
id("net.minecraftforge.gradle").version("4.1.12")
|
||||
id("org.spongepowered.mixin").version("0.7-SNAPSHOT")
|
||||
id("com.intershop.gradle.javacc").version("4.0.0")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -20,6 +21,7 @@ dependencies {
|
||||
configurations["annotationProcessor"].extendsFrom(configurations["implementation"])
|
||||
sourceSets {
|
||||
get("main").ext["refMap"] = "betterfoliage.refmap.json"
|
||||
get("main").java.srcDir("src/main/javacc/")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
@@ -36,6 +38,17 @@ minecraft {
|
||||
}
|
||||
}
|
||||
|
||||
javacc {
|
||||
configs {
|
||||
create("blockconfig") {
|
||||
staticParam = "false"
|
||||
inputFile = file("src/main/javacc/BlockConfig.jj")
|
||||
outputDir = file("src/main/javacc/")
|
||||
packageName = "mods.betterfoliage.config.match.parser"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
102
src/main/javacc/BlockConfig.jj
Normal file
102
src/main/javacc/BlockConfig.jj
Normal file
@@ -0,0 +1,102 @@
|
||||
PARSER_BEGIN(BlockConfigParser)
|
||||
package mods.betterfoliage.config.match.parser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import mods.betterfoliage.config.match.*;
|
||||
|
||||
public class BlockConfigParser {
|
||||
public String configFile;
|
||||
|
||||
ConfigSource getSource(Token t) {
|
||||
return new ConfigSource(configFile, t.beginLine, t.beginColumn);
|
||||
}
|
||||
}
|
||||
|
||||
PARSER_END(BlockConfigParser)
|
||||
|
||||
// Whitespace definition
|
||||
SKIP : { " " | "\n" | "\t" | "\r" }
|
||||
|
||||
// Single-line comment
|
||||
SPECIAL_TOKEN : { <lineComment: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")> }
|
||||
|
||||
// Lexical state for string literal in quotes
|
||||
SPECIAL_TOKEN : { < quoteStart : "\"" > : withinQuotes }
|
||||
<withinQuotes> SPECIAL_TOKEN : { < quoteEnd : "\"" > : DEFAULT }
|
||||
<withinQuotes> TOKEN : { < stringLiteral : (["a"-"z"] | ["0"-"9"] | "/" | "." | "_" | "-" | ":" )* > }
|
||||
|
||||
// Symbol tokens
|
||||
TOKEN : {
|
||||
< parenStart : "(" > |
|
||||
< parenEnd : ")" > |
|
||||
< dot : "." > |
|
||||
< comma : "," >
|
||||
}
|
||||
|
||||
void matchFile(List<Node.MatchAll> parent) : {
|
||||
Token t;
|
||||
} {
|
||||
(
|
||||
t = "match"
|
||||
{ List<Node> nodes = new LinkedList<Node>(); }
|
||||
(match(nodes))*
|
||||
"end"
|
||||
{ parent.add(new Node.MatchAll(getSource(t), nodes)); }
|
||||
)*
|
||||
}
|
||||
|
||||
void match(List<Node> parent) : {
|
||||
Token t; Token t2; MatchMethod mm; List<Node.Value> values; Node.Value v;
|
||||
} {
|
||||
"block." matchBlock(parent)
|
||||
|
|
||||
t = "model." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
|
||||
{ parent.add(new Node.MatchValueList(Node.MatchSource.MODEL_LOCATION, mm, getSource(t), values)); }
|
||||
|
|
||||
t = "isParam" <parenStart> t2 = <stringLiteral> <comma> values = matchValueList() <parenEnd>
|
||||
{ parent.add(new Node.MatchParam(t2.image, values, getSource(t))); }
|
||||
|
|
||||
t = "setParam" <parenStart> t2 = <stringLiteral> <comma> v = matchValue() <parenEnd>
|
||||
{ parent.add(new Node.SetParam(t2.image, v, getSource(t))); }
|
||||
}
|
||||
|
||||
MatchMethod matchMethod() : {} {
|
||||
"matches" { return MatchMethod.EXACT_MATCH; } |
|
||||
"extends" { return MatchMethod.EXTENDS; } |
|
||||
"contains" { return MatchMethod.CONTAINS; }
|
||||
}
|
||||
|
||||
void matchBlock(List<Node> parent) : {
|
||||
Token t; MatchMethod mm; List<Node.Value> values;
|
||||
} {
|
||||
t = "class." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
|
||||
{ parent.add(new Node.MatchValueList(Node.MatchSource.BLOCK_CLASS, mm, getSource(t), values)); }
|
||||
|
|
||||
t = "name." mm = matchMethod() <parenStart> values = matchValueList() <parenEnd>
|
||||
{ parent.add(new Node.MatchValueList(Node.MatchSource.BLOCK_NAME, mm, getSource(t), values)); }
|
||||
}
|
||||
|
||||
List<Node.Value> matchValueList() : {
|
||||
List<Node.Value> values = new LinkedList<Node.Value>();
|
||||
} {
|
||||
matchValueToList(values) (<comma> matchValueToList(values))* { return values; }
|
||||
}
|
||||
|
||||
void matchValueToList(List<Node.Value> values) : {
|
||||
Node.Value v;
|
||||
} {
|
||||
v = matchValue() { values.add(v); }
|
||||
}
|
||||
|
||||
Node.Value matchValue() : {
|
||||
Token t;
|
||||
} {
|
||||
t = <stringLiteral> { return new Node.Value.Literal(t.image); }
|
||||
|
|
||||
"classOf" <parenStart> t = <stringLiteral> <parenEnd>
|
||||
{ return new Node.Value.ClassOf(t.image); }
|
||||
|
|
||||
"model.texture" <parenStart> t = <stringLiteral> <parenEnd>
|
||||
{ return new Node.Value.Texture(t.image); }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import mods.betterfoliage.config.BlockConfig
|
||||
import mods.betterfoliage.config.BlockConfigOld
|
||||
import mods.betterfoliage.config.MainConfig
|
||||
import mods.betterfoliage.util.tryDefault
|
||||
import mods.betterfoliage.config.clothGuiRoot
|
||||
@@ -72,7 +73,7 @@ object BetterFoliageMod {
|
||||
}
|
||||
|
||||
Minecraft.getInstance().resourcePackRepository.addPackFinder(BetterFoliage.generatedPack.finder)
|
||||
bus.register(BlockConfig)
|
||||
bus.register(BlockConfigOld)
|
||||
BetterFoliage.init()
|
||||
}
|
||||
}
|
||||
40
src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt
Normal file
40
src/main/kotlin/mods/betterfoliage/config/BlockConfig.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package mods.betterfoliage.config
|
||||
|
||||
import mods.betterfoliage.config.match.Node
|
||||
import mods.betterfoliage.config.match.parser.BlockConfigParser
|
||||
import mods.betterfoliage.config.match.parser.ParseException
|
||||
import mods.betterfoliage.config.match.parser.TokenMgrError
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.stripStart
|
||||
import net.minecraft.resources.IResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.Level.ERROR
|
||||
|
||||
class BlockConfig : HasLogger() {
|
||||
lateinit var rules: List<Node.MatchAll>
|
||||
|
||||
fun readConfig(manager: IResourceManager) {
|
||||
val configs = manager.listResources("config/betterfoliage") { it.endsWith(".rules") }
|
||||
rules = configs.flatMap { configLocation ->
|
||||
val resource = manager.getResource(configLocation)
|
||||
val parser = BlockConfigParser(resource.inputStream)
|
||||
.apply { configFile = configLocation.stripStart("config/betterfoliage/").toString() }
|
||||
try {
|
||||
mutableListOf<Node.MatchAll>().apply { parser.matchFile(this) }
|
||||
} catch (e: ParseException) {
|
||||
parseError(e, configLocation)
|
||||
} catch (e: TokenMgrError) {
|
||||
parseError(e, configLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parseError(e: Throwable, location: ResourceLocation): List<Node.MatchAll> {
|
||||
"Error parsing block config $location: ${e.message}".let {
|
||||
logger.log(ERROR, it)
|
||||
detailLogger.log(ERROR, it)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent
|
||||
import net.minecraftforge.fml.config.ModConfig
|
||||
|
||||
object BlockConfig {
|
||||
object BlockConfigOld {
|
||||
private val list = mutableListOf<Any>()
|
||||
|
||||
val leafBlocks = blocks("leaves_blocks_default.cfg")
|
||||
|
||||
117
src/main/kotlin/mods/betterfoliage/config/match/Match.kt
Normal file
117
src/main/kotlin/mods/betterfoliage/config/match/Match.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package mods.betterfoliage.config.match
|
||||
|
||||
typealias RuleLogConsumer = (ConfigSource, String) -> Unit
|
||||
|
||||
sealed class MatchValue(val description: String) {
|
||||
class Found<T>(description: String, val value: T) : MatchValue(description)
|
||||
class Missing(description: String) : MatchValue(description)
|
||||
class Invalid(description: String) : MatchValue(description)
|
||||
}
|
||||
|
||||
sealed class MatchResult {
|
||||
abstract val isSuccess: Boolean
|
||||
abstract val isInvariant: Boolean
|
||||
abstract val configSource: ConfigSource
|
||||
abstract fun log(logger: RuleLogConsumer)
|
||||
|
||||
class UniComparison(
|
||||
override val isSuccess: Boolean,
|
||||
override val isInvariant: Boolean,
|
||||
override val configSource: ConfigSource,
|
||||
val sourceValue: MatchValue,
|
||||
val targetValue: String,
|
||||
val matchMethod: MatchMethod
|
||||
) : MatchResult() {
|
||||
override fun log(logger: RuleLogConsumer) = logger(
|
||||
configSource,
|
||||
"${sourceValue.description} ${matchMethod.description(isSuccess)} \"$targetValue\""
|
||||
)
|
||||
}
|
||||
|
||||
class BiComparison(
|
||||
override val isSuccess: Boolean,
|
||||
override val isInvariant: Boolean,
|
||||
override val configSource: ConfigSource,
|
||||
val source: MatchValue,
|
||||
val target: MatchValue,
|
||||
val matchMethod: MatchMethod
|
||||
) : MatchResult() {
|
||||
override fun log(logger: RuleLogConsumer) = logger(
|
||||
configSource,
|
||||
"${source.description} ${matchMethod.description(isSuccess)} ${target.description}"
|
||||
)
|
||||
}
|
||||
|
||||
class InvalidValue(override val configSource: ConfigSource, val value: MatchValue, val description: String) : MatchResult() {
|
||||
override val isSuccess = false
|
||||
override val isInvariant = true
|
||||
|
||||
override fun log(logger: RuleLogConsumer) = logger(configSource, description)
|
||||
}
|
||||
|
||||
class Error(override val configSource: ConfigSource, val description: String) : MatchResult() {
|
||||
override val isSuccess = false
|
||||
override val isInvariant = true
|
||||
|
||||
override fun log(logger: RuleLogConsumer) = logger(configSource, description)
|
||||
}
|
||||
|
||||
class Action(override val configSource: ConfigSource, val description: String) : MatchResult() {
|
||||
override val isSuccess = true
|
||||
override val isInvariant = false
|
||||
|
||||
override fun log(logger: RuleLogConsumer) = logger(configSource, description)
|
||||
}
|
||||
|
||||
class Any(override val configSource: ConfigSource, val results: List<MatchResult>) : MatchResult() {
|
||||
override val isSuccess = results.any(MatchResult::isSuccess)
|
||||
override val isInvariant = results.all(MatchResult::isInvariant)
|
||||
|
||||
override fun log(logger: RuleLogConsumer) {
|
||||
val toLog = if (results.any { it.isSuccess }) results.filter { it.isSuccess } else results
|
||||
toLog.forEach { it.log(logger) }
|
||||
}
|
||||
}
|
||||
|
||||
class RootList(override val configSource: ConfigSource, val results: List<MatchResult>) : MatchResult() {
|
||||
override val isSuccess = results.all(MatchResult::isSuccess)
|
||||
override val isInvariant = results.all(MatchResult::isInvariant)
|
||||
|
||||
override fun log(logger: RuleLogConsumer) {
|
||||
results.forEach { it.log(logger) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Node.HasSource.error(description: String) = MatchResult.Error(configSource, description)
|
||||
fun Node.HasSource.notImplemented() = MatchResult.Error(configSource, "match type not implemented: ${this::class.java.name}")
|
||||
fun Node.HasSource.action(description: String) = MatchResult.Action(configSource, description)
|
||||
fun Node.HasSource.invalidValue(value: MatchValue) = MatchResult.InvalidValue(
|
||||
configSource, value, "invalid value: ${value.description}"
|
||||
)
|
||||
fun Node.HasSource.invalidValueType(comparisonLeft: String, value: Node.Value) = MatchResult.Error(
|
||||
configSource, "invalid type for $comparisonLeft: [${value::class.java.name}] \"${value.value}\""
|
||||
)
|
||||
fun <T> Node.MatchValueList.compare(sourceValue: MatchValue, targetValue: Node.Value, func: (MatchValue.Found<T>) -> Boolean): MatchResult {
|
||||
if (sourceValue is MatchValue.Missing || sourceValue is MatchValue.Invalid) return invalidValue(sourceValue)
|
||||
val isSuccess = func(sourceValue as MatchValue.Found<T>)
|
||||
return MatchResult.UniComparison(isSuccess, true, configSource, sourceValue, targetValue.value, matchMethod)
|
||||
}
|
||||
fun <T> Node.MatchValueList.compare(sourceValue: MatchValue, targetValue: MatchValue, func: (MatchValue.Found<T>, MatchValue.Found<T>) -> Boolean): MatchResult {
|
||||
if (sourceValue is MatchValue.Missing || sourceValue is MatchValue.Invalid) return invalidValue(sourceValue)
|
||||
if (targetValue is MatchValue.Missing || targetValue is MatchValue.Invalid) return invalidValue(targetValue)
|
||||
val isSuccess = func(sourceValue as MatchValue.Found<T>, targetValue as MatchValue.Found<T>)
|
||||
return MatchResult.BiComparison(isSuccess, true, configSource, sourceValue, targetValue, matchMethod)
|
||||
}
|
||||
|
||||
enum class MatchMethod {
|
||||
EXACT_MATCH, EXTENDS, CONTAINS;
|
||||
|
||||
fun description(isSuccess: Boolean) = when(this) {
|
||||
EXACT_MATCH -> if (isSuccess) "matches" else "does not match"
|
||||
EXTENDS -> if (isSuccess) "extends" else "does not extend"
|
||||
CONTAINS -> if (isSuccess) "contains" else "does not contain"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
150
src/main/kotlin/mods/betterfoliage/config/match/Matchers.kt
Normal file
150
src/main/kotlin/mods/betterfoliage/config/match/Matchers.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
package mods.betterfoliage.config.match
|
||||
|
||||
import mods.betterfoliage.resource.discovery.RuleProcessingContext
|
||||
import mods.betterfoliage.resource.discovery.getAncestry
|
||||
import mods.betterfoliage.util.tryDefault
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.registries.ForgeRegistries
|
||||
|
||||
object MatchRuleList {
|
||||
fun visitRoot(ctx: RuleProcessingContext, node: Node.MatchAll): MatchResult {
|
||||
val results = mutableListOf<MatchResult>()
|
||||
for (rule in node.list) {
|
||||
val result = when(rule) {
|
||||
is Node.MatchValueList -> visitMatchList(ctx, rule)
|
||||
is Node.MatchParam -> ParamMatchRules.visitMatch(ctx, rule)
|
||||
is Node.SetParam -> ParamMatchRules.visitSet(ctx, rule)
|
||||
else -> node.notImplemented()
|
||||
}
|
||||
results.add(result)
|
||||
if (!result.isSuccess) break
|
||||
}
|
||||
return MatchResult.RootList(node.configSource, results)
|
||||
}
|
||||
|
||||
fun visitMatchList(ctx: RuleProcessingContext, node: Node.MatchValueList) = node.values.map { value ->
|
||||
try {
|
||||
when (node.matchSource) {
|
||||
Node.MatchSource.BLOCK_CLASS -> BlockMatchRules.visitClass(ctx, node, value)
|
||||
Node.MatchSource.BLOCK_NAME -> BlockMatchRules.visitName(ctx, node, value)
|
||||
Node.MatchSource.MODEL_LOCATION -> ModelMatchRules.visit(ctx, node, value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
MatchResult.Error(node.configSource, e.message ?: "")
|
||||
}
|
||||
}.let { MatchResult.Any(node.configSource, it) }
|
||||
}
|
||||
|
||||
object BlockMatchRules {
|
||||
fun visitClass(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MatchResult {
|
||||
val source = ctx.discovery.blockState.block::class.java.let {
|
||||
MatchValue.Found("block class \"${it.name}\"", it)
|
||||
}
|
||||
val target = when(value) {
|
||||
is Node.Value.Literal -> tryDefault(null) { Class.forName(value.value) as Class<out Block> }
|
||||
?.let { MatchValue.Found("class \"${value.value}\"", it) }
|
||||
?: MatchValue.Missing("missing class \"${value.value}\"")
|
||||
|
||||
is Node.Value.ClassOf -> ForgeRegistries.BLOCKS.getValue(ResourceLocation(value.value))
|
||||
?.let { MatchValue.Found("class \"${it::class.java}\" of block \"${value.value}\"", it::class.java) }
|
||||
?: MatchValue.Missing("class of missing block \"${value.value}\"")
|
||||
|
||||
else -> MatchValue.Invalid("${value::class.java.name}(${value.value})")
|
||||
}
|
||||
return when(node.matchMethod) {
|
||||
MatchMethod.EXACT_MATCH -> node.compare(source, target, this::isExactClass)
|
||||
MatchMethod.EXTENDS -> node.compare(source, target, this::isExtendsClass)
|
||||
MatchMethod.CONTAINS -> node.error("invalid match type for block class: \"contains\"")
|
||||
}
|
||||
}
|
||||
|
||||
fun visitName(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MatchResult {
|
||||
val source = ctx.discovery.blockState.block.registryName?.let {
|
||||
MatchValue.Found("block name \"$it\"", it)
|
||||
} ?: MatchValue.Missing("missing block name")
|
||||
if (value !is Node.Value.Literal) return node.invalidValueType("block name", value)
|
||||
val (namespace, path) = if (value.value.contains(":")) ResourceLocation(value.value).let { it.namespace to it.path } else null to value.value
|
||||
return when(node.matchMethod) {
|
||||
MatchMethod.EXACT_MATCH -> node.compare<ResourceLocation>(source, value) { isExactName(it.value, namespace, path) }
|
||||
MatchMethod.CONTAINS -> node.compare<ResourceLocation>(source, value) { isContainsName(it.value, namespace, path) }
|
||||
MatchMethod.EXTENDS -> node.error("invalid match type for block name: \"extends\"")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isExactClass(source: MatchValue.Found<Class<out Block>>, target: MatchValue.Found<Class<out Block>>) =
|
||||
source.value == target.value
|
||||
private fun isExtendsClass(source: MatchValue.Found<Class<out Block>>, target: MatchValue.Found<Class<out Block>>) =
|
||||
target.value.isAssignableFrom(source.value)
|
||||
|
||||
fun isExactName(source: ResourceLocation, namespace: String?, path: String) =
|
||||
(namespace == null || namespace == source.namespace) && path == source.path
|
||||
fun isContainsName(source: ResourceLocation, namespace: String?, path: String) =
|
||||
(namespace == null || source.namespace.contains(namespace)) && source.path.contains(path)
|
||||
}
|
||||
|
||||
object ModelMatchRules {
|
||||
fun visit(ctx: RuleProcessingContext, node: Node.MatchValueList, value: Node.Value): MatchResult {
|
||||
val source = ctx.discovery.modelLocation.let { MatchValue.Found("model \"$it\"", it) }
|
||||
if (value !is Node.Value.Literal) return node.invalidValueType("model", value)
|
||||
val (namespace, path) = value.value.splitLocation()
|
||||
|
||||
val check = when (node.matchMethod) {
|
||||
MatchMethod.EXACT_MATCH, MatchMethod.CONTAINS -> listOf(ctx.discovery.modelLocation)
|
||||
MatchMethod.EXTENDS -> ctx.discovery.bakery.getAncestry(ctx.discovery.modelLocation)
|
||||
}
|
||||
|
||||
return when (node.matchMethod) {
|
||||
MatchMethod.EXACT_MATCH, MatchMethod.EXTENDS -> node.compare<ResourceLocation>(source, value) { isExactModel(check, namespace, path) }
|
||||
MatchMethod.CONTAINS -> node.compare<ResourceLocation>(source, value) { isContainsModel(check, namespace, path) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.splitLocation() = when(contains(":")) {
|
||||
true -> ResourceLocation(this).let { it.namespace to it.path }
|
||||
false -> null to this
|
||||
}
|
||||
|
||||
private fun isExactModel(models: List<ResourceLocation>, namespace: String?, path: String) = models.any { model ->
|
||||
(namespace == null || namespace == model.namespace) && path == model.path
|
||||
}
|
||||
|
||||
private fun isContainsModel(models: List<ResourceLocation>, namespace: String?, path: String) = models.any { model ->
|
||||
(namespace == null || model.namespace.contains(namespace)) && model.path.contains(path)
|
||||
}
|
||||
}
|
||||
|
||||
object ParamMatchRules {
|
||||
fun visitMatch(ctx: RuleProcessingContext, node: Node.MatchParam) = node.values.map { value ->
|
||||
if (value !is Node.Value.Literal) return@map node.invalidValueType("parameter", value)
|
||||
val currentParamValue = ctx.params[node.name] ?: return@map MatchResult.UniComparison(
|
||||
isSuccess = false, isInvariant = false,
|
||||
node.configSource,
|
||||
MatchValue.Missing("missing parameter \"${node.name}\""), value.value,
|
||||
MatchMethod.EXACT_MATCH
|
||||
)
|
||||
val isSuccess = currentParamValue == value.value
|
||||
MatchResult.UniComparison(
|
||||
isSuccess, false,
|
||||
node.configSource,
|
||||
MatchValue.Found("parameter \"${node.name}\"", currentParamValue), value.value,
|
||||
MatchMethod.EXACT_MATCH
|
||||
)
|
||||
}.let { MatchResult.Any(node.configSource, it) }
|
||||
|
||||
fun visitSet(ctx: RuleProcessingContext, node: Node.SetParam): MatchResult {
|
||||
val target = when(node.value) {
|
||||
is Node.Value.Literal -> node.value.value.let { MatchValue.Found("\"$it\"", it) }
|
||||
is Node.Value.Texture -> when(val model = ctx.discovery.getUnbaked()) {
|
||||
is BlockModel -> model.getMaterial(node.value.value).texture().toString().let {
|
||||
MatchValue.Found("texture \"${node.value.value}\" = \"$it\"", it)
|
||||
}
|
||||
else -> return node.error("cannot get texture from ${model::class.java.name}")
|
||||
}
|
||||
else -> return node.invalidValueType("parameter", node.value)
|
||||
}
|
||||
ctx.params[node.name] = target.value
|
||||
return node.action("parameter \"${node.name}\" set to ${target.description}")
|
||||
}
|
||||
}
|
||||
37
src/main/kotlin/mods/betterfoliage/config/match/ParseTree.kt
Normal file
37
src/main/kotlin/mods/betterfoliage/config/match/ParseTree.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package mods.betterfoliage.config.match
|
||||
|
||||
data class ConfigSource(
|
||||
val configFile: String,
|
||||
val line: Int,
|
||||
val column: Int
|
||||
) {
|
||||
override fun toString() = "$configFile @ R$line,C$column"
|
||||
}
|
||||
|
||||
sealed class Node {
|
||||
enum class MatchSource { BLOCK_CLASS, BLOCK_NAME, MODEL_LOCATION }
|
||||
interface HasSource { val configSource: ConfigSource }
|
||||
|
||||
class MatchValueList(
|
||||
val matchSource: MatchSource,
|
||||
val matchMethod: MatchMethod,
|
||||
override val configSource: ConfigSource,
|
||||
val values: List<Value>
|
||||
) : Node(), HasSource
|
||||
|
||||
class MatchParam(
|
||||
val name: String,
|
||||
val values: List<Value>,
|
||||
override val configSource: ConfigSource,
|
||||
) : Node(), HasSource
|
||||
|
||||
class SetParam(val name: String, val value: Value, override val configSource: ConfigSource) : Node(), HasSource
|
||||
|
||||
class MatchAll(override val configSource: ConfigSource, val list: List<Node>) : Node(), HasSource
|
||||
|
||||
abstract class Value(val value: String) : Node() {
|
||||
class Literal(value: String) : Value(value)
|
||||
class ClassOf(value: String) : Value(value)
|
||||
class Texture(value: String) : Value(value)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@ abstract class AbstractModelDiscovery : HasLogger(), ModelDiscovery {
|
||||
}
|
||||
|
||||
open fun processModel(ctx: ModelDiscoveryContext) {
|
||||
processContainerModel(ctx)
|
||||
}
|
||||
|
||||
fun processContainerModel(ctx: ModelDiscoveryContext) {
|
||||
val model = ctx.getUnbaked()
|
||||
|
||||
// built-in support for container models
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.config.match.MatchResult
|
||||
import mods.betterfoliage.config.match.MatchRuleList
|
||||
import mods.betterfoliage.config.match.Node
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import net.minecraft.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.VariantList
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
abstract class ParametrizedModelDiscovery : HasLogger() {
|
||||
abstract fun processModel(ctx: ModelDiscoveryContext, params: Map<String, String>)
|
||||
|
||||
fun Map<String, String>.texture(key: String): ResourceLocation? {
|
||||
val result = get(key)?.let { ResourceLocation(it) }
|
||||
if (result == null) detailLogger.log(Level.WARN, "Cannot find texture parameter \"$key\"")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class RuleProcessingContext(
|
||||
val discovery: ModelDiscoveryContext
|
||||
) {
|
||||
val params = mutableMapOf("type" to "none")
|
||||
}
|
||||
|
||||
class RuleBasedDiscovery : AbstractModelDiscovery() {
|
||||
val discoverers = mutableMapOf<String, ParametrizedModelDiscovery>()
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext) = when(ctx.getUnbaked()) {
|
||||
is VariantList -> processContainerModel(ctx)
|
||||
is BlockModel -> processBlockModel(ctx)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
fun processBlockModel(ctx: ModelDiscoveryContext) {
|
||||
val ruleCtx = RuleProcessingContext(ctx)
|
||||
val rulesToCheck = BetterFoliage.blockConfig.rules.toMutableList()
|
||||
val ruleResults = mutableListOf<MatchResult>()
|
||||
var previousSize = 0
|
||||
|
||||
// stop processing if nothing changes anymore
|
||||
while (rulesToCheck.size != previousSize) {
|
||||
previousSize = rulesToCheck.size
|
||||
val iterator = rulesToCheck.listIterator()
|
||||
while (iterator.hasNext()) iterator.next().let { rule ->
|
||||
// process single rule
|
||||
MatchRuleList.visitRoot(ruleCtx, rule).let { result ->
|
||||
ruleResults.add(result)
|
||||
// remove rule from active list if:
|
||||
// - rule succeeded (all directives returned success)
|
||||
// - rule is invariant (result will always be the same)
|
||||
if (result.isSuccess || result.isInvariant) iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log result of rule processing
|
||||
if (ruleResults.any { it.isSuccess }) {
|
||||
detailLogger.log(Level.INFO, "================================")
|
||||
detailLogger.log(Level.INFO, "block state: ${ctx.blockState}")
|
||||
detailLogger.log(Level.INFO, "block class: ${ctx.blockState.block::class.java.name}")
|
||||
detailLogger.log(Level.INFO, "model : ${ctx.modelLocation}")
|
||||
detailLogger.log(Level.INFO, "--------------------------------")
|
||||
ruleResults.forEach { result ->
|
||||
if (result !is MatchResult.RootList || result.results.shouldLog())
|
||||
result.log { source, message -> detailLogger.log(Level.INFO, "[$source] $message") }
|
||||
}
|
||||
}
|
||||
|
||||
discoverers[ruleCtx.params["type"]]?.processModel(ctx, ruleCtx.params)
|
||||
}
|
||||
|
||||
fun List<MatchResult>.shouldLog() = all { it.isSuccess } || fold(false) { seenInvariantSuccess, result ->
|
||||
seenInvariantSuccess || (result.isSuccess && result.isInvariant)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Leaves
|
||||
match block.class.extends(classOf("minecraft:oak_leaves")) setParam("type", "leaf") end
|
||||
|
||||
match isParam("type", "leaf")
|
||||
model.extends("minecraft:block/leaves", "minecraft:block/cube_all")
|
||||
setParam("texture-leaf", model.texture("all"))
|
||||
end
|
||||
|
||||
// Podzol
|
||||
match block.name.matches("minecraft:podzol") setParam("type", "grass") end
|
||||
|
||||
// Grass
|
||||
match block.class.extends(classOf("minecraft:grass_block")) setParam("type", "grass") end
|
||||
|
||||
match isParam("type", "grass")
|
||||
model.extends("minecraft:block/grass_block", "minecraft:block/cube_bottom_top")
|
||||
setParam("texture-grass", model.texture("top"))
|
||||
end
|
||||
|
||||
// Dirt
|
||||
match block.name.matches("minecraft:dirt") setParam("type", "dirt") end
|
||||
|
||||
// Wood Log
|
||||
match block.class.extends(classOf("minecraft:oak_log")) setParam("type", "round-log") end
|
||||
|
||||
match isParam("type", "round-log")
|
||||
model.extends("minecraft:block/cube_column", "minecraft:block/cube_column_horizontal")
|
||||
setParam("texture-side", model.texture("side"))
|
||||
setParam("texture-end", model.texture("end"))
|
||||
end
|
||||
|
||||
match isParam("type", "round-log")
|
||||
model.extends("minecraft:block/cube_all")
|
||||
setParam("texture-side", model.texture("all"))
|
||||
setParam("texture-end", model.texture("all"))
|
||||
end
|
||||
|
||||
// Sand & Dirt
|
||||
match block.name.matches("minecraft:sand", "minecraft:red_sand") setParam("type", "sand") end
|
||||
|
||||
// Cactus, Lilypad, Mycelium, Netherrack
|
||||
match block.name.matches("minecraft:cactus") setParam("type", "cactus") end
|
||||
match block.name.matches("minecraft:lilypad") setParam("type", "lilypad") end
|
||||
match block.name.matches("minecraft:mycelium") setParam("type", "mycelium") end
|
||||
match block.name.matches("minecraft:netherrack") setParam("type", "netherrack") end
|
||||
Reference in New Issue
Block a user