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 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/ScreenMixin.java b/src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java index 2d3b782..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,10 +59,10 @@ 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() ? 0xAA444444 : 0xAA000000; + int parseBgColor = ParseButtonHandler.INSTANCE.getParseButtonHovered() ? 0xAA444444 : 0xAA000000; context.fill(parseButtonX, parseButtonY, parseButtonX + parseButtonWidth, parseButtonY + parseButtonHeight, parseBgColor); context.drawCenteredTextWithShadow( client.textRenderer, @@ -64,7 +71,28 @@ private void render(DrawContext context, int mouseX, int mouseY, float deltaTick parseButtonY + (parseButtonHeight - 8) / 2, 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 d61c2fd..c1d72d5 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,42 @@ object NBTParser { } } - - suspend fun saveFormattedNBTToFile(nbtList: List, configDir: File) = withContext(Dispatchers.IO) { + private suspend fun saveNbtFile( + 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, "assets/yoinkgui").apply { mkdirs() } - val fileName = "${serverName}-${formattedTime}.txt" - val file = File(yoinkDir, fileName) + val yoinkDir = File(configDir).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 +127,25 @@ object NBTParser { logger.error("Error saving NBT file: ${e.message}", e) } } + + suspend fun saveFormattedNBTToFile( + nbtList: List, + configDir: String + ) { + saveNbtFile( + "${configDir}/yoinkgui", + nbtList + ) + } + + suspend fun saveSingleItem( + rawNbt: String, + configDir: String + ) { + saveNbtFile( + "${configDir}/yoinkgui/items", + listOf(rawNbt) + ) + } + } diff --git a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt index 8724aa3..1bc1b08 100644 --- a/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt +++ b/src/client/kotlin/me/thatonedevil/YoinkGUIClient.kt @@ -1,106 +1,31 @@ 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.utils.LatestErrorLog -import me.thatonedevil.utils.Utils.sendChat -import me.thatonedevil.utils.Utils.toClickCopy +import me.thatonedevil.handlers.KeyboardEventHandler +import me.thatonedevil.handlers.ParseButtonHandler +import me.thatonedevil.keybinds.KeybindManager 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 org.lwjgl.glfw.GLFW import org.slf4j.Logger import org.slf4j.LoggerFactory + object YoinkGUIClient : ClientModInitializer { - var parseButtonHovered = false - private var wasLeftClicking = false 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() { - 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 - - 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 - } - UpdateChecker.setupJoinListener() YoinkGuiClientCommandRegistry.register() - yoinkGuiSettings // Load settings on client init - } - - 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() } + KeybindManager().register() - 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()}") + yoinkGuiSettings // Load settings on client init - } - } + // Register event handlers + ParseButtonHandler.register() + KeyboardEventHandler.register() } } \ No newline at end of file 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 a2751e4..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() @@ -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/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..998c3f5 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/handlers/KeyboardEventHandler.kt @@ -0,0 +1,66 @@ +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 +import net.minecraft.client.gui.screen.ingame.* +import net.minecraft.client.util.InputUtil +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)) { + 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) { + ItemParseHandler.handleSingleItemParse(slot.stack) + } + } + } + } + //? } 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 + || 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 92c93d0..26b8c5d 100644 --- a/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt +++ b/src/client/kotlin/me/thatonedevil/inventory/YoinkInventory.kt @@ -29,5 +29,19 @@ class YoinkInventory(private val player: ClientPlayerEntity, private val invento } + 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() + } + } + + fun getYoinkedItems(): List = encodedItems } 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..e626a33 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/Key.kt @@ -0,0 +1,43 @@ +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) + } + + //? 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/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..e3ae078 --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/MenuKeybind.kt @@ -0,0 +1,25 @@ +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 + +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() + + 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..4fd7b5f --- /dev/null +++ b/src/client/kotlin/me/thatonedevil/keybinds/YoinkSingleKeybind.kt @@ -0,0 +1,24 @@ +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{ + + override fun keyName(): String { + return "key.yoinkgui.yoinksingle" + } + + override fun key(): Int { + return GLFW.GLFW_KEY_Y + } + + 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 2a7d260..473f33e 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", + "HandledScreenAccessor" ], "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" } 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..2c644f2 --- /dev/null +++ b/src/main/resources/changelogs/1.8.0.md @@ -0,0 +1,14 @@ +# 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. \ No newline at end of file