aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-10-19 01:36:38 +0200
committerLinnea Gräf <nea@nea.moe>2025-10-19 01:36:38 +0200
commit8346c05bbfc6d227f3f02c7ee5ec5ceb8f7e4348 (patch)
treeabd765aff43edd19db6ba147d7cf8e1d3145e873 /src/main
parent28c3ba952d69c8a654301c9211d536493513fdd5 (diff)
downloadFirmament-8346c05bbfc6d227f3f02c7ee5ec5ceb8f7e4348.tar.gz
Firmament-8346c05bbfc6d227f3f02c7ee5ec5ceb8f7e4348.tar.bz2
Firmament-8346c05bbfc6d227f3f02c7ee5ec5ceb8f7e4348.zip
feat: add skin preview exporterHEADmc-1.21.7
Diffstat (limited to 'src/main')
-rw-r--r--src/main/kotlin/features/debug/PowerUserTools.kt1
-rw-r--r--src/main/kotlin/features/debug/SkinPreviews.kt101
-rw-r--r--src/main/kotlin/features/inventory/buttons/InventoryButton.kt3
-rw-r--r--src/main/kotlin/util/TimeMark.kt5
4 files changed, 109 insertions, 1 deletions
diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt
index b682813..a549f7e 100644
--- a/src/main/kotlin/features/debug/PowerUserTools.kt
+++ b/src/main/kotlin/features/debug/PowerUserTools.kt
@@ -65,6 +65,7 @@ object PowerUserTools {
val highlightNonOverlayItems by toggle("highlight-non-overlay") { false }
val dontHighlightSemicolonItems by toggle("dont-highlight-semicolon-items") { false }
val showSlotNumbers by keyBindingWithDefaultUnbound("slot-numbers")
+ val autoCopyAnimatedSkins by toggle("copy-animated-skins") { false }
}
var lastCopiedStack: Pair<ItemStack, Text>? = null
diff --git a/src/main/kotlin/features/debug/SkinPreviews.kt b/src/main/kotlin/features/debug/SkinPreviews.kt
new file mode 100644
index 0000000..5c710a4
--- /dev/null
+++ b/src/main/kotlin/features/debug/SkinPreviews.kt
@@ -0,0 +1,101 @@
+package moe.nea.firmament.features.debug
+
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.put
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.component.DataComponentTypes
+import net.minecraft.component.type.ProfileComponent
+import net.minecraft.entity.EquipmentSlot
+import net.minecraft.entity.LivingEntity
+import net.minecraft.util.math.Vec3d
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.EntityUpdateEvent
+import moe.nea.firmament.events.IsSlotProtectedEvent
+import moe.nea.firmament.util.ClipboardUtils
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.TimeMark
+import moe.nea.firmament.util.extraAttributes
+import moe.nea.firmament.util.json.toJsonArray
+import moe.nea.firmament.util.math.GChainReconciliation.shortenCycle
+import moe.nea.firmament.util.mc.displayNameAccordingToNbt
+import moe.nea.firmament.util.mc.loreAccordingToNbt
+import moe.nea.firmament.util.rawSkyBlockId
+import moe.nea.firmament.util.toTicks
+import moe.nea.firmament.util.tr
+
+
+object SkinPreviews {
+
+ // TODO: add pet support
+ @Subscribe
+ fun onEntityUpdate(event: EntityUpdateEvent) {
+ if (!isRecording) return
+ if (event.entity.pos != pos)
+ return
+ val entity = event.entity as? LivingEntity ?: return
+ val stack = entity.getEquippedStack(EquipmentSlot.HEAD) ?: return
+ val profile = stack.get(DataComponentTypes.PROFILE) ?: return
+ if (!profile.isCompleted) {
+ lastDiscard = TimeMark.now()
+ animation.clear()
+ MC.sendChat(
+ tr(
+ "firmament.dev.skinpreviews.discarding",
+ "Encountered unloaded skin, discarding all previews skin frames."
+ )
+ )
+ return
+ }
+ if (profile == animation.lastOrNull()) return
+ animation.add(profile)
+ val shortened = animation.shortenCycle()
+ if (shortened.size <= (animation.size / 2).coerceAtLeast(1) && lastDiscard.passedTime() > 2.seconds) {
+ val tickEstimation = (lastDiscard.passedTime() / animation.size).toTicks()
+ val skinName = if (skinColor != null) "${skinId}_${skinColor?.uppercase()}" else skinId!!
+ val json =
+ buildJsonObject {
+ put("ticks", tickEstimation)
+ put(
+ "textures",
+ shortened.map {
+ it.gameProfile().id.toString() + ":" + it.properties()["textures"].first().value()
+ }.toJsonArray()
+ )
+ }
+ MC.sendChat(
+ tr(
+ "firmament.dev.skinpreviews.done",
+ "Observed a total of ${animation.size} elements, which could be shortened to a cycle of ${shortened.size}. Copying JSON array. Estimated ticks per frame: $tickEstimation."
+ )
+ )
+ isRecording = false
+ ClipboardUtils.setTextContent(JsonPrimitive(skinName).toString() + ":" + json.toString())
+ }
+ }
+
+ var animation = mutableListOf<ProfileComponent>()
+ var pos = Vec3d(-1.0, 72.0, -101.25)
+ var isRecording = false
+ var skinColor: String? = null
+ var skinId: String? = null
+ var lastDiscard = TimeMark.farPast()
+
+ @Subscribe
+ fun onActivate(event: IsSlotProtectedEvent) {
+ if (!PowerUserTools.TConfig.autoCopyAnimatedSkins) return
+ val lastLine = event.itemStack.loreAccordingToNbt.lastOrNull()?.string
+ if (lastLine != "Right-click to preview!" && lastLine != "Click to preview!") return
+ lastDiscard = TimeMark.now()
+ val stackName = event.itemStack.displayNameAccordingToNbt.string
+ if (stackName == "FIRE SALE!") {
+ skinColor = null
+ skinId = event.itemStack.rawSkyBlockId
+ } else {
+ skinColor = stackName
+ }
+ animation.clear()
+ isRecording = true
+ MC.sendChat(tr("firmament.dev.skinpreviews.start", "Starting to observe items"))
+ }
+}
diff --git a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
index 0a1121d..e31f4a0 100644
--- a/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
+++ b/src/main/kotlin/features/inventory/buttons/InventoryButton.kt
@@ -10,6 +10,7 @@ import net.minecraft.client.gui.DrawContext
import net.minecraft.command.CommandRegistryAccess
import net.minecraft.command.argument.ItemStackArgumentType
import net.minecraft.item.ItemStack
+import net.minecraft.item.Items
import net.minecraft.resource.featuretoggle.FeatureFlags
import net.minecraft.util.Identifier
import moe.nea.firmament.repo.ExpensiveItemCacheApi
@@ -77,7 +78,7 @@ data class InventoryButton(
}
}
}
- if (itemStack.isBroken)
+ if (itemStack.item == Items.PAINTING)
ErrorUtil.logError("created broken itemstack for inventory button $icon: $itemStack")
return itemStack
}
diff --git a/src/main/kotlin/util/TimeMark.kt b/src/main/kotlin/util/TimeMark.kt
index 4a076ac..112a727 100644
--- a/src/main/kotlin/util/TimeMark.kt
+++ b/src/main/kotlin/util/TimeMark.kt
@@ -2,6 +2,7 @@ package moe.nea.firmament.util
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
fun passedTime() =
@@ -50,3 +51,7 @@ class TimeMark private constructor(private val timeMark: Long) : Comparable<Time
return this.timeMark.compareTo(other.timeMark)
}
}
+
+fun Duration.toTicks(): Long {
+ return toLong(DurationUnit.MILLISECONDS) / 50
+}