package moe.nea.firmament.features.texturepack.predicates import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import moe.nea.firmament.features.texturepack.FirmamentModelPredicate import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser import moe.nea.firmament.features.texturepack.StringMatcher import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtByte import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtDouble import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtFloat import net.minecraft.nbt.NbtInt import net.minecraft.nbt.NbtList import net.minecraft.nbt.NbtLong import net.minecraft.nbt.NbtShort import net.minecraft.nbt.NbtString import moe.nea.firmament.util.extraAttributes fun interface NbtMatcher { fun matches(nbt: NbtElement): Boolean object Parser { fun parse(jsonElement: JsonElement): NbtMatcher? { if (jsonElement is JsonPrimitive) { if (jsonElement.isString) { val string = jsonElement.asString return MatchStringExact(string) } if (jsonElement.isNumber) { return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number } } if (jsonElement is JsonObject) { var encounteredParser: NbtMatcher? = null for (entry in ExclusiveParserType.entries) { val data = jsonElement[entry.key] ?: continue if (encounteredParser != null) { // TODO: warn return null } encounteredParser = entry.parse(data) ?: return null } return encounteredParser } return null } enum class ExclusiveParserType(val key: String) { STRING("string") { override fun parse(element: JsonElement): NbtMatcher? { return MatchString(StringMatcher.parse(element)) } }, INT("int") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asInt }, { (it as? NbtInt)?.intValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, FLOAT("float") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asFloat }, { (it as? NbtFloat)?.floatValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, DOUBLE("double") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asDouble }, { (it as? NbtDouble)?.doubleValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, LONG("long") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asLong }, { (it as? NbtLong)?.longValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, SHORT("short") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asShort }, { (it as? NbtShort)?.shortValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, BYTE("byte") { override fun parse(element: JsonElement): NbtMatcher? { return parseGenericNumber(element, { it.asByte }, { (it as? NbtByte)?.byteValue() }, { a, b -> if (a == b) Comparison.EQUAL else if (a < b) Comparison.LESS_THAN else Comparison.GREATER }) } }, ; abstract fun parse(element: JsonElement): NbtMatcher? } enum class Comparison { LESS_THAN, EQUAL, GREATER } inline fun parseGenericNumber( jsonElement: JsonElement, primitiveExtractor: (JsonPrimitive) -> T?, crossinline nbtExtractor: (NbtElement) -> T?, crossinline compare: (T, T) -> Comparison ): NbtMatcher? { if (jsonElement is JsonPrimitive) { val expected = primitiveExtractor(jsonElement) ?: return null return NbtMatcher { val actual = nbtExtractor(it) ?: return@NbtMatcher false compare(actual, expected) == Comparison.EQUAL } } if (jsonElement is JsonObject) { val minElement = jsonElement.getAsJsonPrimitive("min") val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false val maxElement = jsonElement.getAsJsonPrimitive("max") val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true if (min == null && max == null) return null return NbtMatcher { val actual = nbtExtractor(it) ?: return@NbtMatcher false if (max != null) { val comp = compare(actual, max) if (comp == Comparison.GREATER) return@NbtMatcher false if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false } if (min != null) { val comp = compare(actual, min) if (comp == Comparison.LESS_THAN) return@NbtMatcher false if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false } return@NbtMatcher true } } return null } } class MatchNumberExact(val number: Long) : NbtMatcher { override fun matches(nbt: NbtElement): Boolean { return when (nbt) { is NbtByte -> nbt.byteValue().toLong() == number is NbtInt -> nbt.intValue().toLong() == number is NbtShort -> nbt.shortValue().toLong() == number is NbtLong -> nbt.longValue().toLong() == number else -> false } } } class MatchStringExact(val string: String) : NbtMatcher { override fun matches(nbt: NbtElement): Boolean { return nbt is NbtString && nbt.asString() == string } override fun toString(): String { return "MatchNbtStringExactly($string)" } } class MatchString(val string: StringMatcher) : NbtMatcher { override fun matches(nbt: NbtElement): Boolean { return nbt is NbtString && string.matches(nbt.asString()) } override fun toString(): String { return "MatchNbtString($string)" } } } data class ExtraAttributesPredicate( val path: NbtPrism, val matcher: NbtMatcher, ) : FirmamentModelPredicate { object Parser : FirmamentModelPredicateParser { override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { if (jsonElement !is JsonObject) return null val path = jsonElement.get("path") ?: return null val pathSegments = if (path is JsonArray) { path.map { (it as JsonPrimitive).asString } } else if (path is JsonPrimitive && path.isString) { path.asString.split(".") } else return null val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) ?: return null return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) } } override fun test(stack: ItemStack): Boolean { return path.access(stack.extraAttributes) .any { matcher.matches(it) } } } class NbtPrism(val path: List) { override fun toString(): String { return "Prism($path)" } fun access(root: NbtElement): Collection { var rootSet = mutableListOf(root) var switch = mutableListOf() for (pathSegment in path) { if (pathSegment == ".") continue for (element in rootSet) { if (element is NbtList) { if (pathSegment == "*") switch.addAll(element) val index = pathSegment.toIntOrNull() ?: continue if (index !in element.indices) continue switch.add(element[index]) } if (element is NbtCompound) { if (pathSegment == "*") element.keys.mapTo(switch) { element.get(it)!! } switch.add(element.get(pathSegment) ?: continue) } } val temp = switch switch = rootSet rootSet = temp switch.clear() } return rootSet } }