feat: Add *base64 and *json nbt path matchers

This commit is contained in:
Linnea Gräf
2025-05-04 14:18:24 +02:00
parent afa128e8c6
commit 3743ae88d9
3 changed files with 80 additions and 3 deletions

View File

@@ -1,7 +1,14 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import java.util.Base64
object Base64Util { object Base64Util {
fun decodeString(str: String): String {
return Base64.getDecoder().decode(str.padToValidBase64())
.decodeToString()
}
fun String.padToValidBase64(): String { fun String.padToValidBase64(): String {
val align = this.length % 4 val align = this.length % 4
if (align == 0) return this if (align == 0) return this

View File

@@ -1,10 +1,12 @@
package moe.nea.firmament.features.texturepack.predicates package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.mojang.serialization.JsonOps
import kotlin.jvm.optionals.getOrDefault
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
import moe.nea.firmament.features.texturepack.StringMatcher import moe.nea.firmament.features.texturepack.StringMatcher
@@ -17,8 +19,10 @@ import net.minecraft.nbt.NbtFloat
import net.minecraft.nbt.NbtInt import net.minecraft.nbt.NbtInt
import net.minecraft.nbt.NbtList import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtLong import net.minecraft.nbt.NbtLong
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtShort import net.minecraft.nbt.NbtShort
import net.minecraft.nbt.NbtString import net.minecraft.nbt.NbtString
import moe.nea.firmament.util.Base64Util
import moe.nea.firmament.util.extraAttributes import moe.nea.firmament.util.extraAttributes
fun interface NbtMatcher { fun interface NbtMatcher {
@@ -261,6 +265,20 @@ class NbtPrism(val path: List<String>) {
var switch = mutableListOf<NbtElement>() var switch = mutableListOf<NbtElement>()
for (pathSegment in path) { for (pathSegment in path) {
if (pathSegment == ".") continue if (pathSegment == ".") continue
if (pathSegment != "*" && pathSegment.startsWith("*")) {
if (pathSegment == "*json") {
for (element in rootSet) {
val eString = element.asString() ?: continue
val element = Gson().fromJson(eString, JsonElement::class.java)
switch.add(JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE, element))
}
} else if (pathSegment == "*base64") {
for (element in rootSet) {
val string = element.asString() ?: continue
switch.add(NbtString.of(Base64Util.decodeString(string)))
}
}
}
for (element in rootSet) { for (element in rootSet) {
if (element is NbtList) { if (element is NbtList) {
if (pathSegment == "*") if (pathSegment == "*")

View File

@@ -143,7 +143,7 @@ Filter by item type:
Filter by extra attribute NBT data: Filter by extra attribute NBT data:
Specify a `path` to look at, separating sub elements with a `.`. You can use a `*` to check any child. Specify a `path` (using an [nbt prism](#nbt-prism)) to look at, separating sub elements with a `.`. You can use a `*` to check any child.
Then either specify a `match` sub-object or directly inline that object in the format of an [nbt matcher](#nbt-matcher). Then either specify a `match` sub-object or directly inline that object in the format of an [nbt matcher](#nbt-matcher).
@@ -172,7 +172,7 @@ Sub object match:
You can match generic components similarly to [extra attributes](#extra-attributes). If you want to match an extra 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. 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 You can specify a `path` (using an [nbt prism](#nbt-prism)) 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` 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). using a [nbt matcher](#nbt-matcher).
@@ -336,6 +336,58 @@ compare your number:
This example would match if the level is less than fifty. The available operators are `<`, `>`, `<=` and `>=`. The This example would match if the level is less than fifty. The available operators are `<`, `>`, `<=` and `>=`. The
operator needs to be specified on the left. The versions of the operator with `=` also allow the number to be equal. operator needs to be specified on the left. The versions of the operator with `=` also allow the number to be equal.
### Nbt Prism
An nbt prism (or path) is used to specify where in a complex nbt construct to look for a value. A basic prism just looks
like a dot-separated path (`parent.child.grandchild`), but more complex paths can be constructed.
First the specified path is split into dot separated chunks: `"a.b.c"` -> `["a", "b", "c"]`. You can also directly
specify the list if you would like. Any entry in that list not starting with a `*` ist treated as an attribute name or
an index:
```json
{
"propA": {
"propB": {
"propC": 100,
"propD": 1000
}
},
"someOtherProp": "hello",
"someThirdProp": "{\"innerProp\": true}",
"someFourthProp": "aGlkZGVuIHZhbHVl"
}
```
In this example json (which is supposed to represent a corresponding nbt object), you can use a path like
`propA.propB.propC` to directly extract the value `100`.
If you want to extract all of the innermost values of `propB`
(for example if `propB` was an array instead), you could use `propA.propB.*`. You can use the `*` at any level:
`*.*.*` for example extracts all properties that are exactly at the third level. In that case you would try to match any
of the values of `[100, 1000]` to your match object.
Sometimes values are encoded in a non-nbt format inside a string. For those you can use other star based directives like
`*base64` or `*json` to decode those entries.
`*base64` turns a base64 encoded string into the base64 decoded counterpart. `*.json` decodes a string into the json
object represented by that string. Note that json to nbt conversion isn't always straightforwards and the types can
end up being mangled (for example what could have been a byte ends up an int).
| Path | Result |
|---------------------------------|---------------------------------|
| `propA.propB` | `{"propC": 100, "propD": 1000}` |
| `propA.propB.propC` | `100` |
| `propA.*.propC` | `100` |
| `propA.propB.*` | `100`, `1000` |
| `someOtherProp` | `"hello"` |
| `someThirdProp` | "{\"innerProp\": true}" |
| `someThirdProp.*json` | {"innerProp": true} |
| `someThirdProp.*json.innerProp` | true |
| `someFourthProp` | `"aGlkZGVuIHZhbHVl"` |
| `someFourthProp.*base64` | `"hidden value"` |
### Nbt Matcher ### Nbt Matcher
This matches a single nbt element. This matches a single nbt element.