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

View File

@@ -58,7 +58,8 @@ fun interface NbtMatcher {
},
INT("int") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asInt },
{ (it as? NbtInt)?.intValue() },
{ a, b ->
@@ -70,7 +71,8 @@ fun interface NbtMatcher {
},
FLOAT("float") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asFloat },
{ (it as? NbtFloat)?.floatValue() },
{ a, b ->
@@ -82,7 +84,8 @@ fun interface NbtMatcher {
},
DOUBLE("double") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asDouble },
{ (it as? NbtDouble)?.doubleValue() },
{ a, b ->
@@ -94,7 +97,8 @@ fun interface NbtMatcher {
},
LONG("long") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asLong },
{ (it as? NbtLong)?.longValue() },
{ a, b ->
@@ -106,7 +110,8 @@ fun interface NbtMatcher {
},
SHORT("short") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asShort },
{ (it as? NbtShort)?.shortValue() },
{ a, b ->
@@ -118,7 +123,8 @@ fun interface NbtMatcher {
},
BYTE("byte") {
override fun parse(element: JsonElement): NbtMatcher? {
return parseGenericNumber(element,
return parseGenericNumber(
element,
{ it.asByte },
{ (it as? NbtByte)?.byteValue() },
{ a, b ->
@@ -193,7 +199,7 @@ fun interface NbtMatcher {
class MatchStringExact(val string: String) : NbtMatcher {
override fun matches(nbt: NbtElement): Boolean {
return nbt is NbtString && nbt.asString() == string
return nbt.asString() == string
}
override fun toString(): String {
@@ -203,7 +209,7 @@ fun interface NbtMatcher {
class MatchString(val string: StringMatcher) : NbtMatcher {
override fun matches(nbt: NbtElement): Boolean {
return nbt is NbtString && string.matches(nbt.asString())
return nbt.asString().let(string::matches)
}
override fun toString(): String {
@@ -221,14 +227,10 @@ data class ExtraAttributesPredicate(
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 prism = NbtPrism.fromElement(path) ?: return null
val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
?: return null
return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher)
return ExtraAttributesPredicate(prism, matcher)
}
}
@@ -239,9 +241,21 @@ data class ExtraAttributesPredicate(
}
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 {
return "Prism($path)"
}
fun access(root: NbtElement): Collection<NbtElement> {
var rootSet = mutableListOf(root)
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
Filter by pet information. While you can already filter by the skyblock id for pet type and tier, this allows you to