diff options
author | nea <nea@nea.moe> | 2023-10-28 03:36:11 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2023-11-12 09:35:33 +0100 |
commit | e15406e22ef65b4933274df791632b6c17f594be (patch) | |
tree | e1625c713bf95e9f0cb65dfe9a7c9b9ceaa46402 | |
parent | 47fbb25ab280b6af9496780672780db78fe36f27 (diff) | |
download | Firmament-e15406e22ef65b4933274df791632b6c17f594be.tar.gz Firmament-e15406e22ef65b4933274df791632b6c17f594be.tar.bz2 Firmament-e15406e22ef65b4933274df791632b6c17f594be.zip |
Add Inventory Buttons
24 files changed, 683 insertions, 9 deletions
@@ -3,7 +3,8 @@ # SPDX-License-Identifier: CC0-1.0 Prio 0 (Bugs): -- WorldReadyEvent buggy? -> out of date locraw +- Figure out how to do translations in moulconfig + Priority 1: - recipes diff --git a/build.gradle.kts b/build.gradle.kts index 14a6900..1e8eeb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,6 +66,7 @@ repositories { } maven("https://server.bbkr.space/artifactory/libs-release") maven("https://repo.nea.moe/releases") + maven("https://maven.notenoughupdates.org/releases") mavenLocal() } @@ -104,8 +105,10 @@ dependencies { modImplementation(libs.fabric.kotlin) modImplementation(libs.modmenu) modImplementation(libs.libgui) + modImplementation(libs.moulconfig) modCompileOnly(libs.explosiveenhancement) include(libs.libgui) + include(libs.moulconfig) annotationProcessor(libs.mixinextras) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de0f5de..79e3609 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ mixinextras = "0.2.0-rc.5" jarvis = "1.1.1" nealisp = "1.0.0" explosiveenhancement = "1.2.1-1.20.x" +moulconfig = "2.4.0" [libraries] @@ -35,6 +36,7 @@ fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab fabric_kotlin = { module = "net.fabricmc:fabric-language-kotlin", version.ref = "fabric_kotlin" } architectury = { module = "dev.architectury:architectury", version.ref = "architectury" } rei_api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" } +moulconfig = { module = "org.notenoughupdates.moulconfig:modern", version.ref = "moulconfig" } repoparser = { module = "moe.nea:neurepoparser", version.ref = "neurepoparser" } dbus_java_core = { module = "com.github.hypfvieh:dbus-java-core", version.ref = "dbus_java" } dbus_java_unixsocket = { module = "com.github.hypfvieh:dbus-java-transport-native-unixsocket", version.ref = "dbus_java" } diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java index 3972ffc..35856cb 100644 --- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java +++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java @@ -35,6 +35,10 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> { @Shadow public abstract T getScreenHandler(); + @Shadow + protected int y; + @Shadow + protected int x; @Unique PlayerInventory playerInventory; @@ -52,14 +56,17 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> { @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) public void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) { - if (ScreenClickEvent.Companion.publish(new ScreenClickEvent((HandledScreen<?>) (Object) this, mouseX, mouseY, button)).getCancelled()) { + if (HandledScreenClickEvent.Companion.publish(new HandledScreenClickEvent((HandledScreen<?>) (Object) this, mouseX, mouseY, button)).getCancelled()) { cir.setReturnValue(true); } } @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/gui/DrawContext;II)V", shift = At.Shift.AFTER)) public void onAfterRenderForeground(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - HandledScreenForegroundEvent.Companion.publish(new HandledScreenForegroundEvent((HandledScreen<?>) (Object) this, mouseX, mouseY, delta)); + context.getMatrices().push(); + context.getMatrices().translate(-x, -y, 0); + HandledScreenForegroundEvent.Companion.publish(new HandledScreenForegroundEvent((HandledScreen<?>) (Object) this, context, mouseX, mouseY, delta)); + context.getMatrices().pop(); } @Inject(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorHandledScreen.java index c42f819..b14717d 100644 --- a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorHandledScreen.java +++ b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorHandledScreen.java @@ -6,6 +6,7 @@ package moe.nea.firmament.mixins.accessor; +import me.shedaniel.math.Rectangle; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.screen.slot.Slot; import org.jetbrains.annotations.Nullable; @@ -17,4 +18,18 @@ public interface AccessorHandledScreen { @Accessor("focusedSlot") @Nullable Slot getFocusedSlot_Firmament(); + + @Accessor("backgroundWidth") + int getBackgroundWidth_Firmament(); + + @Accessor("backgroundHeight") + int getBackgroundHeight_Firmament(); + + @Accessor("x") + int getX_Firmament(); + + @Accessor("y") + int getY_Firmament(); + + } diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt index 3d62408..f4c20bd 100644 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt @@ -21,6 +21,7 @@ import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.util.* import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.minecraft.text.Text +import moe.nea.firmament.features.inventory.buttons.InventoryButtons fun firmamentCommand() = literal("firmament") { @@ -83,6 +84,11 @@ fun firmamentCommand() = literal("firmament") { } } } + thenLiteral("buttons") { + thenExecute { + InventoryButtons.openEditor() + } + } thenLiteral("storage") { thenExecute { ScreenUtil.setScreenLater(StorageOverlayScreen()) diff --git a/src/main/kotlin/moe/nea/firmament/events/ScreenClickEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt index 0977971..0c4d4f6 100644 --- a/src/main/kotlin/moe/nea/firmament/events/ScreenClickEvent.kt +++ b/src/main/kotlin/moe/nea/firmament/events/HandledScreenClickEvent.kt @@ -8,7 +8,7 @@ package moe.nea.firmament.events import net.minecraft.client.gui.screen.ingame.HandledScreen -data class ScreenClickEvent(val screen: HandledScreen<*>, val mouseX: Double, val mouseY: Double, val button: Int) : +data class HandledScreenClickEvent(val screen: HandledScreen<*>, val mouseX: Double, val mouseY: Double, val button: Int) : FirmamentEvent.Cancellable() { - companion object : FirmamentEventBus<ScreenClickEvent>() + companion object : FirmamentEventBus<HandledScreenClickEvent>() } diff --git a/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt b/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt index d45b484..95f9b70 100644 --- a/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt +++ b/src/main/kotlin/moe/nea/firmament/events/HandledScreenForegroundEvent.kt @@ -6,11 +6,15 @@ package moe.nea.firmament.events +import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.screen.ingame.HandledScreen data class HandledScreenForegroundEvent( val screen: HandledScreen<*>, - val mouseX: Int, val mouseY: Int, val delta: Float + val context: DrawContext, + val mouseX: Int, + val mouseY: Int, + val delta: Float ) : FirmamentEvent() { companion object : FirmamentEventBus<HandledScreenForegroundEvent>() } diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index 2683775..4888f54 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -23,6 +23,7 @@ import moe.nea.firmament.features.inventory.ItemRarityCosmetics import moe.nea.firmament.features.inventory.PriceData import moe.nea.firmament.features.inventory.SaveCursorPosition import moe.nea.firmament.features.inventory.SlotLocking +import moe.nea.firmament.features.inventory.buttons.InventoryButtons import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures import moe.nea.firmament.features.world.FairySouls @@ -56,6 +57,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature loadFeature(CraftingOverlay) loadFeature(PowerUserTools) loadFeature(ChatLinks) + loadFeature(InventoryButtons) loadFeature(CompatibliltyFeatures) loadFeature(QuickCommands) loadFeature(SaveCursorPosition) diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt b/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt index e045fa8..7e3203b 100644 --- a/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt +++ b/src/main/kotlin/moe/nea/firmament/features/debug/DeveloperFeatures.kt @@ -64,6 +64,7 @@ object DeveloperFeatures : FirmamentFeature { return reloadFuture.thenCompose { client.reloadResources() } } + override fun onLoad() { HandledScreenKeyPressedEvent.subscribe { if (it.matches(IKeyBinding.ofKeyCode(GLFW.GLFW_KEY_K))) { @@ -78,10 +79,16 @@ object DeveloperFeatures : FirmamentFeature { ).setStyle( Style.EMPTY.withColor(Formatting.AQUA) .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, ident)) - .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("firmament.debug.skyblockid.copy"))) + .withHoverEvent( + HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Text.translatable("firmament.debug.skyblockid.copy") + ) + ) ) ) } } } } + diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt new file mode 100644 index 0000000..0383258 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButton.kt @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.buttons + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import kotlinx.serialization.Serializable +import net.minecraft.client.gui.DrawContext +import net.minecraft.item.ItemStack +import net.minecraft.util.Identifier +import moe.nea.firmament.repo.ItemCache.asItemStack +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SkyblockId + +@Serializable +data class InventoryButton( + val x: Int, + val y: Int, + val anchorRight: Boolean, + val anchorBottom: Boolean, + var icon: String? = "", + var command: String? = "", +) { + companion object { + val dimensions = Dimension(18, 18) + fun getItemForName(icon: String): ItemStack { + return RepoManager.getNEUItem(SkyblockId(icon)).asItemStack(idHint = SkyblockId(icon)) + } + } + + fun render(context: DrawContext) { + context.drawSprite( + 0, + 0, + 0, + dimensions.width, + dimensions.height, + MC.guiAtlasManager.getSprite(Identifier("firmament:inventory_button_background")) + ) + context.drawItem(getItem(), 1, 1) + } + + fun isValid() = !icon.isNullOrBlank() && !command.isNullOrBlank() + + fun getPosition(guiRect: Rectangle): Point { + return Point( + (if (anchorRight) guiRect.maxX else guiRect.minX) + x, + (if (anchorBottom) guiRect.maxY else guiRect.minY) + y, + ) + } + + fun getBounds(guiRect: Rectangle): Rectangle { + return Rectangle(getPosition(guiRect), dimensions) + } + + fun getItem(): ItemStack { + return getItemForName(icon ?: "") + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt new file mode 100644 index 0000000..66be212 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonEditor.kt @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.buttons + +import io.github.moulberry.moulconfig.common.IItemStack +import io.github.moulberry.moulconfig.xml.Bind +import io.github.notenoughupdates.moulconfig.platform.ModernItemStack +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import org.lwjgl.glfw.GLFW +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.text.Text +import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.FragmentGuiScreen +import moe.nea.firmament.util.MoulConfigUtils + +class InventoryButtonEditor( + val lastGuiRect: Rectangle, +) : FragmentGuiScreen() { + inner class Editor(val originalButton: InventoryButton) { + @field:Bind + var command: String = originalButton.command ?: "" + + @field:Bind + var icon: String = originalButton.icon ?: "" + + @Bind + fun getItemIcon(): IItemStack { + save() + return ModernItemStack.of(InventoryButton.getItemForName(icon)) + } + + @Bind + fun delete() { + buttons.removeIf { it === originalButton } + popup = null + } + + fun save() { + originalButton.icon = icon + originalButton.command = command + } + } + + var buttons: MutableList<InventoryButton> = + InventoryButtons.DConfig.data.buttons.map { it.copy() }.toMutableList() + + override fun close() { + InventoryButtons.DConfig.data.buttons = buttons + InventoryButtons.DConfig.markDirty() + super.close() + } + + override fun init() { + super.init() + addDrawableChild( + ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.load-preset")) { + val t = ClipboardUtils.getTextContents() + val newButtons = InventoryButtonTemplates.loadTemplate(t) + if (newButtons != null) + buttons = newButtons.toMutableList() + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 35) + .width(lastGuiRect.width - 20) + .build() + ) + addDrawableChild( + ButtonWidget.builder(Text.translatable("firmament.inventory-buttons.save-preset")) { + ClipboardUtils.setTextContent(InventoryButtonTemplates.saveTemplate(buttons)) + } + .position(lastGuiRect.minX + 10, lastGuiRect.minY + 60) + .width(lastGuiRect.width - 20) + .build() + ) + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1) + context.setShaderColor(1f, 1f, 1f, 1f) + super.render(context, mouseX, mouseY, delta) + for (button in buttons) { + val buttonPosition = button.getBounds(lastGuiRect) + context.matrices.push() + context.matrices.translate(buttonPosition.minX.toFloat(), buttonPosition.minY.toFloat(), 0F) + button.render(context) + context.matrices.pop() + } + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + if (super.keyPressed(keyCode, scanCode, modifiers)) return true + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + close() + return true + } + return false + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + if (super.mouseClicked(mouseX, mouseY, button)) return true + val clickedButton = buttons.firstOrNull { it.getBounds(lastGuiRect).contains(Point(mouseX, mouseY)) } + if (clickedButton != null) { + createPopup(MoulConfigUtils.loadGui("button_editor_fragment", Editor(clickedButton)), Point(mouseX, mouseY)) + return true + } + if (lastGuiRect.contains(mouseX, mouseY) || lastGuiRect.contains( + Point( + mouseX + InventoryButton.dimensions.width, + mouseY + InventoryButton.dimensions.height, + ) + ) + ) return true + val mx = mouseX.toInt() + val my = mouseY.toInt() + val anchorRight = mx > lastGuiRect.maxX + val anchorBottom = my > lastGuiRect.maxY + val offsetX = mx - if (anchorRight) lastGuiRect.maxX else lastGuiRect.minX + val offsetY = my - if (anchorBottom) lastGuiRect.maxY else lastGuiRect.minY + buttons.add(InventoryButton(offsetX, offsetY, anchorRight, anchorBottom, null, null)) + return true + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt new file mode 100644 index 0000000..6b551b0 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtonTemplates.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.buttons + +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.TemplateUtil + +object InventoryButtonTemplates { + + val legacyPrefix = "NEUBUTTONS/" + val modernPrefix = "MAYBEONEDAYIWILLHAVEMYOWNFORMAT" + + fun loadTemplate(t: String): List<InventoryButton>? { + val buttons = TemplateUtil.maybeDecodeTemplate<List<String>>(legacyPrefix, t) ?: return null + return buttons.mapNotNull { + try { + Firmament.json.decodeFromString<InventoryButton>(it).also { + if (it.icon?.startsWith("extra:") == true || it.command?.any { it.isLowerCase() } == true) { + MC.sendChat(Text.translatable("firmament.inventory-buttons.import-failed")) + } + } + } catch (e: Exception) { + null + } + } + } + + fun saveTemplate(buttons: List<InventoryButton>): String { + return TemplateUtil.encodeTemplate(legacyPrefix, buttons.map { Firmament.json.encodeToString(it) }) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt new file mode 100644 index 0000000..85e1d88 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/inventory/buttons/InventoryButtons.kt @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.inventory.buttons + +import me.shedaniel.math.Rectangle +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import moe.nea.firmament.events.HandledScreenClickEvent +import moe.nea.firmament.events.HandledScreenForegroundEvent +import moe.nea.firmament.events.HandledScreenPushREIEvent +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.data.DataHolder +import moe.nea.firmament.util.getRectangle + +object InventoryButtons : FirmamentFeature { + override val identifier: String + get() = "inventory-buttons" + + object TConfig : ManagedConfig(identifier) {} + object DConfig : DataHolder<Data>(serializer(), identifier, ::Data) + + @Serializable + data class Data( + var buttons: MutableList<InventoryButton> = mutableListOf() + ) + + + override val config: ManagedConfig + get() = TConfig + + fun getValidButtons() = DConfig.data.buttons.asSequence().filter { it.isValid() } + override fun onLoad() { + HandledScreenForegroundEvent.subscribe { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + it.context.matrices.push() + it.context.matrices.translate(buttonBounds.minX.toFloat(), buttonBounds.minY.toFloat(), 0F) + button.render(it.context) + it.context.matrices.pop() + } + lastRectangle = bounds + } + HandledScreenClickEvent.subscribe { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + if (buttonBounds.contains(it.mouseX, it.mouseY)) { + MC.sendCommand(button.command!! /* non null invariant covered by getValidButtons */) + break + } + } + } + HandledScreenPushREIEvent.subscribe { + val bounds = it.screen.getRectangle() + for (button in getValidButtons()) { + val buttonBounds = button.getBounds(bounds) + it.block(buttonBounds) + } + } + } + + var lastRectangle: Rectangle? = null + fun openEditor() { + ScreenUtil.setScreenLater( + InventoryButtonEditor( + lastRectangle ?: Rectangle( + MC.window.scaledWidth / 2 - 100, + MC.window.scaledHeight / 2 - 100, + 200, 200, + ) + ) + ) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt index a91cd71..d643cbf 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt @@ -42,7 +42,6 @@ class FirmamentReiPlugin : REIClientPlugin { val SKYBLOCK_ITEM_TYPE_ID = Identifier("firmament", "skyblockitems") } - override fun registerTransferHandlers(registry: TransferHandlerRegistry) { registry.register(TransferHandler { context -> val screen = context.containerScreen @@ -66,7 +65,7 @@ class FirmamentReiPlugin : REIClientPlugin { } override fun registerExclusionZones(zones: ExclusionZones) { - zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent(it).rectangles } + zones.register(HandledScreen::class.java) { HandledScreenPushREIEvent.publish(HandledScreenPushREIEvent(it)).rectangles } } override fun registerDisplays(registry: DisplayRegistry) { diff --git a/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt b/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt new file mode 100644 index 0000000..4927d65 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/FragmentGuiScreen.kt @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import io.github.moulberry.moulconfig.gui.GuiContext +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.text.Text + +abstract class FragmentGuiScreen( + val dismissOnOutOfBounds: Boolean = true +) : Screen(Text.literal("")) { + var popup: MoulConfigFragment? = null + + fun createPopup(context: GuiContext, position: Point) { + popup = MoulConfigFragment(context, position) { popup = null } + } + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + super.render(context, mouseX, mouseY, delta) + context.matrices.push() + context.matrices.translate(0f, 0f, 1000f) + popup?.render(context, mouseX, mouseY, delta) + context.matrices.pop() + } + + private inline fun ifPopup(ifYes: (MoulConfigFragment) -> Unit): Boolean { + val p = popup ?: return false + ifYes(p) + return true + } + + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return ifPopup { + it.keyPressed(keyCode, scanCode, modifiers) + } + } + + override fun keyReleased(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + return ifPopup { + it.keyReleased(keyCode, scanCode, modifiers) + } + } + + override fun mouseMoved(mouseX: Double, mouseY: Double) { + ifPopup { it.mouseMoved(mouseX, mouseY) } + } + + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + return ifPopup { + it.mouseReleased(mouseX, mouseY, button) + } + } + + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + return ifPopup { + it.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) + } + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + return ifPopup { + if (!Rectangle( + it.position, + Dimension(it.context.root.width, it.context.root.height) + ).contains(Point(mouseX, mouseY)) + && dismissOnOutOfBounds + ) { + popup = null + } else { + it.mouseClicked(mouseX, mouseY, button) + } + }|| super.mouseClicked(mouseX, mouseY, button) + } + + override fun charTyped(chr: Char, modifiers: Int): Boolean { + return ifPopup { it.charTyped(chr, modifiers) } + } + + override fun mouseScrolled( + mouseX: Double, + mouseY: Double, + horizontalAmount: Double, + verticalAmount: Double + ): Boolean { + return ifPopup { + it.mouseScrolled(mouseX, mouseY, verticalAmount) + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt b/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt new file mode 100644 index 0000000..261c97a --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/GetRectangle.kt @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import me.shedaniel.math.Rectangle +import moe.nea.firmament.mixins.accessor.AccessorHandledScreen +import net.minecraft.client.gui.screen.ingame.HandledScreen + +fun HandledScreen<*>.getRectangle(): Rectangle { + this as AccessorHandledScreen + return Rectangle( + getX_Firmament(), + getY_Firmament(), + getBackgroundWidth_Firmament(), + getBackgroundHeight_Firmament() + ) +} diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt new file mode 100644 index 0000000..bb4a860 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/MoulConfigFragment.kt @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import io.github.moulberry.moulconfig.gui.GuiContext +import io.github.moulberry.moulconfig.gui.GuiImmediateContext +import io.github.notenoughupdates.moulconfig.gui.GuiComponentWrapper +import me.shedaniel.math.Point +import net.minecraft.client.gui.DrawContext +import moe.nea.firmament.util.MC + +class MoulConfigFragment( + context: GuiContext, + val position: Point, + val dismiss: () -> Unit +) : GuiComponentWrapper(context) { + init { + this.init(MC.instance, MC.screen!!.width, MC.screen!!.height) + } + + override fun createContext(drawContext: DrawContext?): GuiImmediateContext { + val oldContext = super.createContext(drawContext) + return oldContext.translated( + position.x, + position.y, + context.root.width, + context.root.height, + ) + } + + + override fun render(drawContext: DrawContext?, i: Int, j: Int, f: Float) { + val ctx = createContext(drawContext) + val m = drawContext!!.matrices + m.push() + m.translate(position.x.toFloat(), position.y.toFloat(), 0F) + context.root.render(ctx) + m.pop() + ctx.renderContext.doDrawTooltip() + } + + override fun close() { + dismiss() + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt new file mode 100644 index 0000000..bea3bc6 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/MoulConfigUtils.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import io.github.moulberry.moulconfig.common.MyResourceLocation +import io.github.moulberry.moulconfig.gui.GuiContext +import io.github.moulberry.moulconfig.xml.XMLUniverse + +object MoulConfigUtils { + val universe = XMLUniverse.getDefaultUniverse() + fun loadGui(name: String, bindTo: Any): GuiContext { + return GuiContext(universe.load(bindTo, MyResourceLocation("firmament", "gui/$name.xml"))) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt b/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt new file mode 100644 index 0000000..f5e3466 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/TemplateUtil.kt @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import java.util.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.serializer +import moe.nea.firmament.Firmament + +object TemplateUtil { + + @JvmStatic + fun getTemplatePrefix(data: String): String? { + val decoded = maybeFromBase64Encoded(data) ?: return null + return decoded.replaceAfter("/", "", "").ifBlank { null } + } + + @JvmStatic + fun intoBase64Encoded(raw: String): String { + return Base64.getEncoder().encodeToString(raw.encodeToByteArray()) + } + + private val base64Alphabet = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' + ) + + @JvmStatic + fun maybeFromBase64Encoded(raw: String): String? { + val raw = raw.trim() + if (raw.any { it !in base64Alphabet }) { + return null + } + return try { + Base64.getDecoder().decode(raw).decodeToString() + } catch (ex: Exception) { + null + } + } + + + /** + * Returns a base64 encoded string, truncated such that for all `x`, `x.startsWith(prefix)` implies + * `base64Encoded(x).startsWith(getPrefixComparisonSafeBase64Encoding(prefix))` + * (however, the inverse may not always be true). + */ + @JvmStatic + fun getPrefixComparisonSafeBase64Encoding(prefix: String): String { + val rawEncoded = + Base64.getEncoder().encodeToString(prefix.encodeToByteArray()) + .replace("=", "") + return rawEncoded.substring(0, rawEncoded.length - rawEncoded.length % 4) + } + + inline fun <reified T> encodeTemplate(sharePrefix: String, data: T): String = + encodeTemplate(sharePrefix, data, serializer()) + + fun <T> encodeTemplate(sharePrefix: String, data: T, serializer: SerializationStrategy<T>): String { + require(sharePrefix.endsWith("/")) + return intoBase64Encoded(sharePrefix + Firmament.json.encodeToString(serializer, data)) + } + + inline fun <reified T : Any> maybeDecodeTemplate(sharePrefix: String, data: String): T? = + maybeDecodeTemplate(sharePrefix, data, serializer()) + + fun <T : Any> maybeDecodeTemplate(sharePrefix: String, data: String, serializer: DeserializationStrategy<T>): T? { + require(sharePrefix.endsWith("/")) + val data = data.trim() + if (!data.startsWith(getPrefixComparisonSafeBase64Encoding(sharePrefix))) + return null + val decoded = maybeFromBase64Encoded(data) ?: return null + if (!decoded.startsWith(sharePrefix)) + return null + return try { + Firmament.json.decodeFromString<T>(serializer, decoded.substring(sharePrefix.length)) + } catch (e: Exception) { + null + } + } + +} diff --git a/src/main/resources/assets/firmament/gui/button_editor_fragment.xml b/src/main/resources/assets/firmament/gui/button_editor_fragment.xml new file mode 100644 index 0000000..2992825 --- /dev/null +++ b/src/main/resources/assets/firmament/gui/button_editor_fragment.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<!-- +SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + +SPDX-License-Identifier: GPL-3.0-or-later +--> + +<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://notenoughupdates.org/moulconfig https://raw.githubusercontent.com/NotEnoughUpdates/MoulConfig/master/MoulConfig.xsd"> + <Panel> + <Column> + <Row> + <ItemStack value="@getItemIcon"/> + <Text text="Icon"/> + </Row> + <Hover lines="Put your display item in here.;Can be any SkyBlock item id."> + <TextField value="@icon" width="180"/> + </Hover> + <Text text="Command"/> + <Hover lines="Put the command in here.;The text box should not start with a /"> + <Row> + <Text text="/"/> + <TextField value="@command" width="180"/> + </Row> + </Hover> + <Button onClick="@delete"> + <Text text="Delete"/> + </Button> + </Column> + </Panel> +</Root> diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index 4e68efe..4d00d4d 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -61,6 +61,10 @@ "firmament.config.fishing-warning.display-warning": "Display a warning when you are about to hook a fish", "firmament.config.fishing-warning.highlight-wake-chain": "Highlight fishing particles", "firmament.protectitem": "Firmament protected your item: ", + "firmament.inventory-buttons.save-preset": "Save Preset", + "firmament.inventory-buttons.load-preset": "Load Preset", + "firmament.inventory-buttons.import-failed": "One of your buttons could only be imported partially", + "firmament.config.inventory-buttons": "One of your buttons could only be imported partially", "firmament.recipe.forge.time": "Forging Time: %s", "firmament.pv.skills": "Skills", "firmament.pv.skills.farming": "Farming", diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png b/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png Binary files differnew file mode 100644 index 0000000..3e1e769 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png diff --git a/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png.license b/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png.license new file mode 100644 index 0000000..c01d463 --- /dev/null +++ b/src/main/resources/assets/firmament/textures/gui/sprites/inventory_button_background.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + +SPDX-License-Identifier: CC0-1.0 |