From 854ec336cc6a0a3bb60f33acfac28ed45b491a15 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Mon, 14 Oct 2024 00:07:21 +0200 Subject: Add /firm exporthotm to save hotm presets --- src/main/kotlin/Firmament.kt | 4 + src/main/kotlin/events/SlotRenderEvents.kt | 10 + .../kotlin/features/mining/CommissionFeatures.kt | 3 +- src/main/kotlin/features/mining/HotmPresets.kt | 241 +++++++++++++++++++++ src/main/kotlin/util/TemplateUtil.kt | 3 +- src/main/kotlin/util/mc/CommonTextures.kt | 7 + .../resources/assets/firmament/lang/en_us.json | 6 + .../gui/sprites/generic_vanilla_widget.png | Bin 0 -> 4314 bytes .../gui/sprites/generic_vanilla_widget.png.mcmeta | 10 + .../textures/gui/sprites/hotm_perk_preset.png | Bin 0 -> 558 bytes 10 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/features/mining/HotmPresets.kt create mode 100644 src/main/kotlin/util/mc/CommonTextures.kt create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png.mcmeta create mode 100644 src/main/resources/assets/firmament/textures/gui/sprites/hotm_perk_preset.png diff --git a/src/main/kotlin/Firmament.kt b/src/main/kotlin/Firmament.kt index 343ec40..d8a309b 100644 --- a/src/main/kotlin/Firmament.kt +++ b/src/main/kotlin/Firmament.kt @@ -65,6 +65,10 @@ object Firmament { ignoreUnknownKeys = true encodeDefaults = true } + val tightJson = Json(from = json) { + prettyPrint = false + } + val httpClient by lazy { HttpClient { diff --git a/src/main/kotlin/events/SlotRenderEvents.kt b/src/main/kotlin/events/SlotRenderEvents.kt index 8352581..5431573 100644 --- a/src/main/kotlin/events/SlotRenderEvents.kt +++ b/src/main/kotlin/events/SlotRenderEvents.kt @@ -3,7 +3,10 @@ package moe.nea.firmament.events import net.minecraft.client.gui.DrawContext +import net.minecraft.client.texture.Sprite import net.minecraft.screen.slot.Slot +import net.minecraft.util.Identifier +import moe.nea.firmament.util.MC interface SlotRenderEvents { val context: DrawContext @@ -12,6 +15,13 @@ interface SlotRenderEvents { val mouseY: Int val delta: Float + fun highlight(sprite: Sprite) { + context.drawSprite( + slot.x, slot.y, 0, 16, 16, + sprite + ) + } + data class Before( override val context: DrawContext, override val slot: Slot, override val mouseX: Int, diff --git a/src/main/kotlin/features/mining/CommissionFeatures.kt b/src/main/kotlin/features/mining/CommissionFeatures.kt index d1b501e..d0acdfd 100644 --- a/src/main/kotlin/features/mining/CommissionFeatures.kt +++ b/src/main/kotlin/features/mining/CommissionFeatures.kt @@ -20,8 +20,7 @@ object CommissionFeatures { if (MC.screenName != "Commissions") return val stack = event.slot.stack if(stack.loreAccordingToNbt.any { it.unformattedString == "COMPLETED" }) { - event.context.drawSprite( - event.slot.x, event.slot.y, 0, 16, 16, + event.highlight( MC.guiAtlasManager.getSprite(Identifier.of("firmament:completed_commission_background")) ) } diff --git a/src/main/kotlin/features/mining/HotmPresets.kt b/src/main/kotlin/features/mining/HotmPresets.kt new file mode 100644 index 0000000..329ff77 --- /dev/null +++ b/src/main/kotlin/features/mining/HotmPresets.kt @@ -0,0 +1,241 @@ +package moe.nea.firmament.features.mining + +import me.shedaniel.math.Rectangle +import kotlinx.serialization.Serializable +import kotlin.time.Duration.Companion.seconds +import net.minecraft.block.Blocks +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.item.Items +import net.minecraft.screen.GenericContainerScreenHandler +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.ChestInventoryUpdateEvent +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.events.ScreenChangeEvent +import moe.nea.firmament.events.SlotRenderEvents +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TemplateUtil +import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.customgui.CustomGui +import moe.nea.firmament.util.customgui.customGui +import moe.nea.firmament.util.mc.CommonTextures +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.unformattedString +import moe.nea.firmament.util.useMatch + +object HotmPresets { + object Config : ManagedConfig("hotm-presets", Category.MINING) { + } + + val SHARE_PREFIX = "FIRMHOTM/" + + @Serializable + data class HotmPreset( + val perks: List, + ) + + @Serializable + data class PerkPreset(val perkName: String) + + var hotmCommandSent = TimeMark.farPast() + val hotmInventoryName = "Heart of the Mountain" + + @Subscribe + fun onScreenOpen(event: ScreenChangeEvent) { + val title = event.new?.title?.unformattedString + if (title != hotmInventoryName) return + val screen = event.new as? HandledScreen<*> ?: return + val oldHandler = (event.old as? HandledScreen<*>)?.customGui + if (oldHandler is HotmScrollPrompt) { + event.new.customGui = oldHandler + oldHandler.setNewScreen(screen) + return + } + if (hotmCommandSent.passedTime() > 5.seconds) return + hotmCommandSent = TimeMark.farPast() + screen.customGui = HotmScrollPrompt(screen) + } + + class HotmScrollPrompt(var screen: HandledScreen<*>) : CustomGui() { + var bounds = Rectangle( + 0, 0, 0, 0 + ) + + fun setNewScreen(screen: HandledScreen<*>) { + this.screen = screen + onInit() + hasScrolled = false + } + + override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) { + drawContext.drawGuiTexture( + CommonTextures.genericWidget(), + bounds.x, bounds.y, 0, + bounds.width, + bounds.height, + ) + drawContext.drawCenteredTextWithShadow( + MC.font, + if (hasAll) { + Text.translatable("firmament.hotmpreset.copied") + } else if (!hasScrolled) { + Text.translatable("firmament.hotmpreset.scrollprompt") + } else { + Text.translatable("firmament.hotmpreset.scrolled") + }, + bounds.centerX, + bounds.centerY - 5, + -1 + ) + } + + + var hasScrolled = false + var hasAll = false + + fun Slot.clickMiddleMouseButton(handler: ScreenHandler) { + MC.interactionManager?.clickSlot( + handler.syncId, + this.id, + 2, + SlotActionType.CLONE, + MC.player + ) + } + + fun Slot.clickRightMouseButton(handler: ScreenHandler) { + MC.interactionManager?.clickSlot( + handler.syncId, + this.id, + 1, + SlotActionType.PICKUP, + MC.player + ) + } + + override fun mouseClick(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (!hasScrolled) { + val slot = screen.screenHandler.getSlot(8) + println("Clicking ${slot.stack}") + slot.clickRightMouseButton(screen.screenHandler) + } + hasScrolled = true + return super.mouseClick(mouseX, mouseY, button) + } + + override fun shouldDrawForeground(): Boolean { + return false + } + + override fun getBounds(): List { + return listOf(bounds) + } + + override fun onInit() { + bounds = Rectangle( + screen.width / 2 - 150, + screen.height / 2 - 100, + 300, 200 + ) + val screen = screen as AccessorHandledScreen + screen.x_Firmament = bounds.x + screen.y_Firmament = bounds.y + screen.backgroundWidth_Firmament = bounds.width + screen.backgroundHeight_Firmament = bounds.height + } + + override fun moveSlot(slot: Slot) { + slot.x = -10000 + } + + val coveredRows = mutableSetOf() + val unlockedPerks = mutableSetOf() + val allRows = (1..10).toSet() + + fun onNewItems(event: ChestInventoryUpdateEvent) { + val handler = screen.screenHandler as? GenericContainerScreenHandler ?: return + for (it in handler.slots) { + if (it.inventory is PlayerInventory) continue + val stack = it.stack + val name = stack.displayNameAccordingToNbt.unformattedString + tierRegex.useMatch(name) { + coveredRows.add(group("tier").toInt()) + } + if (stack.item == Items.DIAMOND + || stack.item == Items.EMERALD + || stack.item == Blocks.EMERALD_BLOCK.asItem() + ) { + unlockedPerks.add(name) + } + } + if (allRows == coveredRows) { + ClipboardUtils.setTextContent(TemplateUtil.encodeTemplate(SHARE_PREFIX, HotmPreset( + unlockedPerks.map { PerkPreset(it) } + ))) + hasAll = true + } + } + } + + val tierRegex = "Tier (?[0-9]+)".toPattern() + var highlightedPerks: Set = emptySet() + + @Subscribe + fun onSlotUpdates(event: ChestInventoryUpdateEvent) { + val customGui = (event.inventory as? HandledScreen<*>)?.customGui + if (customGui is HotmScrollPrompt) { + customGui.onNewItems(event) + } + } + + @Subscribe + fun resetOnScreen(event: ScreenChangeEvent) { + if (event.new != null && event.new.title.unformattedString != hotmInventoryName) { + highlightedPerks = emptySet() + } + } + + @Subscribe + fun onSlotRender(event: SlotRenderEvents.Before) { + if (hotmInventoryName == MC.screenName + && event.slot.stack.displayNameAccordingToNbt.unformattedString in highlightedPerks + ) { + event.highlight(MC.guiAtlasManager.getSprite(Firmament.identifier("hotm_perk_preset"))) + } + } + + @Subscribe + fun onCommand(event: CommandEvent.SubCommand) { + event.subcommand("exporthotm") { + thenExecute { + hotmCommandSent = TimeMark.now() + MC.sendCommand("hotm") + source.sendFeedback(Text.translatable("firmament.hotmpreset.openinghotm")) + } + } + event.subcommand("importhotm") { + thenExecute { + val template = + TemplateUtil.maybeDecodeTemplate(SHARE_PREFIX, ClipboardUtils.getTextContents()) + if (template == null) { + source.sendFeedback(Text.translatable("firmament.hotmpreset.failedimport")) + } else { + highlightedPerks = template.perks.mapTo(mutableSetOf()) { it.perkName } + source.sendFeedback(Text.translatable("firmament.hotmpreset.okayimport")) + MC.sendCommand("hotm") + } + } + } + } + +} diff --git a/src/main/kotlin/util/TemplateUtil.kt b/src/main/kotlin/util/TemplateUtil.kt index 11100e9..f4ff37c 100644 --- a/src/main/kotlin/util/TemplateUtil.kt +++ b/src/main/kotlin/util/TemplateUtil.kt @@ -5,6 +5,7 @@ package moe.nea.firmament.util import java.util.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.json.Json import kotlinx.serialization.serializer import moe.nea.firmament.Firmament @@ -61,7 +62,7 @@ object TemplateUtil { fun encodeTemplate(sharePrefix: String, data: T, serializer: SerializationStrategy): String { require(sharePrefix.endsWith("/")) - return intoBase64Encoded(sharePrefix + Firmament.json.encodeToString(serializer, data)) + return intoBase64Encoded(sharePrefix + Firmament.tightJson.encodeToString(serializer, data)) } inline fun maybeDecodeTemplate(sharePrefix: String, data: String): T? = diff --git a/src/main/kotlin/util/mc/CommonTextures.kt b/src/main/kotlin/util/mc/CommonTextures.kt new file mode 100644 index 0000000..6b847df --- /dev/null +++ b/src/main/kotlin/util/mc/CommonTextures.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.util.mc + +import moe.nea.firmament.Firmament + +object CommonTextures { + fun genericWidget() = (Firmament.identifier("generic_vanilla_widget")) +} diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index eb0e551..1f2bbeb 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -94,6 +94,12 @@ "firmament.config.category.meta": "Meta & Firmament", "firmament.config.category.dev": "Developer & Debug", "firmament.ursa.debugrequest.start": "Ursa request launched", + "firmament.hotmpreset.openinghotm": "Opening /hotm menu for export.", + "firmament.hotmpreset.scrollprompt": "We need to scroll! Please click anywhere to continue.", + "firmament.hotmpreset.scrolled": "Just scrolled. Waiting on server to update items.", + "firmament.hotmpreset.copied": "Collected all HOTM perks to clipboard. Use /firm importhotm to import.", + "firmament.hotmpreset.okayimport": "Imported a HOTM perk preset.", + "firmament.hotmpreset.failedimport": "Could not find a HOTM perk preset in your clipboard. You can export your current HOTM perks with /firm exporthotm", "firmament.ursa.debugrequest.result": "Ursa request succeeded: %s", "firmament.sbinfo.nolocraw": "No locraw data available", "firmament.sbinfo.profile": "Current profile cutename: %s", diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png b/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png new file mode 100644 index 0000000..83fae16 Binary files /dev/null and b/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png differ diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png.mcmeta b/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png.mcmeta new file mode 100644 index 0000000..9d84425 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/generic_vanilla_widget.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 24, + "height": 41, + "border": 5 + } + } +} diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/hotm_perk_preset.png b/src/main/resources/assets/firmament/textures/gui/sprites/hotm_perk_preset.png new file mode 100644 index 0000000..a19f227 Binary files /dev/null and b/src/main/resources/assets/firmament/textures/gui/sprites/hotm_perk_preset.png differ -- cgit