aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/texturepack/predicates
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-11-01 23:00:53 +0100
committerLinnea Gräf <nea@nea.moe>2024-11-01 23:00:53 +0100
commit8b410fbdf2cffb3ceaa51bbea150f5165848bc37 (patch)
treee4b4ec4ba24a7edbefa45b7deb2503b74214ee02 /src/main/kotlin/features/texturepack/predicates
parent653454e290a1bb6142e2bb40947239c2e450820b (diff)
downloadFirmament-8b410fbdf2cffb3ceaa51bbea150f5165848bc37.tar.gz
Firmament-8b410fbdf2cffb3ceaa51bbea150f5165848bc37.tar.bz2
Firmament-8b410fbdf2cffb3ceaa51bbea150f5165848bc37.zip
Add tint override to texture packs
Diffstat (limited to 'src/main/kotlin/features/texturepack/predicates')
-rw-r--r--src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt19
-rw-r--r--src/main/kotlin/features/texturepack/predicates/AndPredicate.kt28
-rw-r--r--src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt22
-rw-r--r--src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt271
-rw-r--r--src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt34
-rw-r--r--src/main/kotlin/features/texturepack/predicates/LorePredicate.kt22
-rw-r--r--src/main/kotlin/features/texturepack/predicates/NotPredicate.kt21
-rw-r--r--src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt124
-rw-r--r--src/main/kotlin/features/texturepack/predicates/OrPredicate.kt29
-rw-r--r--src/main/kotlin/features/texturepack/predicates/PetPredicate.kt66
10 files changed, 636 insertions, 0 deletions
diff --git a/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt b/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt
new file mode 100644
index 0000000..7e0ddb1
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt
@@ -0,0 +1,19 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import net.minecraft.item.ItemStack
+
+object AlwaysPredicate : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ return true
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ return AlwaysPredicate
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt b/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt
new file mode 100644
index 0000000..99abaaa
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt
@@ -0,0 +1,28 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import net.minecraft.item.ItemStack
+
+class AndPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ return children.all { it.test(stack) }
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ val children =
+ (jsonElement as JsonArray)
+ .flatMap {
+ CustomModelOverrideParser.parsePredicates(it as JsonObject)
+ }
+ .toTypedArray()
+ return AndPredicate(children)
+ }
+
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt b/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt
new file mode 100644
index 0000000..04c7a2b
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt
@@ -0,0 +1,22 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+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 moe.nea.firmament.util.mc.displayNameAccordingToNbt
+
+data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ val display = stack.displayNameAccordingToNbt
+ return stringMatcher.matches(display)
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ return DisplayNamePredicate(StringMatcher.parse(jsonElement))
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt b/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt
new file mode 100644
index 0000000..3c8023d
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt
@@ -0,0 +1,271 @@
+
+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 <T : Any> 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<String>) {
+ override fun toString(): String {
+ return "Prism($path)"
+ }
+ fun access(root: NbtElement): Collection<NbtElement> {
+ var rootSet = mutableListOf(root)
+ var switch = mutableListOf<NbtElement>()
+ 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
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt b/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt
new file mode 100644
index 0000000..3cb80c7
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt
@@ -0,0 +1,34 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonPrimitive
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.item.Item
+import net.minecraft.item.ItemStack
+import net.minecraft.registry.RegistryKey
+import net.minecraft.registry.RegistryKeys
+import net.minecraft.util.Identifier
+import moe.nea.firmament.util.MC
+
+class ItemPredicate(
+ val item: Item
+) : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ return stack.item == item
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): ItemPredicate? {
+ if (jsonElement is JsonPrimitive && jsonElement.isString) {
+ val itemKey = RegistryKey.of(RegistryKeys.ITEM,
+ Identifier.tryParse(jsonElement.asString)
+ ?: return null)
+ return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null)
+ }
+ return null
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt b/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt
new file mode 100644
index 0000000..f0b4737
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt
@@ -0,0 +1,22 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+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 moe.nea.firmament.util.mc.loreAccordingToNbt
+
+class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate {
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ return LorePredicate(StringMatcher.parse(jsonElement))
+ }
+ }
+
+ override fun test(stack: ItemStack): Boolean {
+ val lore = stack.loreAccordingToNbt
+ return lore.any { matcher.matches(it) }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt b/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt
new file mode 100644
index 0000000..4986ad9
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt
@@ -0,0 +1,21 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import net.minecraft.item.ItemStack
+
+class NotPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ return children.none { it.test(stack) }
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray())
+ }
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt b/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt
new file mode 100644
index 0000000..b0d5178
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt
@@ -0,0 +1,124 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonPrimitive
+import moe.nea.firmament.util.useMatch
+
+abstract class NumberMatcher {
+ abstract fun test(number: Number): Boolean
+
+
+ companion object {
+ fun parse(jsonElement: JsonElement): NumberMatcher? {
+ if (jsonElement is JsonPrimitive) {
+ if (jsonElement.isString) {
+ val string = jsonElement.asString
+ return parseRange(string) ?: parseOperator(string)
+ }
+ if (jsonElement.isNumber) {
+ val number = jsonElement.asNumber
+ val hasDecimals = (number.toString().contains("."))
+ return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
+ }
+ }
+ return null
+ }
+
+ private val intervalSpec =
+ "(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
+ .toPattern()
+
+ fun parseRange(string: String): RangeMatcher? {
+ intervalSpec.useMatch<Nothing>(string) {
+ // Open in the set-theory sense, meaning does not include its end.
+ val beginningOpen = group("beginningOpen") == "("
+ val endingOpen = group("endingOpen") == ")"
+ val beginning = group("beginning")?.toDouble()
+ val ending = group("ending")?.toDouble()
+ return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
+ }
+ return null
+ }
+
+ enum class Operator(val operator: String) {
+ LESS("<") {
+ override fun matches(comparisonResult: Int): Boolean {
+ return comparisonResult < 0
+ }
+ },
+ LESS_EQUALS("<=") {
+ override fun matches(comparisonResult: Int): Boolean {
+ return comparisonResult <= 0
+ }
+ },
+ GREATER(">") {
+ override fun matches(comparisonResult: Int): Boolean {
+ return comparisonResult > 0
+ }
+ },
+ GREATER_EQUALS(">=") {
+ override fun matches(comparisonResult: Int): Boolean {
+ return comparisonResult >= 0
+ }
+ },
+ ;
+
+ abstract fun matches(comparisonResult: Int): Boolean
+ }
+
+ private val operatorPattern =
+ "(?<operator>${Operator.entries.joinToString("|") { it.operator }})(?<value>[0-9.]+)".toPattern()
+
+ fun parseOperator(string: String): OperatorMatcher? {
+ return operatorPattern.useMatch(string) {
+ val operatorName = group("operator")
+ val operator = Operator.entries.find { it.operator == operatorName }!!
+ val value = group("value").toDouble()
+ OperatorMatcher(operator, value)
+ }
+ }
+
+ data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
+ override fun test(number: Number): Boolean {
+ return operator.matches(number.toDouble().compareTo(value))
+ }
+ }
+
+
+ data class MatchNumberExact(val number: Number) : NumberMatcher() {
+ override fun test(number: Number): Boolean {
+ return when (this.number) {
+ is Double -> number.toDouble() == this.number.toDouble()
+ else -> number.toLong() == this.number.toLong()
+ }
+ }
+ }
+
+ data class RangeMatcher(
+ val beginning: Double?,
+ val beginningInclusive: Boolean,
+ val ending: Double?,
+ val endingInclusive: Boolean,
+ ) : NumberMatcher() {
+ override fun test(number: Number): Boolean {
+ val value = number.toDouble()
+ if (beginning != null) {
+ if (beginningInclusive) {
+ if (value < beginning) return false
+ } else {
+ if (value <= beginning) return false
+ }
+ }
+ if (ending != null) {
+ if (endingInclusive) {
+ if (value > ending) return false
+ } else {
+ if (value >= ending) return false
+ }
+ }
+ return true
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt b/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt
new file mode 100644
index 0000000..e3093cd
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt
@@ -0,0 +1,29 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import net.minecraft.item.ItemStack
+
+class OrPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
+ override fun test(stack: ItemStack): Boolean {
+ return children.any { it.test(stack) }
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
+ val children =
+ (jsonElement as JsonArray)
+ .flatMap {
+ CustomModelOverrideParser.parsePredicates(it as JsonObject)
+ }
+ .toTypedArray()
+ return OrPredicate(children)
+ }
+
+ }
+}
diff --git a/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt b/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt
new file mode 100644
index 0000000..b30b7c9
--- /dev/null
+++ b/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt
@@ -0,0 +1,66 @@
+
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import moe.nea.firmament.features.texturepack.RarityMatcher
+import moe.nea.firmament.features.texturepack.StringMatcher
+import net.minecraft.item.ItemStack
+import moe.nea.firmament.repo.ExpLadders
+import moe.nea.firmament.util.petData
+
+data class PetPredicate(
+ val petId: StringMatcher?,
+ val tier: RarityMatcher?,
+ val exp: NumberMatcher?,
+ val candyUsed: NumberMatcher?,
+ val level: NumberMatcher?,
+) : FirmamentModelPredicate {
+
+ override fun test(stack: ItemStack): Boolean {
+ val petData = stack.petData ?: return false
+ if (petId != null) {
+ if (!petId.matches(petData.type)) return false
+ }
+ if (exp != null) {
+ if (!exp.test(petData.exp)) return false
+ }
+ if (candyUsed != null) {
+ if (!candyUsed.test(petData.candyUsed)) return false
+ }
+ if (tier != null) {
+ if (!tier.match(petData.tier)) return false
+ }
+ val levelData by lazy(LazyThreadSafetyMode.NONE) {
+ ExpLadders.getExpLadder(petData.type, petData.tier)
+ .getPetLevel(petData.exp)
+ }
+ if (level != null) {
+ if (!level.test(levelData.currentLevel)) return false
+ }
+ return true
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ if (jsonElement.isJsonPrimitive) {
+ return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null)
+ }
+ if (jsonElement !is JsonObject) return null
+ val idMatcher = jsonElement["id"]?.let(StringMatcher::parse)
+ val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse)
+ val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse)
+ val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse)
+ val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse)
+ return PetPredicate(
+ idMatcher,
+ tierMatcher,
+ expMatcher,
+ candyMatcher,
+ levelMatcher,
+ )
+ }
+ }
+}