feat: Add skull parser

This commit is contained in:
Linnea Gräf
2025-05-04 15:40:08 +02:00
parent 3743ae88d9
commit cea5cd65cc
5 changed files with 93 additions and 4 deletions

View File

@@ -10,6 +10,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import moe.nea.firmament.util.parseDashlessUUID
import moe.nea.firmament.util.parsePotentiallyDashlessUUID
object DashlessUUIDSerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor =
@@ -17,10 +18,7 @@ object DashlessUUIDSerializer : KSerializer<UUID> {
override fun deserialize(decoder: Decoder): UUID {
val str = decoder.decodeString()
if ("-" in str) {
return UUID.fromString(str)
}
return parseDashlessUUID(str)
return parsePotentiallyDashlessUUID(str)
}
override fun serialize(encoder: Encoder, value: UUID) {

View File

@@ -3,6 +3,12 @@ package moe.nea.firmament.util
import java.math.BigInteger
import java.util.UUID
fun parsePotentiallyDashlessUUID(unknownFormattedUUID: String): UUID {
if ("-" in unknownFormattedUUID)
return UUID.fromString(unknownFormattedUUID)
return parseDashlessUUID(unknownFormattedUUID)
}
fun parseDashlessUUID(dashlessUuid: String): UUID {
val most = BigInteger(dashlessUuid.substring(0, 16), 16)
val least = BigInteger(dashlessUuid.substring(16, 32), 16)

View File

@@ -24,6 +24,7 @@ import moe.nea.firmament.features.texturepack.predicates.NotPredicate
import moe.nea.firmament.features.texturepack.predicates.OrPredicate
import moe.nea.firmament.features.texturepack.predicates.PetPredicate
import moe.nea.firmament.features.texturepack.predicates.PullingPredicate
import moe.nea.firmament.features.texturepack.predicates.SkullPredicate
import moe.nea.firmament.util.json.KJsonOps
object CustomModelOverrideParser {
@@ -65,6 +66,7 @@ object CustomModelOverrideParser {
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser)
registerPredicateParser("component", GenericComponentPredicate.Parser)
registerPredicateParser("skull", SkullPredicate.Parser)
}
private val neverPredicate = listOf(

View File

@@ -0,0 +1,63 @@
package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.JsonElement
import com.mojang.authlib.minecraft.MinecraftProfileTexture
import java.util.UUID
import kotlin.jvm.optionals.getOrNull
import net.minecraft.component.DataComponentTypes
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
import moe.nea.firmament.features.texturepack.StringMatcher
import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.parsePotentiallyDashlessUUID
class SkullPredicate(
val profileId: UUID?,
val textureProfileId: UUID?,
val skinUrl: StringMatcher?,
val textureValue: StringMatcher?,
) : FirmamentModelPredicate {
object Parser : FirmamentModelPredicateParser {
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
val obj = jsonElement.asJsonObject
val profileId = obj.getAsJsonPrimitive("profileId")
?.asString?.let(::parsePotentiallyDashlessUUID)
val textureProfileId = obj.getAsJsonPrimitive("textureProfileId")
?.asString?.let(::parsePotentiallyDashlessUUID)
val textureValue = obj.get("textureValue")?.let(StringMatcher::parse)
val skinUrl = obj.get("skinUrl")?.let(StringMatcher::parse)
return SkullPredicate(profileId, textureProfileId, skinUrl, textureValue)
}
}
override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
if (!stack.isOf(Items.PLAYER_HEAD)) return false
val profile = stack.get(DataComponentTypes.PROFILE) ?: return false
val textureProperty = profile.properties["textures"].firstOrNull()
val textureMode = lazy(LazyThreadSafetyMode.NONE) {
decodeProfileTextureProperty(textureProperty ?: return@lazy null)
}
when {
profileId != null
&& profileId != profile.id.getOrNull() ->
return false
textureValue != null
&& !textureValue.matches(textureProperty?.value ?: "") ->
return false
skinUrl != null
&& !skinUrl.matches(textureMode.value?.textures?.get(MinecraftProfileTexture.Type.SKIN)?.url ?: "") ->
return false
textureProfileId != null
&& textureProfileId != textureMode.value?.profileId ->
return false
else -> return true
}
}
}

View File

@@ -139,6 +139,26 @@ Filter by item type:
"firmament:item": "minecraft:clock"
```
#### Skulls
You can match skulls using the skull textures and other properties using the skull predicate. If there are no properties specified this is equivalent to checking if the item is a `minecraft:player_head`.
```json
"firmament:skull": {
"profileId": "cca2d452-c6d3-39cb-b695-5ec92b2d6729",
"textureProfileId": "1d5233d388624bafb00e3150a7aa3a89",
"skinUrl": "http://textures.minecraft.net/texture/7bf01c198f6e16965e230235cd22a5a9f4a40e40941234478948ff9a56e51775",
"textureValue": "ewogICJ0aW1lc3RhbXAiIDogMTYxODUyMTY2MzY1NCwKICAicHJvZmlsZUlkIiA6ICIxZDUyMzNkMzg4NjI0YmFmYjAwZTMxNTBhN2FhM2E4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICIwMDAwMDAwMDAwMDAwMDBKIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdiZjAxYzE5OGY2ZTE2OTY1ZTIzMDIzNWNkMjJhNWE5ZjRhNDBlNDA5NDEyMzQ0Nzg5NDhmZjlhNTZlNTE3NzUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ"
}
```
| Name | Type | Description |
|--------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `profileId` | UUID | Match the uuid of the profile component directly. |
| `textureProfileId` | UUID | Match the uuid of the skin owner in the encoded texture value. This is more expensive, but can deviate from the profile id of the profile owner. |
| `skinUrl` | [string](#string-matcher) | Match the texture url of the skin. This starts with `http://`, not with `https:/` in most cases. |
| `textureValue` | [string](#string-matcher) | Match the texture value. This is the encoded base64 string of the texture url along with metadata. It is faster to query than the `skinUrl`, but it can out of changed without causing any semantic changes, and is less readable than the skinUrl. |
#### Extra attributes
Filter by extra attribute NBT data: