feat: Add generic component matcher

This commit is contained in:
Linnea Gräf
2025-05-02 18:48:30 +02:00
parent b98272c364
commit afa128e8c6
4 changed files with 322 additions and 223 deletions

View File

@@ -17,6 +17,7 @@ import moe.nea.firmament.features.texturepack.predicates.AndPredicate
import moe.nea.firmament.features.texturepack.predicates.CastPredicate import moe.nea.firmament.features.texturepack.predicates.CastPredicate
import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
import moe.nea.firmament.features.texturepack.predicates.GenericComponentPredicate
import moe.nea.firmament.features.texturepack.predicates.ItemPredicate import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
import moe.nea.firmament.features.texturepack.predicates.LorePredicate import moe.nea.firmament.features.texturepack.predicates.LorePredicate
import moe.nea.firmament.features.texturepack.predicates.NotPredicate import moe.nea.firmament.features.texturepack.predicates.NotPredicate
@@ -63,6 +64,7 @@ object CustomModelOverrideParser {
registerPredicateParser("item", ItemPredicate.Parser) registerPredicateParser("item", ItemPredicate.Parser)
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser) registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser) registerPredicateParser("pet", PetPredicate.Parser)
registerPredicateParser("component", GenericComponentPredicate.Parser)
} }
private val neverPredicate = listOf( private val neverPredicate = listOf(

View File

@@ -58,7 +58,8 @@ fun interface NbtMatcher {
}, },
INT("int") { INT("int") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asInt }, { it.asInt },
{ (it as? NbtInt)?.intValue() }, { (it as? NbtInt)?.intValue() },
{ a, b -> { a, b ->
@@ -70,7 +71,8 @@ fun interface NbtMatcher {
}, },
FLOAT("float") { FLOAT("float") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asFloat }, { it.asFloat },
{ (it as? NbtFloat)?.floatValue() }, { (it as? NbtFloat)?.floatValue() },
{ a, b -> { a, b ->
@@ -82,7 +84,8 @@ fun interface NbtMatcher {
}, },
DOUBLE("double") { DOUBLE("double") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asDouble }, { it.asDouble },
{ (it as? NbtDouble)?.doubleValue() }, { (it as? NbtDouble)?.doubleValue() },
{ a, b -> { a, b ->
@@ -94,7 +97,8 @@ fun interface NbtMatcher {
}, },
LONG("long") { LONG("long") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asLong }, { it.asLong },
{ (it as? NbtLong)?.longValue() }, { (it as? NbtLong)?.longValue() },
{ a, b -> { a, b ->
@@ -106,7 +110,8 @@ fun interface NbtMatcher {
}, },
SHORT("short") { SHORT("short") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asShort }, { it.asShort },
{ (it as? NbtShort)?.shortValue() }, { (it as? NbtShort)?.shortValue() },
{ a, b -> { a, b ->
@@ -118,7 +123,8 @@ fun interface NbtMatcher {
}, },
BYTE("byte") { BYTE("byte") {
override fun parse(element: JsonElement): NbtMatcher? { override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element, return parseGenericNumber(
element,
{ it.asByte }, { it.asByte },
{ (it as? NbtByte)?.byteValue() }, { (it as? NbtByte)?.byteValue() },
{ a, b -> { a, b ->
@@ -193,7 +199,7 @@ fun interface NbtMatcher {
class MatchStringExact(val string: String) : NbtMatcher { class MatchStringExact(val string: String) : NbtMatcher {
override fun matches(nbt: NbtElement): Boolean { override fun matches(nbt: NbtElement): Boolean {
return nbt is NbtString && nbt.asString() == string return nbt.asString() == string
} }
override fun toString(): String { override fun toString(): String {
@@ -203,7 +209,7 @@ fun interface NbtMatcher {
class MatchString(val string: StringMatcher) : NbtMatcher { class MatchString(val string: StringMatcher) : NbtMatcher {
override fun matches(nbt: NbtElement): Boolean { override fun matches(nbt: NbtElement): Boolean {
return nbt is NbtString && string.matches(nbt.asString()) return nbt.asString().let(string::matches)
} }
override fun toString(): String { override fun toString(): String {
@@ -221,14 +227,10 @@ data class ExtraAttributesPredicate(
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
if (jsonElement !is JsonObject) return null if (jsonElement !is JsonObject) return null
val path = jsonElement.get("path") ?: return null val path = jsonElement.get("path") ?: return null
val pathSegments = if (path is JsonArray) { val prism = NbtPrism.fromElement(path) ?: return null
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) val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
?: return null ?: return null
return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) return ExtraAttributesPredicate(prism, matcher)
} }
} }
@@ -239,9 +241,21 @@ data class ExtraAttributesPredicate(
} }
class NbtPrism(val path: List<String>) { class NbtPrism(val path: List<String>) {
companion object {
fun fromElement(path: JsonElement): NbtPrism? {
if (path is JsonArray) {
return NbtPrism(path.map { (it as JsonPrimitive).asString })
} else if (path is JsonPrimitive && path.isString) {
return NbtPrism(path.asString.split("."))
}
return null
}
}
override fun toString(): String { override fun toString(): String {
return "Prism($path)" return "Prism($path)"
} }
fun access(root: NbtElement): Collection<NbtElement> { fun access(root: NbtElement): Collection<NbtElement> {
var rootSet = mutableListOf(root) var rootSet = mutableListOf(root)
var switch = mutableListOf<NbtElement>() var switch = mutableListOf<NbtElement>()

View File

@@ -0,0 +1,57 @@
package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.mojang.serialization.Codec
import kotlin.jvm.optionals.getOrNull
import net.minecraft.component.ComponentType
import net.minecraft.component.type.NbtComponent
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtOps
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.util.Identifier
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
import moe.nea.firmament.util.MC
data class GenericComponentPredicate<T>(
val componentType: ComponentType<T>,
val codec: Codec<T>,
val path: NbtPrism,
val matcher: NbtMatcher,
) : FirmamentModelPredicate {
constructor(componentType: ComponentType<T>, path: NbtPrism, matcher: NbtMatcher)
: this(componentType, componentType.codecOrThrow, path, matcher)
override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
val component = stack.get(componentType) ?: return false
// TODO: cache this
val nbt =
if (component is NbtComponent) component.nbt
else codec.encodeStart(NbtOps.INSTANCE, component)
.resultOrPartial().getOrNull() ?: return false
return path.access(nbt).any { matcher.matches(it) }
}
object Parser : FirmamentModelPredicateParser {
override fun parse(jsonElement: JsonElement): GenericComponentPredicate<*>? {
if (jsonElement !is JsonObject) return null
val path = jsonElement.get("path") ?: return null
val prism = NbtPrism.fromElement(path) ?: return null
val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
?: return null
val component = MC.currentOrDefaultRegistries
.getOrThrow(RegistryKeys.DATA_COMPONENT_TYPE)
.getOrThrow(
RegistryKey.of(
RegistryKeys.DATA_COMPONENT_TYPE,
Identifier.of(jsonElement.get("component").asString)
)
).value()
return GenericComponentPredicate(component, prism, matcher)
}
}
}

View File

@@ -167,6 +167,32 @@ Sub object match:
} }
``` ```
#### Components
You can match generic components similarly to [extra attributes](#extra-attributes). If you want to match an extra
attribute match directly using that, for better performance.
You can specify a `path` and match similar to extra attributes, but in addition you can also specify a `component`. This
variable is the identifier of a component type that will then be encoded to nbt and matched according to the `match`
using a [nbt matcher](#nbt-matcher).
```json5
"firmament:component": {
"path": "rgb",
"component": "minecraft:dyed_color",
"int": 255
}
// Alternatively
"firmament:component": {
"path": "rgb",
"component": "minecraft:dyed_color",
"match": {
"int": 255
}
}
```
#### Pet Data #### Pet Data
Filter by pet information. While you can already filter by the skyblock id for pet type and tier, this allows you to Filter by pet information. While you can already filter by the skyblock id for pet type and tier, this allows you to