From 854ec336cc6a0a3bb60f33acfac28ed45b491a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linnea=20Gr=C3=A4f?= Date: Mon, 14 Oct 2024 00:07:21 +0200 Subject: [PATCH] Add /firm exporthotm to save hotm presets --- src/main/kotlin/Firmament.kt | 4 + src/main/kotlin/events/SlotRenderEvents.kt | 10 + .../features/mining/CommissionFeatures.kt | 3 +- .../kotlin/features/mining/HotmPresets.kt | 241 ++++++++++++++++++ src/main/kotlin/util/TemplateUtil.kt | 3 +- src/main/kotlin/util/mc/CommonTextures.kt | 7 + .../assets/firmament/lang/en_us.json | 6 + .../gui/sprites/generic_vanilla_widget.png | Bin 0 -> 4314 bytes .../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 0000000000000000000000000000000000000000..83fae16e5618d5282ef6422a0feabc5f6c6fd012 GIT binary patch literal 4314 zcmeHKYitx%6rL?rOJfZx;H#Qp%B#Tay!JI?w-367uCh?t0u>aUow-xSb|1`6yIT~f z7#==BA<;q+BCkLKu@Mr%0tG1rK~f27jG&~50bli9gPM9~cDrqr#59qA-O1g1?!D)H z=iKj}dv@o!lIc@3((g^jFf7Ac>?wu*ZfMzQH^VnI^Xd=KwWA8J;`L6z(qI6ZmQ>6F zlA&iw$k`yLScYIjpkD#)4v-BUqupz{$?RVQ`M!j_4&?C(9m9sPq+l7)H$r1U9uEC3 zXzI$iuR|akpEUFN6Wq;%QBObB>np)oD??j37ueIRO<-t&;c%7}IGbRn!M|m5Z$t*t z3x_v?1Ix2&VPT24un-SxAw{i57#3?>{EEByz5LA9$IJGW&dofHcMRJqd|FhLSvBJM zl2PZI^tZ9UdKX8B0wfy}N; z7Y?>vc(A@|_J|L@_G0UU8yd<>Ph6g}ynW))RmBOD!jzusDLqJ7EPw!Ckz6=^5G+?7A7E#=8}eE*WS@_xUtdsgH6UtT4=o4Z;U z<{qzax3)j&I@8s3Y47y&m5VDH-Gi($Dg-d9kOQIp0m!DBctf~Z+=^|DcTk7`mM8}c4=Nf z*0wI49lHW47*oqD^a|fJK?((|q8#!gYb+2p&I`-Qi-kq03hB5XRjNTZ(fRIS0#{`> zF~{zsec?j1KrLRRp|VBO%cVtC5-$^Zx#>AE0RRF>7x7r2Iv5dRZoWJM_TOi4h%$W1KJ^{_xu(P-2f<*XsCl45wC zr)ZX9SrRnJNL^4DV`MOrWkAF+JSZY*YFJl8LEOL;{h=D&O%Sk-_rw}r(IiwgiR|TyPu}6D!pX&i_KUux6?NeFi+z4)9%^3sSH{^ zpWq2eHAZ+|kDD<33vx(OWx*6hC+AS?te<3Q-a*=sB9gpcp-Emw9CC0BhY;Nd${UR6 zVo*W`6o6Y*z;W1RMdq9wsW^BZAQ*-8v#dzk94<+5*_}2&*B4@jrb1SV)qSHfpk#n@ zIh}T!o%fTp>~xYgT9!zc%<-gXr)`L{^LEb0o6*RUFeRh~L^w`0AXXwO9IP}K48nzi z60e(Jt#PmBOi8tbXt2pRS(nSsICz=`H@ve~X*SX#kckGS zSvt)X<5&dP3=k_Cc?tmLJnTg%)R3r$wDM4>+D#;jZgSZN*GU$2(Ie^zK$B&-yr&FL zq*;MZk|y{JC)_ek6in)-0o0V%zd+ zVe%H;>npnDN_xg{g1uJOnFmubucx5Axb3@7nlf7OTT@$>uG?AhYC-eLdroB!8%?0b z3Ed-(p53&WTR3LTUEi-f?G`wu)^%VY2Saqaf+^E>~tx6d7dy?)R8i`UFNwBzEX zv|)IDJ!VNs1>xf1mh(a7<)*ip^M5g?pSD!(-r1@+MxOaDck!HD3;TRl>2~zeVu%6r N7ESl;nf%1EzX5br(slp< literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a19f2279a03b37492675d2352e409d30ed734d45 GIT binary patch literal 558 zcmV+}0@3}6P)EX>4Tx04R}tkv&MmKpe$iTcuK31nnT=kf92KT~x%eR-p(LLaorMgUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwX9p)m7b)?7NufoI2gm(*ckglc4)8atOf`EFfT~$W zCY2O&`BfqKiVz|gz&L^uGxa%9Ov7`0-NVP%yExDCKKJK{CjWHa-`QDULg#c~(3vY`@B6UP))qkMnP zWrgz=XSG^q?R)YUh6~!tGS_L2AcaLNL4*JqbyQG=g*dGmDJC+spY-q#JARQ|GPx>X z zjFl*R-Q(ST-M#&LrrqBUd{}a-{5(Fe00009a7bBm001r{001r{0eGc9b^rhX2XskI zMF;2#4H7B@`Mr!B0000PbVXQnLvL+uWo~o;Lvm$dbY)~9cWHEJAV*0}P*;Ht7XSbN w7)eAyR4C75l=#m;5@2Fr7}YSUVYq4l0Em|XH+oK