From 67cc7c22ac5f6873eb4549bc69db9f014c09c07f Mon Sep 17 00:00:00 2001
From: Linnea Gräf <nea@nea.moe>
Date: Wed, 10 Jul 2024 19:58:51 +0200
Subject: Add pet upgrade cost recipes

---
 .../features/inventory/CraftingOverlay.kt          |   3 +-
 .../moe/nea/firmament/rei/FirmamentReiPlugin.kt    |  16 +-
 .../moe/nea/firmament/rei/NEUItemEntryRenderer.kt  |  12 +-
 .../nea/firmament/rei/NEUItemEntrySerializer.kt    |   3 +-
 .../moe/nea/firmament/rei/SBItemEntryDefinition.kt |  50 +++--
 .../rei/SkyblockCraftingRecipeDynamicGenerator.kt  |  26 +--
 .../moe/nea/firmament/rei/recipes/SBKatRecipe.kt   | 229 +++++++++++++++++++++
 .../moe/nea/firmament/util/item/SkullItemData.kt   |   8 +-
 8 files changed, 305 insertions(+), 42 deletions(-)
 create mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt

(limited to 'src/main/kotlin')

diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
index 866d1cc..1108926 100644
--- a/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
+++ b/src/main/kotlin/moe/nea/firmament/features/inventory/CraftingOverlay.kt
@@ -1,5 +1,6 @@
 /*
  * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
  *
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
@@ -46,7 +47,7 @@ object CraftingOverlay : FirmamentFeature {
         val expectedItem = recipe.neuRecipe.inputs[recipeIndex]
         val actualStack = slot.stack ?: ItemStack.EMPTY!!
         val actualEntry = SBItemEntryDefinition.getEntry(actualStack).value
-        if ((actualEntry.skyblockId.neuItem != expectedItem.itemId || actualEntry.stackSize < expectedItem.amount) && expectedItem.amount.toInt() != 0) {
+        if ((actualEntry.skyblockId.neuItem != expectedItem.itemId || actualEntry.getStackSize() < expectedItem.amount) && expectedItem.amount.toInt() != 0) {
             event.context.fill(
                 event.slot.x,
                 event.slot.y,
diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt
index 4f897bd..86ad98b 100644
--- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt
+++ b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt
@@ -32,6 +32,7 @@ import moe.nea.firmament.features.inventory.CraftingOverlay
 import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
 import moe.nea.firmament.rei.recipes.SBCraftingRecipe
 import moe.nea.firmament.rei.recipes.SBForgeRecipe
+import moe.nea.firmament.rei.recipes.SBKatRecipe
 import moe.nea.firmament.rei.recipes.SBMobDropRecipe
 import moe.nea.firmament.repo.RepoManager
 import moe.nea.firmament.util.SkyblockId
@@ -70,6 +71,7 @@ class FirmamentReiPlugin : REIClientPlugin {
         registry.add(SBCraftingRecipe.Category)
         registry.add(SBForgeRecipe.Category)
         registry.add(SBMobDropRecipe.Category)
+        registry.add(SBKatRecipe.Category)
     }
 
     override fun registerExclusionZones(zones: ExclusionZones) {
@@ -80,14 +82,16 @@ class FirmamentReiPlugin : REIClientPlugin {
     override fun registerDisplays(registry: DisplayRegistry) {
         registry.registerDisplayGenerator(
             SBCraftingRecipe.Category.catIdentifier,
-            SkyblockCraftingRecipeDynamicGenerator
-        )
+            SkyblockCraftingRecipeDynamicGenerator)
         registry.registerDisplayGenerator(
             SBForgeRecipe.Category.categoryIdentifier,
-            SkyblockForgeRecipeDynamicGenerator
-        )
-        registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier,
-                                          SkyblockMobDropRecipeDynamicGenerator)
+            SkyblockForgeRecipeDynamicGenerator)
+        registry.registerDisplayGenerator(
+            SBMobDropRecipe.Category.categoryIdentifier,
+            SkyblockMobDropRecipeDynamicGenerator)
+        registry.registerDisplayGenerator(
+            SBKatRecipe.Category.categoryIdentifier,
+            SkyblockKatRecipeDynamicGenerator)
     }
 
     override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) {
diff --git a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt
index 6b5e3fe..ba99b30 100644
--- a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt
+++ b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntryRenderer.kt
@@ -47,13 +47,13 @@ object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<S
     val minecraft = MinecraftClient.getInstance()
 
     override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
-        return Tooltip.create(
-            entry.asItemEntry().value.getTooltip(
-                Item.TooltipContext.DEFAULT,
-                null,
-                TooltipType.BASIC
-            )
+        val stack = entry.value.asImmutableItemStack()
+        val lore = stack.getTooltip(
+            Item.TooltipContext.DEFAULT,
+            null,
+            TooltipType.BASIC
         )
+        return Tooltip.create(lore)
     }
 
     override fun getExtraData(entry: EntryStack<SBItemStack>): BakedModel {
diff --git a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt
index 1da123e..6c47cb2 100644
--- a/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt
+++ b/src/main/kotlin/moe/nea/firmament/rei/NEUItemEntrySerializer.kt
@@ -1,5 +1,6 @@
 /*
  * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
  *
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
@@ -27,7 +28,7 @@ object NEUItemEntrySerializer : EntrySerializer<SBItemStack> {
     override fun save(entry: EntryStack<SBItemStack>, value: SBItemStack): NbtCompound {
         return NbtCompound().apply {
             putString(SKYBLOCK_ID_ENTRY, value.skyblockId.neuItem)
-            putInt(SKYBLOCK_ITEM_COUNT, value.stackSize)
+            putInt(SKYBLOCK_ITEM_COUNT, value.getStackSize())
         }
     }
 }
diff --git a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt b/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt
index 2a20ff1..3897d01 100644
--- a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt
+++ b/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt
@@ -46,6 +46,9 @@ data class PetData(
         fun fromHypixel(petInfo: HypixelPetInfo) = PetData(
             petInfo.tier, petInfo.type, petInfo.exp,
         )
+        fun forLevel(petId: String, rarity: Rarity, level: Int) = PetData(
+            rarity, petId, ExpLadders.getExpLadder(petId, rarity).getPetExpForLevel(level).toDouble()
+        )
     }
 
     val levelData by lazy { ExpLadders.getExpLadder(petId, rarity).getPetLevel(exp) }
@@ -54,10 +57,22 @@ data class PetData(
 data class SBItemStack(
     val skyblockId: SkyblockId,
     val neuItem: NEUItem?,
-    val stackSize: Int,
-    val petData: PetData?,
+    private var stackSize: Int,
+    private var petData: PetData?,
     val extraLore: List<Text> = emptyList(),
 ) {
+
+    fun getStackSize() = stackSize
+    fun setStackSize(newSize: Int) {
+        this.stackSize = stackSize
+        this.itemStack_ = null
+    }
+    fun getPetData() = petData
+    fun setPetData(petData: PetData?) {
+        this.petData = petData
+        this.itemStack_ = null
+    }
+
     constructor(skyblockId: SkyblockId, petData: PetData) : this(
         skyblockId,
         RepoManager.getNEUItem(skyblockId),
@@ -87,7 +102,7 @@ data class SBItemStack(
     }
 
     private fun injectReplacementDataForPets(replacementData: MutableMap<String, String>) {
-        if (petData == null) return
+        val petData = this.petData ?: return
         val petInfo = RepoManager.neuRepo.constants.petNumbers[petData.petId]?.get(petData.rarity) ?: return
         if (petData.isStub) {
             val mapLow = mutableMapOf<String, String>()
@@ -105,14 +120,23 @@ data class SBItemStack(
         }
     }
 
-    private val itemStack: ItemStack by lazy(LazyThreadSafetyMode.NONE) {
-        if (skyblockId == SkyblockId.COINS)
-            return@lazy ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
-        val replacementData = mutableMapOf<String, String>()
-        injectReplacementDataForPets(replacementData)
-        return@lazy neuItem.asItemStack(idHint = skyblockId, replacementData).copyWithCount(stackSize)
-            .also { it.appendLore(extraLore) }
-    }
+
+    private var itemStack_: ItemStack? = null
+
+    private val itemStack: ItemStack
+        get() {
+            val itemStack = itemStack_ ?: run {
+                if (skyblockId == SkyblockId.COINS)
+                    return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
+                val replacementData = mutableMapOf<String, String>()
+                injectReplacementDataForPets(replacementData)
+                return@run neuItem.asItemStack(idHint = skyblockId, replacementData).copyWithCount(stackSize)
+                    .also { it.appendLore(extraLore) }
+            }
+            if (itemStack_ == null)
+                itemStack_ = itemStack
+            return itemStack
+        }
 
     fun asImmutableItemStack(): ItemStack {
         return itemStack
@@ -125,7 +149,7 @@ data class SBItemStack(
 
 object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
     override fun equals(o1: SBItemStack, o2: SBItemStack, context: ComparisonContext): Boolean {
-        return o1.skyblockId == o2.skyblockId && o1.stackSize == o2.stackSize
+        return o1.skyblockId == o2.skyblockId && o1.getStackSize() == o2.getStackSize()
     }
 
     override fun cheatsAs(entry: EntryStack<SBItemStack>?, value: SBItemStack): ItemStack {
@@ -167,7 +191,7 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
     }
 
     override fun isEmpty(entry: EntryStack<SBItemStack>?, value: SBItemStack): Boolean {
-        return value.stackSize == 0
+        return value.getStackSize() == 0
     }
 
     override fun getIdentifier(entry: EntryStack<SBItemStack>?, value: SBItemStack): Identifier {
diff --git a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt
index ac5a1fc..ead2119 100644
--- a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt
+++ b/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt
@@ -9,32 +9,34 @@ package moe.nea.firmament.rei
 
 import io.github.moulberry.repo.data.NEUCraftingRecipe
 import io.github.moulberry.repo.data.NEUForgeRecipe
+import io.github.moulberry.repo.data.NEUKatUpgradeRecipe
 import io.github.moulberry.repo.data.NEUMobDropRecipe
 import io.github.moulberry.repo.data.NEURecipe
-import java.util.*
+import java.util.Optional
 import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator
 import me.shedaniel.rei.api.client.view.ViewSearchBuilder
 import me.shedaniel.rei.api.common.display.Display
 import me.shedaniel.rei.api.common.entry.EntryStack
 import moe.nea.firmament.rei.recipes.SBCraftingRecipe
 import moe.nea.firmament.rei.recipes.SBForgeRecipe
+import moe.nea.firmament.rei.recipes.SBKatRecipe
 import moe.nea.firmament.rei.recipes.SBMobDropRecipe
 import moe.nea.firmament.repo.RepoManager
 
 
-val SkyblockCraftingRecipeDynamicGenerator = neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> {
-    SBCraftingRecipe(it)
-}
+val SkyblockCraftingRecipeDynamicGenerator =
+    neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> { SBCraftingRecipe(it) }
 
-val SkyblockForgeRecipeDynamicGenerator = neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> {
-    SBForgeRecipe(it)
-}
+val SkyblockForgeRecipeDynamicGenerator =
+    neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> { SBForgeRecipe(it) }
 
-val SkyblockMobDropRecipeDynamicGenerator = neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> {
-    SBMobDropRecipe(it)
-}
+val SkyblockMobDropRecipeDynamicGenerator =
+    neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> { SBMobDropRecipe(it) }
 
-inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(noinline mapper: (T) -> D) =
+val SkyblockKatRecipeDynamicGenerator =
+    neuDisplayGenerator<SBKatRecipe, NEUKatUpgradeRecipe> { SBKatRecipe(it) }
+
+inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(crossinline mapper: (T) -> D) =
     object : DynamicDisplayGenerator<D> {
         override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> {
             if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
@@ -47,7 +49,7 @@ inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(noinline map
         override fun generate(builder: ViewSearchBuilder): Optional<List<D>> {
             if (SBCraftingRecipe.Category.catIdentifier !in builder.categories) return Optional.empty()
             return Optional.of(
-                RepoManager.getAllRecipes().filterIsInstance<T>().map(mapper)
+                RepoManager.getAllRecipes().filterIsInstance<T>().map { mapper(it) }
                     .toList()
             )
         }
diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt
new file mode 100644
index 0000000..fc0deaf
--- /dev/null
+++ b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBKatRecipe.kt
@@ -0,0 +1,229 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package moe.nea.firmament.rei.recipes
+
+import io.github.moulberry.repo.data.NEUKatUpgradeRecipe
+import io.github.notenoughupdates.moulconfig.common.IMinecraft
+import io.github.notenoughupdates.moulconfig.gui.GuiComponent
+import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext
+import io.github.notenoughupdates.moulconfig.gui.MouseEvent
+import io.github.notenoughupdates.moulconfig.gui.component.SliderComponent
+import io.github.notenoughupdates.moulconfig.observer.GetSetter
+import io.github.notenoughupdates.moulconfig.observer.Property
+import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.gui.Renderer
+import me.shedaniel.rei.api.client.gui.widgets.Widget
+import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
+import me.shedaniel.rei.api.client.gui.widgets.Widgets
+import me.shedaniel.rei.api.client.registry.display.DisplayCategory
+import me.shedaniel.rei.api.common.category.CategoryIdentifier
+import me.shedaniel.rei.api.common.util.EntryStacks
+import kotlin.time.Duration.Companion.seconds
+import net.minecraft.block.Blocks
+import net.minecraft.client.gui.DrawContext
+import net.minecraft.client.gui.Element
+import net.minecraft.item.Items
+import net.minecraft.text.Text
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.rei.PetData
+import moe.nea.firmament.rei.SBItemEntryDefinition
+import moe.nea.firmament.rei.SBItemStack
+import moe.nea.firmament.util.FirmFormatters
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.SkyblockId
+
+class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() {
+    override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
+
+    object Category : DisplayCategory<SBKatRecipe> {
+        override fun getCategoryIdentifier(): CategoryIdentifier<SBKatRecipe> =
+            CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe")
+
+        override fun getTitle(): Text = Text.literal("Kat Pet Upgrade")
+        override fun getDisplayHeight(): Int {
+            return 100
+        }
+
+        override fun getIcon(): Renderer = EntryStacks.of(Items.BONE)
+        override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List<Widget> {
+            return buildList {
+                val arrowWidth = 24
+                val recipe = display.neuRecipe
+                val levelValue = Property.upgrade(GetSetter.floating(0F))
+                val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100)
+                val outputStack = SBItemStack(SkyblockId(recipe.output.itemId))
+                val inputStack = SBItemStack(SkyblockId(recipe.input.itemId))
+                val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25)
+                val inputLevelLabel = Widgets.createLabel(
+                    inputLevelLabelCenter,
+                    Text.literal("")).centered()
+                val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25)
+                val outputLevelLabel = Widgets.createLabel(
+                    outputLevelLabelCenter,
+                    Text.literal("")).centered()
+                val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt())
+                levelValue.whenChanged { oldValue, newValue ->
+                    if (oldValue.toInt() == newValue.toInt()) return@whenChanged
+                    val oldInput = inputStack.getPetData() ?: return@whenChanged
+                    val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt())
+                    inputStack.setPetData(newInput)
+                    val oldOutput = outputStack.getPetData() ?: return@whenChanged
+                    val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp)
+                    outputStack.setPetData(newOutput)
+                    inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString())
+                    inputLevelLabel.bounds.location = Point(
+                        inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2,
+                        inputLevelLabelCenter.y)
+                    outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString())
+                    outputLevelLabel.bounds.location = Point(
+                        outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2,
+                        outputLevelLabelCenter.y)
+                    coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt())
+                }
+                levelValue.set(1F)
+                add(Widgets.createRecipeBase(bounds))
+                add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2,
+                                         bounds.maxY - 30,
+                                         slider.width,
+                                         slider.height),
+                               slider))
+                add(Widgets.withTooltip(
+                    Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)),
+                    Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds))))
+
+                add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40)))
+                add(inputLevelLabel)
+                add(outputLevelLabel)
+                add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground()
+                        .entry(SBItemEntryDefinition.getEntry(outputStack)))
+                add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput()
+                        .entry(SBItemEntryDefinition.getEntry(inputStack)))
+
+                val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } +
+                    listOf(SBItemEntryDefinition.getEntry(coinStack))
+                for ((index, item) in allInputs.withIndex()) {
+                    add(Widgets.createSlot(
+                        Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2,
+                              bounds.minY + 20))
+                            .markInput()
+                            .entry(item))
+                }
+            }
+        }
+    }
+}
+
+fun wrapWidget(bounds: Rectangle, component: GuiComponent): Widget {
+    return object : WidgetWithBounds() {
+        override fun getBounds(): Rectangle {
+            return bounds
+        }
+
+        override fun children(): List<Element> {
+            return listOf()
+        }
+
+        override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
+            context.matrices.push()
+            context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F)
+            component.render(
+                GuiImmediateContext(
+                    ModernRenderContext(context),
+                    bounds.minX, bounds.minY,
+                    bounds.width, bounds.height,
+                    mouseX - bounds.minX, mouseY - bounds.minY,
+                    mouseX, mouseY,
+                    mouseX.toFloat(), mouseY.toFloat()
+                ))
+            context.matrices.pop()
+        }
+
+        override fun mouseMoved(mouseX: Double, mouseY: Double) {
+            val mouseXInt = mouseX.toInt()
+            val mouseYInt = mouseY.toInt()
+            component.mouseEvent(MouseEvent.Move(0F, 0F),
+                                 GuiImmediateContext(
+                                     IMinecraft.instance.provideTopLevelRenderContext(),
+                                     bounds.minX, bounds.minY,
+                                     bounds.width, bounds.height,
+                                     mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+                                     mouseXInt, mouseYInt,
+                                     mouseX.toFloat(), mouseY.toFloat()
+                                 ))
+        }
+
+        override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+            val mouseXInt = mouseX.toInt()
+            val mouseYInt = mouseY.toInt()
+            return component.mouseEvent(MouseEvent.Click(button, true),
+                                        GuiImmediateContext(
+                                            IMinecraft.instance.provideTopLevelRenderContext(),
+                                            bounds.minX, bounds.minY,
+                                            bounds.width, bounds.height,
+                                            mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+                                            mouseXInt, mouseYInt,
+                                            mouseX.toFloat(), mouseY.toFloat()
+                                        ))
+        }
+
+        override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
+            val mouseXInt = mouseX.toInt()
+            val mouseYInt = mouseY.toInt()
+            return component.mouseEvent(MouseEvent.Click(button, false),
+                                        GuiImmediateContext(
+                                            IMinecraft.instance.provideTopLevelRenderContext(),
+                                            bounds.minX, bounds.minY,
+                                            bounds.width, bounds.height,
+                                            mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+                                            mouseXInt, mouseYInt,
+                                            mouseX.toFloat(), mouseY.toFloat()
+                                        ))
+        }
+
+        override fun mouseDragged(
+            mouseX: Double,
+            mouseY: Double,
+            button: Int,
+            deltaX: Double,
+            deltaY: Double
+        ): Boolean {
+            val mouseXInt = mouseX.toInt()
+            val mouseYInt = mouseY.toInt()
+            return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()),
+                                        GuiImmediateContext(
+                                            IMinecraft.instance.provideTopLevelRenderContext(),
+                                            bounds.minX, bounds.minY,
+                                            bounds.width, bounds.height,
+                                            mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+                                            mouseXInt, mouseYInt,
+                                            mouseX.toFloat(), mouseY.toFloat()
+                                        ))
+
+        }
+
+        override fun mouseScrolled(
+            mouseX: Double,
+            mouseY: Double,
+            horizontalAmount: Double,
+            verticalAmount: Double
+        ): Boolean {
+            val mouseXInt = mouseX.toInt()
+            val mouseYInt = mouseY.toInt()
+            return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()),
+                                        GuiImmediateContext(
+                                            IMinecraft.instance.provideTopLevelRenderContext(),
+                                            bounds.minX, bounds.minY,
+                                            bounds.width, bounds.height,
+                                            mouseXInt - bounds.minX, mouseYInt - bounds.minY,
+                                            mouseXInt, mouseYInt,
+                                            mouseX.toFloat(), mouseY.toFloat()
+                                        ))
+        }
+    }
+}
diff --git a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt
index 96bf49a..551fa1f 100644
--- a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt
+++ b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt
@@ -12,7 +12,7 @@ package moe.nea.firmament.util.item
 import com.mojang.authlib.GameProfile
 import com.mojang.authlib.minecraft.MinecraftProfileTexture
 import com.mojang.authlib.properties.Property
-import java.util.*
+import java.util.UUID
 import kotlinx.datetime.Clock
 import kotlinx.datetime.Instant
 import kotlinx.serialization.Serializable
@@ -63,10 +63,12 @@ fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) {
 val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1")
 fun ItemStack.setSkullOwner(uuid: UUID, url: String) {
     assert(this.item == Items.PLAYER_HEAD)
-    val gameProfile = GameProfile(uuid, "LameGuy123")
+    val gameProfile = GameProfile(uuid, "nea89")
     gameProfile.setTextures(
         MinecraftTexturesPayloadKt(
-            mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url))
+            textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)),
+            profileId = uuid,
+            profileName = "nea89",
         )
     )
     this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile))
-- 
cgit