From 08157c2e3aab89c1717ba07c420acc9a3bf68693 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:13:04 +0000 Subject: [PATCH 1/8] ### Features - Reworked keybind system with a dedicated single-item "yoink" key. - New configurable toggle to enable or disable the single-item keybind. ### Code changes - Introduced `Key` interface to simplify keybind creation. - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. --- .../mixin/client/ItemStackMixin.java | 36 ++++++++++++ .../kotlin/me/thatonedevil/YoinkGUIClient.kt | 56 ++++++++----------- .../thatonedevil/config/ModMenuIntegration.kt | 6 ++ .../thatonedevil/config/YoinkGuiSettings.kt | 2 + .../kotlin/me/thatonedevil/keybinds/Key.kt | 41 ++++++++++++++ .../thatonedevil/keybinds/KeybindManager.kt | 28 ++++++++++ .../me/thatonedevil/keybinds/MenuKeybind.kt | 29 ++++++++++ .../keybinds/YoinkSingleKeybind.kt | 22 ++++++++ .../resources/yoinkgui.client.mixins.json | 3 +- .../resources/assets/yoinkgui/lang/en_us.json | 4 +- 10 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java create mode 100644 src/client/kotlin/me/thatonedevil/keybinds/Key.kt create mode 100644 src/client/kotlin/me/thatonedevil/keybinds/KeybindManager.kt create mode 100644 src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt create mode 100644 src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt diff --git a/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java b/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java new file mode 100644 index 0000000..bc64675 --- /dev/null +++ b/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java @@ -0,0 +1,36 @@ +package me.thatonedevil.mixin.client; + +import me.thatonedevil.YoinkGUIClient; +import me.thatonedevil.config.YoinkGuiSettings; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.tooltip.TooltipType; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(ItemStack.class) +public class ItemStackMixin { + + @Inject(method = "getTooltip", at = @At("RETURN")) + private void onGetTooltip(Item.TooltipContext context, PlayerEntity player, TooltipType type, CallbackInfoReturnable> cir) { + List tooltip = cir.getReturnValue(); + YoinkGuiSettings config = YoinkGUIClient.getYoinkGuiSettings(); + + if (!config.getEnableSingleItemYoink().get()) { + return; + } + + tooltip.add(Text.literal("")); + + tooltip.add(Text.literal("§ePress X to yoink item")); + + YoinkGUIClient.setHoveredItemStack((ItemStack)(Object)this); + } +} + diff --git a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt index 8724aa3..bcd62f8 100644 --- a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt +++ b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt @@ -8,54 +8,38 @@ import me.thatonedevil.config.YoinkGuiSettings import me.thatonedevil.gui.ButtonPositionScreen import me.thatonedevil.inventory.TopInventory import me.thatonedevil.inventory.YoinkInventory +import me.thatonedevil.keybinds.KeybindManager import me.thatonedevil.utils.LatestErrorLog import me.thatonedevil.utils.Utils.sendChat import me.thatonedevil.utils.Utils.toClickCopy import me.thatonedevil.utils.api.UpdateChecker import net.fabricmc.api.ClientModInitializer import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper import net.minecraft.client.MinecraftClient -import net.minecraft.client.option.KeyBinding -import net.minecraft.client.util.InputUtil -import net.minecraft.util.Identifier +import net.minecraft.item.ItemStack import org.lwjgl.glfw.GLFW import org.slf4j.Logger import org.slf4j.LoggerFactory object YoinkGUIClient : ClientModInitializer { var parseButtonHovered = false - private var wasLeftClicking = false + var wasLeftClicking = false + private var hoveredItemStack: ItemStack? = null + val logger: Logger = LoggerFactory.getLogger(BuildConfig.MOD_ID) @JvmStatic val yoinkGuiSettings = YoinkGuiSettings - //? if >=1.21.9 { - private val positionButtonKeybind = KeyBindingHelper.registerKeyBinding( - KeyBinding( - "key.yoinkgui.position", - InputUtil.Type.KEYSYM, - GLFW.GLFW_KEY_M, - KeyBinding.Category.create(Identifier.of("keybinds")) - ) - ) - //? } else { - /*private val positionButtonKeybind = KeyBindingHelper.registerKeyBinding( - KeyBinding( - "key.yoinkgui.position", - InputUtil.Type.KEYSYM, - GLFW.GLFW_KEY_M, - "key.category.minecraft.keybinds" - ) - )*/ - //?} - override fun onInitializeClient() { + UpdateChecker.setupJoinListener() + YoinkGuiClientCommandRegistry.register() + KeybindManager().register() + + yoinkGuiSettings // Load settings on client init + + // Button click logic. ClientTickEvents.END_CLIENT_TICK.register { client -> - if (positionButtonKeybind.wasPressed()) { - client.setScreen(ButtonPositionScreen(client.currentScreen)) - } val isLeftClicking = GLFW.glfwGetMouseButton(client.window.handle, GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS @@ -74,10 +58,6 @@ object YoinkGUIClient : ClientModInitializer { wasLeftClicking = isLeftClicking } - - UpdateChecker.setupJoinListener() - YoinkGuiClientCommandRegistry.register() - yoinkGuiSettings // Load settings on client init } fun handleParseButton(client: MinecraftClient) { @@ -103,4 +83,16 @@ object YoinkGUIClient : ClientModInitializer { } } } + + //temporary + @JvmStatic + fun getHoveredItemStack(): ItemStack? { + return hoveredItemStack + } + + + @JvmStatic + fun setHoveredItemStack(itemStack: ItemStack?) { + hoveredItemStack = itemStack + } } \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt b/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt index a2751e4..a4698c8 100644 --- a/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt +++ b/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt @@ -36,6 +36,12 @@ class ModMenuIntegration : ModMenuApi { field = yoinkGuiSettings.enableYoinkButton, defaultValue = true )) + .option(booleanOption( + name = "Enable Single Item Yoink", + field = yoinkGuiSettings.enableSingleItemYoink, + defaultValue = true, + description = "Allows yoinking single items when pressing X while hovering." + )) .build()) .build()) diff --git a/src/client/kotlin/me/thatonedevil/config/YoinkGuiSettings.kt b/src/client/kotlin/me/thatonedevil/config/YoinkGuiSettings.kt index 535c0b5..dda1c5e 100644 --- a/src/client/kotlin/me/thatonedevil/config/YoinkGuiSettings.kt +++ b/src/client/kotlin/me/thatonedevil/config/YoinkGuiSettings.kt @@ -19,6 +19,8 @@ open class YoinkGuiSettings() : JsonFileCodecConfig( } val enableYoinkButton by register(default = true, BOOL) + val enableSingleItemYoink by register(default = true, BOOL) + val buttonScaleFactor by register(default = 1.0f, FLOAT) val buttonX by register(default = 40, INT) val buttonY by register(default = 35, INT) diff --git a/src/client/kotlin/me/thatonedevil/keybinds/Key.kt b/src/client/kotlin/me/thatonedevil/keybinds/Key.kt new file mode 100644 index 0000000..e6c47d6 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/Key.kt @@ -0,0 +1,41 @@ +package me.thatonedevil.keybinds + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper +import net.minecraft.client.option.KeyBinding +import net.minecraft.client.util.InputUtil +import net.minecraft.util.Identifier + +interface Key { + fun keyName(): String + fun key(): Int + + fun whenPressed() + fun keyType(): InputUtil.Type = InputUtil.Type.KEYSYM + + fun register(): KeyBinding { + //? if >=1.21.9 { + val keyBinding = KeyBinding( + keyName(), + keyType(), + key(), + newKeybindCategory + ) + //? } else { + /*val keyBinding = KeyBinding( + keyName(), + keyType(), + key(), + "key.category.minecraft.keybinds" + )*/ + //?} + + return KeyBindingHelper.registerKeyBinding(keyBinding) + } + + companion object { + val newKeybindCategory: KeyBinding.Category = + KeyBinding.Category.create(Identifier.of("keybinds")) + } + + +} \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/keybinds/KeybindManager.kt b/src/client/kotlin/me/thatonedevil/keybinds/KeybindManager.kt new file mode 100644 index 0000000..47ae64b --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/KeybindManager.kt @@ -0,0 +1,28 @@ +package me.thatonedevil.keybinds + +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.minecraft.client.option.KeyBinding + +class KeybindManager { + + private val keys: List = listOf( + MenuKeybind(), YoinkSingleKeybind() + ) + + private val bindings = mutableMapOf() + + fun register() { + keys.forEach { key -> + val binding = key.register() + bindings[binding] = key + } + + ClientTickEvents.END_CLIENT_TICK.register { + bindings.forEach { (binding, key) -> + if (binding.wasPressed()) { + key.whenPressed() + } + } + } + } +} diff --git a/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt new file mode 100644 index 0000000..f5db9e2 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt @@ -0,0 +1,29 @@ +package me.thatonedevil.keybinds + +import me.thatonedevil.gui.ButtonPositionScreen +import net.minecraft.client.MinecraftClient +import org.lwjgl.glfw.GLFW + +class MenuKeybind : Key{ + + override fun keyName(): String { + return "key.yoinkgui.position" + } + + override fun key(): Int { + return GLFW.GLFW_KEY_M + } + + override fun whenPressed() { + val client = MinecraftClient.getInstance() + + // Close screen when button pressed again while in ButtonPositionScreen + if (client.currentScreen is ButtonPositionScreen) { + client.setScreen(null) + return + } + + client.setScreen(ButtonPositionScreen(client.currentScreen)) + } + +} \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt new file mode 100644 index 0000000..ab87d39 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt @@ -0,0 +1,22 @@ +package me.thatonedevil.keybinds + +import me.thatonedevil.utils.Utils +import net.minecraft.client.MinecraftClient +import org.lwjgl.glfw.GLFW + +class YoinkSingleKeybind : Key{ + + override fun keyName(): String { + return "key.yoinkgui.yoinksingle" + } + + override fun key(): Int { + return GLFW.GLFW_KEY_X + } + + override fun whenPressed() { + val client = MinecraftClient.getInstance() + Utils.sendChat("/yoink single keybind pressed") + } + +} \ No newline at end of file diff --git a/src/client/resources/yoinkgui.client.mixins.json b/src/client/resources/yoinkgui.client.mixins.json index 2a7d260..cbb51f5 100644 --- a/src/client/resources/yoinkgui.client.mixins.json +++ b/src/client/resources/yoinkgui.client.mixins.json @@ -3,7 +3,8 @@ "package": "me.thatonedevil.mixin.client", "compatibilityLevel": "JAVA_21", "client": [ - "ScreenMixin" + "ScreenMixin", + "ItemStackMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/assets/yoinkgui/lang/en_us.json b/src/main/resources/assets/yoinkgui/lang/en_us.json index 50f3b41..352487d 100644 --- a/src/main/resources/assets/yoinkgui/lang/en_us.json +++ b/src/main/resources/assets/yoinkgui/lang/en_us.json @@ -1,5 +1,7 @@ { "key.category.minecraft.keybinds": "Yoinkgui Key Binds", - "key.yoinkgui.position": "Open Button Position Screen" + + "key.yoinkgui.position": "Open Button Position Screen", + "key.yoinkgui.yoinksingle": "Yoink Single item" } From 81b11c11219ab63b493709ce5239248632199691 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:13:08 +0000 Subject: [PATCH 2/8] ### Features - Reworked keybind system with a dedicated single-item "yoink" key. - New configurable toggle to enable or disable the single-item keybind. ### Code changes - Introduced `Key` interface to simplify keybind creation. - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. --- src/main/resources/changelogs/1.8.0.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/resources/changelogs/1.8.0.md diff --git a/src/main/resources/changelogs/1.8.0.md b/src/main/resources/changelogs/1.8.0.md new file mode 100644 index 0000000..0a4d84a --- /dev/null +++ b/src/main/resources/changelogs/1.8.0.md @@ -0,0 +1,12 @@ +# YoinkGUI v1.7.2 +### Released January 05, 2026 + +### Features +- Reworked keybind system with a dedicated single-item "yoink" key. +- New configurable toggle to enable or disable the single-item keybind. + +### Code changes +- Introduced `Key` interface to simplify keybind creation. +- Added `KeybindManager` for centralized registration and management of keybinds. +- Removed redundant logic from `YoinkGuiClient`. +- Added `SingleItemKeybind` implementation for the new key. From fbb4ad6849cbc181b266d96a8f09d09422a6724c Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:13:21 +0000 Subject: [PATCH 3/8] ### Features - Reworked keybind system with a dedicated single-item "yoink" key. - New configurable toggle to enable or disable the single-item keybind. ### Code changes - Introduced `Key` interface to simplify keybind creation. - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 47c0b51..7c52291 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { id("me.modmuss50.mod-publish-plugin") version "0.8.4" } -val modVersion = "1.7.2" +val modVersion = "1.8.0" version = "${modVersion}+${property("mod.mod_version") as String}" group = property("maven_group") as String From 8c65c93a5e51d4763b8f8c180aa9fab025056c4b Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:26:08 +0000 Subject: [PATCH 4/8] fix(Key.kt): - Fixed compile error caused by version specific methods. - Changed key for yoink single item from X > Y - Added NBTParser#saveSingleItem method. --- .../mixin/client/ItemStackMixin.java | 2 +- .../kotlin/me/thatonedevil/NBTParser.kt | 59 +++++++++++++++---- .../thatonedevil/inventory/YoinkInventory.kt | 11 ++++ .../kotlin/me/thatonedevil/keybinds/Key.kt | 2 + .../keybinds/YoinkSingleKeybind.kt | 2 +- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java b/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java index bc64675..8b54209 100644 --- a/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java +++ b/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java @@ -28,7 +28,7 @@ private void onGetTooltip(Item.TooltipContext context, PlayerEntity player, Tool tooltip.add(Text.literal("")); - tooltip.add(Text.literal("§ePress X to yoink item")); + tooltip.add(Text.literal("§ePress Y to yoink item")); YoinkGUIClient.setHoveredItemStack((ItemStack)(Object)this); } diff --git a/src/client/kotlin/me/thatonedevil/NBTParser.kt b/src/client/kotlin/me/thatonedevil/NBTParser.kt index d61c2fd..daafe2a 100644 --- a/src/client/kotlin/me/thatonedevil/NBTParser.kt +++ b/src/client/kotlin/me/thatonedevil/NBTParser.kt @@ -12,6 +12,7 @@ import me.thatonedevil.utils.Utils import me.thatonedevil.nbt.ComponentValueRegistry import me.thatonedevil.utils.LatestErrorLog import me.thatonedevil.utils.api.UpdateChecker.serverName +import net.minecraft.nbt.NbtElement import java.io.File import java.io.FileWriter import java.time.Duration @@ -74,34 +75,43 @@ object NBTParser { } } - - suspend fun saveFormattedNBTToFile(nbtList: List, configDir: File) = withContext(Dispatchers.IO) { + private suspend fun saveNbtFile( + configDir: File, + subDir: String, + rawItems: List + ) = withContext(Dispatchers.IO) { val start = LocalDateTime.now() val formattedTime = start.format(DateTimeFormatter.ofPattern("MM-dd HH-mm-ss")) + try { - val yoinkDir = File(configDir, "assets/yoinkgui").apply { mkdirs() } - val fileName = "${serverName}-${formattedTime}.txt" - val file = File(yoinkDir, fileName) + val yoinkDir = File(configDir, subDir).apply { mkdirs() } + val file = File(yoinkDir, "${serverName}-${formattedTime}.txt") FileWriter(file).use { writer -> - val contentItems = nbtList.mapIndexedNotNull { i, raw -> - val formatted = parseNewNBTFormat(raw) - if (formatted.isNotBlank()) Pair(i, formatted) else null + val items = rawItems.mapNotNull { raw -> + val formatted = runCatching { parseNewNBTFormat(raw) }.getOrNull() + if (formatted.isNullOrBlank()) null else raw to formatted } writer.write("=== Formatted NBT Data ===\n") writer.write("Generated: $formattedTime\n") - writer.write("Items with content: ${contentItems.size} / ${nbtList.size}\n\n") + writer.write("Items with content: ${items.size} / ${rawItems.size}\n\n") + writer.write("=== Details ===\n") writer.write("Mod Version: ${BuildConfig.VERSION}\n") writer.write("Minecraft Version: ${BuildConfig.MC_VERSION}\n\n") - contentItems.forEachIndexed { outIdx, (originalIndex, formatted) -> - writer.write("=== ITEM ${originalIndex + 1} ===\n") - if (YoinkGuiSettings.includeRawNbt.get()) { writer.write("Raw NBT: ${nbtList[originalIndex]}\n") } + items.forEachIndexed { index, (raw, formatted) -> + writer.write("=== ITEM ${index + 1} ===\n") + if (YoinkGuiSettings.includeRawNbt.get()) { + writer.write("Raw NBT: $raw\n") + } writer.write("\n$formatted\n") - if (outIdx < contentItems.lastIndex) writer.write("\n${"=".repeat(50)}\n\n") + + if (index < items.lastIndex) { + writer.write("\n${"=".repeat(50)}\n\n") + } } } @@ -118,4 +128,27 @@ object NBTParser { logger.error("Error saving NBT file: ${e.message}", e) } } + + suspend fun saveFormattedNBTToFile( + nbtList: List, + configDir: File + ) { + saveNbtFile( + configDir, + "assets/yoinkgui", + nbtList + ) + } + + suspend fun saveSingleItem( + rawNbt: String, + configDir: File + ) { + saveNbtFile( + configDir, + "assets/items/yoinkgui", + listOf(rawNbt) + ) + } + } diff --git a/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt b/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt index 92c93d0..9c78669 100644 --- a/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt +++ b/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt @@ -29,5 +29,16 @@ class YoinkInventory(private val player: ClientPlayerEntity, private val invento } + fun yoinkSingleItem(itemStack: ItemStack): String? { + if (topInventory == null || itemStack.isEmpty) { + sendChat("No inventory found or item is empty!") + return null + } + + val registryOps = player.registryManager.getOps(NbtOps.INSTANCE) + val encodeResult = ItemStack.CODEC.encodeStart(registryOps, itemStack) + return encodeResult.result().get().toString() + } + fun getYoinkedItems(): List = encodedItems } diff --git a/src/client/kotlin/me/thatonedevil/keybinds/Key.kt b/src/client/kotlin/me/thatonedevil/keybinds/Key.kt index e6c47d6..e626a33 100644 --- a/src/client/kotlin/me/thatonedevil/keybinds/Key.kt +++ b/src/client/kotlin/me/thatonedevil/keybinds/Key.kt @@ -32,10 +32,12 @@ interface Key { return KeyBindingHelper.registerKeyBinding(keyBinding) } + //? if >=1.21.9 { companion object { val newKeybindCategory: KeyBinding.Category = KeyBinding.Category.create(Identifier.of("keybinds")) } + //? } } \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt index ab87d39..f9a38fc 100644 --- a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt +++ b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt @@ -11,7 +11,7 @@ class YoinkSingleKeybind : Key{ } override fun key(): Int { - return GLFW.GLFW_KEY_X + return GLFW.GLFW_KEY_Y } override fun whenPressed() { From a6289b809911f902a5673c2fedf3e420d7942669 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:52:10 +0000 Subject: [PATCH 5/8] Fixed version mismatch on 1.8.0.md Made hex values less oops Added `/yoinkgui config` command to open the configuration GUI. --- .../me/thatonedevil/mixin/client/ScreenMixin.java | 4 ++-- .../commands/YoinkGuiClientCommandRegistry.kt | 11 +++++++++++ .../me/thatonedevil/config/ModMenuIntegration.kt | 2 +- .../me/thatonedevil/gui/ButtonPositionScreen.kt | 10 +++++----- .../kotlin/me/thatonedevil/gui/ChangelogScreen.kt | 8 ++++---- .../me/thatonedevil/keybinds/YoinkSingleKeybind.kt | 2 -- src/main/resources/changelogs/1.8.0.md | 3 ++- 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java b/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java index 2d3b782..6def714 100644 --- a/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java +++ b/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java @@ -55,14 +55,14 @@ private void render(DrawContext context, int mouseX, int mouseY, float deltaTick YoinkGUIClient.INSTANCE.setParseButtonHovered(mouseXUi >= parseButtonX && mouseXUi <= parseButtonX + parseButtonWidth && mouseYUi >= parseButtonY && mouseYUi <= parseButtonY + parseButtonHeight); - int parseBgColor = YoinkGUIClient.INSTANCE.getParseButtonHovered() ? 0xAA444444 : 0xAA000000; + int parseBgColor = YoinkGUIClient.INSTANCE.getParseButtonHovered() ? 0xAA444 : 0xAA000; context.fill(parseButtonX, parseButtonY, parseButtonX + parseButtonWidth, parseButtonY + parseButtonHeight, parseBgColor); context.drawCenteredTextWithShadow( client.textRenderer, literal(parseButtonText), parseButtonX + parseButtonWidth / 2, parseButtonY + (parseButtonHeight - 8) / 2, - 0xFFFFFFFF + 0xFFFFF ); } diff --git a/src/client/kotlin/me/thatonedevil/commands/YoinkGuiClientCommandRegistry.kt b/src/client/kotlin/me/thatonedevil/commands/YoinkGuiClientCommandRegistry.kt index fd80efe..8a80073 100644 --- a/src/client/kotlin/me/thatonedevil/commands/YoinkGuiClientCommandRegistry.kt +++ b/src/client/kotlin/me/thatonedevil/commands/YoinkGuiClientCommandRegistry.kt @@ -2,6 +2,7 @@ package me.thatonedevil.commands import com.mojang.brigadier.Command import com.mojang.brigadier.CommandDispatcher +import me.thatonedevil.config.ModMenuIntegration import me.thatonedevil.gui.ButtonPositionScreen import me.thatonedevil.gui.ChangelogScreen import me.thatonedevil.utils.api.UpdateChecker @@ -52,6 +53,16 @@ object YoinkGuiClientCommandRegistry { ClientCommandManager.literal("debug") .executes { _ -> debugCommand.execute() } ) + .then( + ClientCommandManager.literal("config") + .executes { context -> + val client = context.source.client + client.send { + client.setScreen(ModMenuIntegration().createScreen(null)) + } + Command.SINGLE_SUCCESS + } + ) ) } diff --git a/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt b/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt index a4698c8..c6ebe0d 100644 --- a/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt +++ b/src/client/kotlin/me/thatonedevil/config/ModMenuIntegration.kt @@ -18,7 +18,7 @@ class ModMenuIntegration : ModMenuApi { createScreen(parentScreen) } - private fun createScreen(parentScreen: Screen?): Screen { + fun createScreen(parentScreen: Screen?): Screen { val screen = YetAnotherConfigLib.createBuilder() .save { YoinkGuiSettings.saveToFile() diff --git a/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt b/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt index 945a5ec..1ea7417 100644 --- a/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt +++ b/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt @@ -75,7 +75,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P buttonY = (mouseY - dragOffsetY).coerceIn(0, height - scaledButtonHeight) } - val buttonColor = if (isMouseOverButton(mouseX, mouseY)) 0xAA444444.toInt() else 0xAA000000.toInt() + val buttonColor = if (isMouseOverButton(mouseX, mouseY)) 0xAA444 else 0xAA000 context.fill( buttonX, buttonY, @@ -88,7 +88,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Yoink and Parse NBT into file"), buttonX + scaledButtonWidth / 2, buttonY + (scaledButtonHeight - 8) / 2, - 0xFFFFFFFF.toInt() + 0xFFFFF ) context.drawCenteredTextWithShadow( @@ -96,7 +96,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Drag the button to reposition it"), width / 2, 20, - 0xFFFFFFFF.toInt() + 0xFFFFF ) context.drawCenteredTextWithShadow( @@ -104,7 +104,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Use mouse wheel to scale (Current: ${"%.2f".format(scaleFactor)}x)"), width / 2, 35, - 0xFFFFFFFF.toInt() + 0xFFFFF ) context.drawCenteredTextWithShadow( @@ -112,7 +112,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Press ESC or ENTER to save and exit"), width / 2, 50, - 0xFFFFFFFF.toInt() + 0xFFFFF ) } diff --git a/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt b/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt index bf19819..bc43152 100644 --- a/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt +++ b/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt @@ -60,7 +60,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("No changelog available."), centerX, height / 2, - 0xFFE0E0E0.toInt() + 0xFFE0E ) return } @@ -79,7 +79,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change line, centerX, y, - 0xFFE0E0E0.toInt() + 0xFFE0E ) } y += lineHeight @@ -92,7 +92,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("Press ESC to close"), centerX, height - 20, - 0xFFAAAAAA.toInt() + 0xFFAAA ) if (maxScroll > 0) { @@ -102,7 +102,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("↕ Scroll: $scrollPercentage%"), centerX, height - 10, - 0xFF888888.toInt() + 0xFF888 ) } } diff --git a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt index f9a38fc..b28f2ef 100644 --- a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt +++ b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt @@ -1,7 +1,6 @@ package me.thatonedevil.keybinds import me.thatonedevil.utils.Utils -import net.minecraft.client.MinecraftClient import org.lwjgl.glfw.GLFW class YoinkSingleKeybind : Key{ @@ -15,7 +14,6 @@ class YoinkSingleKeybind : Key{ } override fun whenPressed() { - val client = MinecraftClient.getInstance() Utils.sendChat("/yoink single keybind pressed") } diff --git a/src/main/resources/changelogs/1.8.0.md b/src/main/resources/changelogs/1.8.0.md index 0a4d84a..6a8e634 100644 --- a/src/main/resources/changelogs/1.8.0.md +++ b/src/main/resources/changelogs/1.8.0.md @@ -1,4 +1,4 @@ -# YoinkGUI v1.7.2 +# YoinkGUI v1.8.0 ### Released January 05, 2026 ### Features @@ -10,3 +10,4 @@ - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. +- Added `/yoinkgui config` command to open the configuration GUI. \ No newline at end of file From 80b892040c70ec5d411cfcb86e226748d72f7965 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:17:23 +0000 Subject: [PATCH 6/8] # YoinkGUI v1.8.0 ### Released January 05, 2026 ### Features - New configurable toggle to enable or disable the single-item keybind. - By pressing Y, while hovering over an item will yoink only that specific item. - Added `/yoinkgui config` command to open the configuration GUI. ### Code changes - Introduced `Key` interface to simplify keybind creation. - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. - Made main client class less cluttered by logic into separate classes. - --- .../mixin/client/HandledScreenAccessor.java | 15 ++++ .../mixin/client/ItemStackMixin.java | 36 --------- .../mixin/client/ScreenMixin.java | 34 +++++++- .../kotlin/me/thatonedevil/NBTParser.kt | 15 ++-- .../kotlin/me/thatonedevil/YoinkGUIClient.kt | 79 ++----------------- .../thatonedevil/gui/ButtonPositionScreen.kt | 10 +-- .../me/thatonedevil/gui/ChangelogScreen.kt | 8 +- .../thatonedevil/handlers/ItemParseHandler.kt | 70 ++++++++++++++++ .../handlers/KeyboardEventHandler.kt | 37 +++++++++ .../handlers/ParseButtonHandler.kt | 38 +++++++++ .../thatonedevil/inventory/YoinkInventory.kt | 19 +++-- .../me/thatonedevil/keybinds/MenuKeybind.kt | 8 +- .../keybinds/YoinkSingleKeybind.kt | 10 ++- .../resources/yoinkgui.client.mixins.json | 2 +- src/main/resources/changelogs/1.8.0.md | 6 +- 15 files changed, 237 insertions(+), 150 deletions(-) create mode 100644 src/client/java/me/thatonedevil/mixin/client/HandledScreenAccessor.java delete mode 100644 src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java create mode 100644 src/client/kotlin/me/thatonedevil/handlers/ItemParseHandler.kt create mode 100644 src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt create mode 100644 src/client/kotlin/me/thatonedevil/handlers/ParseButtonHandler.kt diff --git a/src/client/java/me/thatonedevil/mixin/client/HandledScreenAccessor.java b/src/client/java/me/thatonedevil/mixin/client/HandledScreenAccessor.java new file mode 100644 index 0000000..2dae2f8 --- /dev/null +++ b/src/client/java/me/thatonedevil/mixin/client/HandledScreenAccessor.java @@ -0,0 +1,15 @@ +package me.thatonedevil.mixin.client; + +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.screen.slot.Slot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(HandledScreen.class) +public interface HandledScreenAccessor { + + @Accessor("focusedSlot") + Slot yoinkgui$getFocusedSlot(); +} + + diff --git a/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java b/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java deleted file mode 100644 index 8b54209..0000000 --- a/src/client/java/me/thatonedevil/mixin/client/ItemStackMixin.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.thatonedevil.mixin.client; - -import me.thatonedevil.YoinkGUIClient; -import me.thatonedevil.config.YoinkGuiSettings; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.tooltip.TooltipType; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; - -@Mixin(ItemStack.class) -public class ItemStackMixin { - - @Inject(method = "getTooltip", at = @At("RETURN")) - private void onGetTooltip(Item.TooltipContext context, PlayerEntity player, TooltipType type, CallbackInfoReturnable> cir) { - List tooltip = cir.getReturnValue(); - YoinkGuiSettings config = YoinkGUIClient.getYoinkGuiSettings(); - - if (!config.getEnableSingleItemYoink().get()) { - return; - } - - tooltip.add(Text.literal("")); - - tooltip.add(Text.literal("§ePress Y to yoink item")); - - YoinkGUIClient.setHoveredItemStack((ItemStack)(Object)this); - } -} - diff --git a/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java b/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java index 6def714..a9e1f1c 100644 --- a/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java +++ b/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java @@ -2,14 +2,21 @@ import me.thatonedevil.YoinkGUIClient; import me.thatonedevil.config.YoinkGuiSettings; +import me.thatonedevil.handlers.ParseButtonHandler; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.*; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.ArrayList; +import java.util.List; import static net.minecraft.text.Text.literal; @@ -52,19 +59,40 @@ private void render(DrawContext context, int mouseX, int mouseY, float deltaTick int mouseXUi = (int) (client.mouse.getX() * scaledWidth / client.getWindow().getWidth()); int mouseYUi = (int) (client.mouse.getY() * scaledHeight / client.getWindow().getHeight()); - YoinkGUIClient.INSTANCE.setParseButtonHovered(mouseXUi >= parseButtonX && mouseXUi <= parseButtonX + parseButtonWidth && + ParseButtonHandler.INSTANCE.setParseButtonHovered(mouseXUi >= parseButtonX && mouseXUi <= parseButtonX + parseButtonWidth && mouseYUi >= parseButtonY && mouseYUi <= parseButtonY + parseButtonHeight); - int parseBgColor = YoinkGUIClient.INSTANCE.getParseButtonHovered() ? 0xAA444 : 0xAA000; + int parseBgColor = ParseButtonHandler.INSTANCE.getParseButtonHovered() ? 0xAA444444 : 0xAA000000; context.fill(parseButtonX, parseButtonY, parseButtonX + parseButtonWidth, parseButtonY + parseButtonHeight, parseBgColor); context.drawCenteredTextWithShadow( client.textRenderer, literal(parseButtonText), parseButtonX + parseButtonWidth / 2, parseButtonY + (parseButtonHeight - 8) / 2, - 0xFFFFF + 0xFFFFFFFF ); + } + + @Inject(method = "getTooltipFromItem", at = @At("RETURN"), cancellable = true) + private static void onGetTooltipFromItem(MinecraftClient client, ItemStack stack, CallbackInfoReturnable> cir) { + if (!(client.currentScreen instanceof HandledScreen)) { + return; + } + + YoinkGuiSettings config = YoinkGUIClient.getYoinkGuiSettings(); + + if (!config.getEnableSingleItemYoink().get()) { + return; + } + + List originalTooltip = cir.getReturnValue(); + + List modifiedTooltip = new ArrayList<>(originalTooltip); + + modifiedTooltip.add(literal("")); + modifiedTooltip.add(literal("§ePress Y to Yoink item")); + cir.setReturnValue(modifiedTooltip); } } \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/NBTParser.kt b/src/client/kotlin/me/thatonedevil/NBTParser.kt index daafe2a..c1d72d5 100644 --- a/src/client/kotlin/me/thatonedevil/NBTParser.kt +++ b/src/client/kotlin/me/thatonedevil/NBTParser.kt @@ -76,15 +76,14 @@ object NBTParser { } private suspend fun saveNbtFile( - configDir: File, - subDir: String, + configDir: String, rawItems: List ) = withContext(Dispatchers.IO) { val start = LocalDateTime.now() val formattedTime = start.format(DateTimeFormatter.ofPattern("MM-dd HH-mm-ss")) try { - val yoinkDir = File(configDir, subDir).apply { mkdirs() } + val yoinkDir = File(configDir).apply { mkdirs() } val file = File(yoinkDir, "${serverName}-${formattedTime}.txt") FileWriter(file).use { writer -> @@ -131,22 +130,20 @@ object NBTParser { suspend fun saveFormattedNBTToFile( nbtList: List, - configDir: File + configDir: String ) { saveNbtFile( - configDir, - "assets/yoinkgui", + "${configDir}/yoinkgui", nbtList ) } suspend fun saveSingleItem( rawNbt: String, - configDir: File + configDir: String ) { saveNbtFile( - configDir, - "assets/items/yoinkgui", + "${configDir}/yoinkgui/items", listOf(rawNbt) ) } diff --git a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt index bcd62f8..1bc1b08 100644 --- a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt +++ b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt @@ -1,31 +1,17 @@ package me.thatonedevil -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import me.thatonedevil.commands.YoinkGuiClientCommandRegistry import me.thatonedevil.config.YoinkGuiSettings -import me.thatonedevil.gui.ButtonPositionScreen -import me.thatonedevil.inventory.TopInventory -import me.thatonedevil.inventory.YoinkInventory +import me.thatonedevil.handlers.KeyboardEventHandler +import me.thatonedevil.handlers.ParseButtonHandler import me.thatonedevil.keybinds.KeybindManager -import me.thatonedevil.utils.LatestErrorLog -import me.thatonedevil.utils.Utils.sendChat -import me.thatonedevil.utils.Utils.toClickCopy import me.thatonedevil.utils.api.UpdateChecker import net.fabricmc.api.ClientModInitializer -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents -import net.minecraft.client.MinecraftClient -import net.minecraft.item.ItemStack -import org.lwjgl.glfw.GLFW import org.slf4j.Logger import org.slf4j.LoggerFactory -object YoinkGUIClient : ClientModInitializer { - var parseButtonHovered = false - var wasLeftClicking = false - private var hoveredItemStack: ItemStack? = null +object YoinkGUIClient : ClientModInitializer { val logger: Logger = LoggerFactory.getLogger(BuildConfig.MOD_ID) @JvmStatic @@ -38,61 +24,8 @@ object YoinkGUIClient : ClientModInitializer { yoinkGuiSettings // Load settings on client init - // Button click logic. - ClientTickEvents.END_CLIENT_TICK.register { client -> - - val isLeftClicking = GLFW.glfwGetMouseButton(client.window.handle, GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS - - if (client.currentScreen == null) { - return@register - } - if (client.currentScreen is ButtonPositionScreen) { - return@register - } - - if (client.player != null && isLeftClicking && !wasLeftClicking) { - when { - parseButtonHovered -> handleParseButton(client) - } - } - - wasLeftClicking = isLeftClicking - } - } - - fun handleParseButton(client: MinecraftClient) { - CoroutineScope(Dispatchers.IO).launch { - try { - val player = client.player ?: return@launch - val configDir = client.runDirectory.resolve("config") - val yoinkInventory = YoinkInventory(player, TopInventory(client)) - val yoinkedItems = yoinkInventory.apply { yoinkItems() }.getYoinkedItems().map { it.toString() } - - if (yoinkedItems.isEmpty()) { - sendChat("Inventory is empty!") - return@launch - } - - NBTParser.saveFormattedNBTToFile(yoinkedItems, configDir) - - } catch (e: Exception) { - LatestErrorLog.record(e, "Error during NBT parsing") - sendChat("Error during NBT parsing: ${e.message} &7&o(Report on github, Click to copy)".toClickCopy(e.message.toString())) - logger.error("Error during NBT parsing: ${e.stackTraceToString()}") - - } - } - } - - //temporary - @JvmStatic - fun getHoveredItemStack(): ItemStack? { - return hoveredItemStack - } - - - @JvmStatic - fun setHoveredItemStack(itemStack: ItemStack?) { - hoveredItemStack = itemStack + // Register event handlers + ParseButtonHandler.register() + KeyboardEventHandler.register() } } \ No newline at end of file diff --git a/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt b/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt index 1ea7417..945a5ec 100644 --- a/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt +++ b/src/client/kotlin/me/thatonedevil/gui/ButtonPositionScreen.kt @@ -75,7 +75,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P buttonY = (mouseY - dragOffsetY).coerceIn(0, height - scaledButtonHeight) } - val buttonColor = if (isMouseOverButton(mouseX, mouseY)) 0xAA444 else 0xAA000 + val buttonColor = if (isMouseOverButton(mouseX, mouseY)) 0xAA444444.toInt() else 0xAA000000.toInt() context.fill( buttonX, buttonY, @@ -88,7 +88,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Yoink and Parse NBT into file"), buttonX + scaledButtonWidth / 2, buttonY + (scaledButtonHeight - 8) / 2, - 0xFFFFF + 0xFFFFFFFF.toInt() ) context.drawCenteredTextWithShadow( @@ -96,7 +96,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Drag the button to reposition it"), width / 2, 20, - 0xFFFFF + 0xFFFFFFFF.toInt() ) context.drawCenteredTextWithShadow( @@ -104,7 +104,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Use mouse wheel to scale (Current: ${"%.2f".format(scaleFactor)}x)"), width / 2, 35, - 0xFFFFF + 0xFFFFFFFF.toInt() ) context.drawCenteredTextWithShadow( @@ -112,7 +112,7 @@ class ButtonPositionScreen(private val parent: Screen?) : Screen(Text.literal("P Text.literal("Press ESC or ENTER to save and exit"), width / 2, 50, - 0xFFFFF + 0xFFFFFFFF.toInt() ) } diff --git a/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt b/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt index bc43152..bf19819 100644 --- a/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt +++ b/src/client/kotlin/me/thatonedevil/gui/ChangelogScreen.kt @@ -60,7 +60,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("No changelog available."), centerX, height / 2, - 0xFFE0E + 0xFFE0E0E0.toInt() ) return } @@ -79,7 +79,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change line, centerX, y, - 0xFFE0E + 0xFFE0E0E0.toInt() ) } y += lineHeight @@ -92,7 +92,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("Press ESC to close"), centerX, height - 20, - 0xFFAAA + 0xFFAAAAAA.toInt() ) if (maxScroll > 0) { @@ -102,7 +102,7 @@ class ChangelogScreen(private val parent: Screen?) : Screen(Text.literal("Change Text.literal("↕ Scroll: $scrollPercentage%"), centerX, height - 10, - 0xFF888 + 0xFF888888.toInt() ) } } diff --git a/src/client/kotlin/me/thatonedevil/handlers/ItemParseHandler.kt b/src/client/kotlin/me/thatonedevil/handlers/ItemParseHandler.kt new file mode 100644 index 0000000..c900770 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/handlers/ItemParseHandler.kt @@ -0,0 +1,70 @@ +package me.thatonedevil.handlers + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import me.thatonedevil.NBTParser +import me.thatonedevil.YoinkGUIClient +import me.thatonedevil.inventory.TopInventory +import me.thatonedevil.inventory.YoinkInventory +import me.thatonedevil.inventory.YoinkInventory.Companion.yoinkSingleItem +import me.thatonedevil.utils.LatestErrorLog +import me.thatonedevil.utils.Utils.sendChat +import me.thatonedevil.utils.Utils.toClickCopy +import net.minecraft.client.MinecraftClient +import net.minecraft.item.ItemStack + +object ItemParseHandler { + + fun handleSingleItemParse(itemStack: ItemStack?) { + CoroutineScope(Dispatchers.IO).launch { + try { + val client = MinecraftClient.getInstance() + val configDir = client.runDirectory.resolve("config") + + if (itemStack == null) { + sendChat("No item hovered!") + return@launch + } + + val nbtString = yoinkSingleItem(client.player!!, itemStack) + if (nbtString == null) { + sendChat("Failed to parse item NBT!") + YoinkGUIClient.logger.error("Error during NBT parsing: NBT string is null") + return@launch + } + + NBTParser.saveSingleItem(nbtString, configDir.path) + + } catch (e: Exception) { + LatestErrorLog.record(e, "Error during single item NBT parsing") + sendChat("Error during single item NBT parsing: ${e.message} &7&o(Report on github, Click to copy)".toClickCopy(e.message.toString())) + YoinkGUIClient.logger.error("Error during single item NBT parsing: ${e.stackTraceToString()}") + } + } + } + + fun handleParseButton(client: MinecraftClient) { + CoroutineScope(Dispatchers.IO).launch { + try { + val player = client.player ?: return@launch + val configDir = client.runDirectory.resolve("config").path + val yoinkInventory = YoinkInventory(player, TopInventory(client)) + val yoinkedItems = yoinkInventory.apply { yoinkItems() }.getYoinkedItems().map { it.toString() } + + if (yoinkedItems.isEmpty()) { + sendChat("Inventory is empty!") + return@launch + } + + NBTParser.saveFormattedNBTToFile(yoinkedItems, configDir) + + } catch (e: Exception) { + LatestErrorLog.record(e, "Error during NBT parsing") + sendChat("Error during NBT parsing: ${e.message} &7&o(Report on github, Click to copy)".toClickCopy(e.message.toString())) + YoinkGUIClient.logger.error("Error during NBT parsing: ${e.stackTraceToString()}") + } + } + } +} + diff --git a/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt new file mode 100644 index 0000000..25ffbd3 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt @@ -0,0 +1,37 @@ +package me.thatonedevil.handlers + +import me.thatonedevil.mixin.client.HandledScreenAccessor +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents +import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents +import net.minecraft.client.gui.screen.ingame.* +import net.minecraft.client.util.InputUtil +import net.minecraft.screen.slot.Slot + +object KeyboardEventHandler { + + fun register() { + ScreenEvents.AFTER_INIT.register { _, screen, _, _ -> + ScreenKeyboardEvents.afterKeyPress(screen).register { screen, keyInput -> + if (!isValidInventoryScreen(screen)) { + return@register + } + + if (keyInput.key == InputUtil.GLFW_KEY_Y) { + val slot: Slot? = (screen as HandledScreenAccessor).`yoinkgui$getFocusedSlot`() + if (slot != null && !slot.stack.isEmpty) { + ItemParseHandler.handleSingleItemParse(slot.stack) + } + } + } + } + } + + private fun isValidInventoryScreen(screen: Any): Boolean { + return screen is InventoryScreen + || screen is GenericContainerScreen + || screen is MerchantScreen + || screen is CreativeInventoryScreen + || screen is ShulkerBoxScreen + } +} + diff --git a/src/client/kotlin/me/thatonedevil/handlers/ParseButtonHandler.kt b/src/client/kotlin/me/thatonedevil/handlers/ParseButtonHandler.kt new file mode 100644 index 0000000..fd1ac77 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/handlers/ParseButtonHandler.kt @@ -0,0 +1,38 @@ +package me.thatonedevil.handlers + +import me.thatonedevil.gui.ButtonPositionScreen +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import org.lwjgl.glfw.GLFW + + +object ParseButtonHandler { + var parseButtonHovered = false + private var wasLeftClicking = false + + fun register() { + ClientTickEvents.END_CLIENT_TICK.register { client -> + if (client.currentScreen == null) { + parseButtonHovered = false + wasLeftClicking = false + return@register + } + + if (client.currentScreen is ButtonPositionScreen) { + parseButtonHovered = false + return@register + } + + val isLeftClicking = GLFW.glfwGetMouseButton(client.window.handle, GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS + + if (client.player != null && isLeftClicking && !wasLeftClicking) { + if (parseButtonHovered) { + ItemParseHandler.handleParseButton(client) + } + } + + wasLeftClicking = isLeftClicking + } + } +} + + diff --git a/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt b/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt index 9c78669..26b8c5d 100644 --- a/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt +++ b/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt @@ -29,16 +29,19 @@ class YoinkInventory(private val player: ClientPlayerEntity, private val invento } - fun yoinkSingleItem(itemStack: ItemStack): String? { - if (topInventory == null || itemStack.isEmpty) { - sendChat("No inventory found or item is empty!") - return null - } + companion object { + fun yoinkSingleItem(player: ClientPlayerEntity, itemStack: ItemStack): String? { + if (itemStack.isEmpty) { + sendChat("No inventory found or item is empty!") + return null + } - val registryOps = player.registryManager.getOps(NbtOps.INSTANCE) - val encodeResult = ItemStack.CODEC.encodeStart(registryOps, itemStack) - return encodeResult.result().get().toString() + val registryOps = player.registryManager.getOps(NbtOps.INSTANCE) + val encodeResult = ItemStack.CODEC.encodeStart(registryOps, itemStack) + return encodeResult.result().get().toString() + } } + fun getYoinkedItems(): List = encodedItems } diff --git a/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt index f5db9e2..e3ae078 100644 --- a/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt +++ b/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt @@ -1,6 +1,8 @@ package me.thatonedevil.keybinds import me.thatonedevil.gui.ButtonPositionScreen +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents +import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents import net.minecraft.client.MinecraftClient import org.lwjgl.glfw.GLFW @@ -17,12 +19,6 @@ class MenuKeybind : Key{ override fun whenPressed() { val client = MinecraftClient.getInstance() - // Close screen when button pressed again while in ButtonPositionScreen - if (client.currentScreen is ButtonPositionScreen) { - client.setScreen(null) - return - } - client.setScreen(ButtonPositionScreen(client.currentScreen)) } diff --git a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt index b28f2ef..4fd7b5f 100644 --- a/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt +++ b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt @@ -1,6 +1,12 @@ package me.thatonedevil.keybinds import me.thatonedevil.utils.Utils +import me.thatonedevil.utils.Utils.sendChat +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents +import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.util.InputUtil import org.lwjgl.glfw.GLFW class YoinkSingleKeybind : Key{ @@ -13,8 +19,6 @@ class YoinkSingleKeybind : Key{ return GLFW.GLFW_KEY_Y } - override fun whenPressed() { - Utils.sendChat("/yoink single keybind pressed") - } + override fun whenPressed() {} } \ No newline at end of file diff --git a/src/client/resources/yoinkgui.client.mixins.json b/src/client/resources/yoinkgui.client.mixins.json index cbb51f5..473f33e 100644 --- a/src/client/resources/yoinkgui.client.mixins.json +++ b/src/client/resources/yoinkgui.client.mixins.json @@ -4,7 +4,7 @@ "compatibilityLevel": "JAVA_21", "client": [ "ScreenMixin", - "ItemStackMixin" + "HandledScreenAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/changelogs/1.8.0.md b/src/main/resources/changelogs/1.8.0.md index 6a8e634..7416c9a 100644 --- a/src/main/resources/changelogs/1.8.0.md +++ b/src/main/resources/changelogs/1.8.0.md @@ -2,12 +2,14 @@ ### Released January 05, 2026 ### Features -- Reworked keybind system with a dedicated single-item "yoink" key. - New configurable toggle to enable or disable the single-item keybind. +- By pressing Y, while hovering over an item will yoink only that specific item. +- Added `/yoinkgui config` command to open the configuration GUI. ### Code changes - Introduced `Key` interface to simplify keybind creation. - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. -- Added `/yoinkgui config` command to open the configuration GUI. \ No newline at end of file +- Made main client class less cluttered by logic into separate classes. +- \ No newline at end of file From 80c1e3c630ff61ac27076a89285c30a022fd9270 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:19:22 +0000 Subject: [PATCH 7/8] Made the single item not work when feature is off in config menu. --- .../kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt index 25ffbd3..ecf7845 100644 --- a/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt +++ b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt @@ -1,5 +1,6 @@ package me.thatonedevil.handlers +import me.thatonedevil.YoinkGUIClient.yoinkGuiSettings import me.thatonedevil.mixin.client.HandledScreenAccessor import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents @@ -16,6 +17,10 @@ object KeyboardEventHandler { return@register } + if (!yoinkGuiSettings.enableSingleItemYoink.get()) { + return@register + } + if (keyInput.key == InputUtil.GLFW_KEY_Y) { val slot: Slot? = (screen as HandledScreenAccessor).`yoinkgui$getFocusedSlot`() if (slot != null && !slot.stack.isEmpty) { From 45c6e054e43de2bb9fdbdda42ab7ee4a87f2f999 Mon Sep 17 00:00:00 2001 From: ThatOneDevil <62218606+ThatOneDevil@users.noreply.github.com> Date: Tue, 6 Jan 2026 13:28:08 +0000 Subject: [PATCH 8/8] Fixed multiversion build --- .../handlers/KeyboardEventHandler.kt | 24 +++++++++++++++++++ src/main/resources/changelogs/1.8.0.md | 3 +-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt index ecf7845..998c3f5 100644 --- a/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt +++ b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt @@ -11,6 +11,7 @@ import net.minecraft.screen.slot.Slot object KeyboardEventHandler { fun register() { + //? if >=1.21.9 { ScreenEvents.AFTER_INIT.register { _, screen, _, _ -> ScreenKeyboardEvents.afterKeyPress(screen).register { screen, keyInput -> if (!isValidInventoryScreen(screen)) { @@ -29,8 +30,31 @@ object KeyboardEventHandler { } } } + //? } else { + /*ScreenEvents.AFTER_INIT.register { _, screen, _, _ -> + ScreenKeyboardEvents.afterKeyPress(screen).register { screen, key, _, _ -> + if (!isValidInventoryScreen(screen)) { + return@register + } + + if (!yoinkGuiSettings.enableSingleItemYoink.get()) { + return@register + } + + if (key == InputUtil.GLFW_KEY_Y) { + val slot: Slot? = (screen as HandledScreenAccessor).`yoinkgui$getFocusedSlot`() + if (slot != null && !slot.stack.isEmpty) { + ItemParseHandler.handleSingleItemParse(slot.stack) + } + } + } + + }*/ + //?} } + + private fun isValidInventoryScreen(screen: Any): Boolean { return screen is InventoryScreen || screen is GenericContainerScreen diff --git a/src/main/resources/changelogs/1.8.0.md b/src/main/resources/changelogs/1.8.0.md index 7416c9a..2c644f2 100644 --- a/src/main/resources/changelogs/1.8.0.md +++ b/src/main/resources/changelogs/1.8.0.md @@ -11,5 +11,4 @@ - Added `KeybindManager` for centralized registration and management of keybinds. - Removed redundant logic from `YoinkGuiClient`. - Added `SingleItemKeybind` implementation for the new key. -- Made main client class less cluttered by logic into separate classes. -- \ No newline at end of file +- Made main client class less cluttered by logic into separate classes. \ No newline at end of file