feat(dev): add more elaborate automatic texture exporter (stealthisfit)

This commit is contained in:
Linnea Gräf
2025-05-05 01:20:36 +02:00
parent ad084aa0fe
commit 0a5a108b15
5 changed files with 187 additions and 81 deletions

View File

@@ -1,47 +1,105 @@
package moe.nea.firmament.features.debug
import net.minecraft.command.argument.RegistryKeyArgumentType
import net.minecraft.component.ComponentType
import net.minecraft.component.DataComponentTypes
import net.minecraft.entity.Entity
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtOps
import net.minecraft.registry.RegistryKeys
import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.get
import moe.nea.firmament.commands.thenArgument
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.EntityUpdateEvent
import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.mc.NbtPrism
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.tr
object AnimatedClothingScanner {
var observedEntity: Entity? = null
data class SubjectOfFashionTheft<T>(
val observedEntity: Entity,
val prism: NbtPrism,
val component: ComponentType<T>,
) {
fun observe(itemStack: ItemStack): Collection<NbtElement> {
val x = itemStack.get(component) ?: return listOf()
val nbt = component.codecOrThrow.encodeStart(NbtOps.INSTANCE, x).orThrow
return prism.access(nbt)
}
}
var subject: SubjectOfFashionTheft<*>? = null
@OptIn(ExperimentalStdlibApi::class)
@Subscribe
fun onUpdate(event: EntityUpdateEvent) {
if (event.entity != observedEntity) return
val s = subject ?: return
if (event.entity != s.observedEntity) return
if (event is EntityUpdateEvent.EquipmentUpdate) {
val lines = mutableListOf<String>()
event.newEquipment.forEach {
val id = it.second.skyBlockId?.neuItem
val colour = it.second.get(DataComponentTypes.DYED_COLOR)
?.rgb?.toHexString(HexFormat.UpperCase)
?.let { " #$it" } ?: ""
MC.sendChat(tr("firmament.fitstealer.update",
"[FIT CHECK][${MC.currentTick}] ${it.first.asString()} => ${id}${colour}"))
val formatted = (s.observe(it.second)).joinToString()
lines.add(formatted)
MC.sendChat(
tr(
"firmament.fitstealer.update",
"[FIT CHECK][${MC.currentTick}] ${it.first.asString()} => $formatted"
)
)
}
if (lines.isNotEmpty()) {
val contents = ClipboardUtils.getTextContents()
if (contents.startsWith(EXPORT_WATERMARK))
ClipboardUtils.setTextContent(
contents + "\n" + lines.joinToString("\n")
)
}
}
}
val EXPORT_WATERMARK = "[CLOTHES EXPORT]"
@Subscribe
fun onSubCommand(event: CommandEvent.SubCommand) {
event.subcommand("dev") {
thenLiteral("stealthisfit") {
thenArgument(
"component",
RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)
) { component ->
thenArgument("path", NbtPrism.Argument) { path ->
thenExecute {
observedEntity =
if (observedEntity == null) MC.instance.targetedEntity else null
subject =
if (subject == null) run {
val entity = MC.instance.targetedEntity ?: return@run null
val clipboard = ClipboardUtils.getTextContents()
MC.instance.entit
if (!clipboard.startsWith(EXPORT_WATERMARK)) {
ClipboardUtils.setTextContent(EXPORT_WATERMARK)
} else {
ClipboardUtils.setTextContent("$clipboard\n\n[NEW SCANNER]")
}
SubjectOfFashionTheft(
entity,
get(path),
MC.unsafeGetRegistryEntry(get(component))!!,
)
} else null
MC.sendChat(
observedEntity?.let {
tr("firmament.fitstealer.targeted", "Observing the equipment of ${it.name}.")
subject?.let {
tr(
"firmament.fitstealer.targeted",
"Observing the equipment of ${it.observedEntity.name}."
)
} ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."),
)
}
@@ -49,3 +107,5 @@ object AnimatedClothingScanner {
}
}
}
}
}

View File

@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import io.github.moulberry.repo.data.Coordinate
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.hud.InGameHud
import net.minecraft.client.gui.screen.Screen
@@ -16,10 +17,14 @@ import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.RegistryWrapper
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.resource.ReloadableResourceManagerImpl
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import moe.nea.firmament.events.TickEvent
@@ -120,6 +125,19 @@ object MC {
return field
}
private set
fun <T> unsafeGetRegistryEntry(registry: RegistryKey<out Registry<T>>, identifier: Identifier) =
unsafeGetRegistryEntry(RegistryKey.of(registry, identifier))
fun <T> unsafeGetRegistryEntry(registryKey: RegistryKey<T>): T? {
return currentOrDefaultRegistries
.getOrThrow(registryKey.registryRef)
.getOptional(registryKey)
.getOrNull()
?.value()
}
}

View File

@@ -0,0 +1,91 @@
package moe.nea.firmament.util.mc
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import com.mojang.brigadier.StringReader
import com.mojang.brigadier.arguments.ArgumentType
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import com.mojang.serialization.JsonOps
import java.util.concurrent.CompletableFuture
import kotlin.collections.indices
import kotlin.collections.map
import kotlin.jvm.optionals.getOrNull
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtString
import moe.nea.firmament.util.Base64Util
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
}
}
object Argument : ArgumentType<NbtPrism> {
override fun parse(reader: StringReader): NbtPrism? {
return fromElement(JsonPrimitive(StringArgumentType.string().parse(reader)))
}
override fun getExamples(): Collection<String?>? {
return listOf("some.nbt.path", "some.other.*", "some.path.*json.in.a.json.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
if (pathSegment != "*" && pathSegment.startsWith("*")) {
if (pathSegment == "*json") {
for (element in rootSet) {
val eString = element.asString().getOrNull() ?: 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().getOrNull() ?: continue
switch.add(NbtString.of(Base64Util.decodeString(string)))
}
}
}
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
}
}

View File

@@ -1,31 +1,24 @@
package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull
import com.mojang.serialization.JsonOps
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.NbtOps
import net.minecraft.nbt.NbtShort
import net.minecraft.nbt.NbtString
import moe.nea.firmament.util.Base64Util
import moe.nea.firmament.util.extraAttributes
import moe.nea.firmament.util.mc.NbtPrism
fun interface NbtMatcher {
fun matches(nbt: NbtElement): Boolean
@@ -246,60 +239,3 @@ 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>()
for (pathSegment in path) {
if (pathSegment == ".") continue
if (pathSegment != "*" && pathSegment.startsWith("*")) {
if (pathSegment == "*json") {
for (element in rootSet) {
val eString = element.asString().getOrNull() ?: 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().getOrNull() ?: continue
switch.add(NbtString.of(Base64Util.decodeString(string)))
}
}
}
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
}
}

View File

@@ -15,6 +15,7 @@ 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
import moe.nea.firmament.util.mc.NbtPrism
data class GenericComponentPredicate<T>(
val componentType: ComponentType<T>,