diff --git a/build.gradle b/build.gradle index 8d0e21a..0417fc9 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,20 @@ archivesBaseName = project.archives_base_name version = project.mod_version + "+mc" + project.minecraft_version repositories { + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.terraformersmc.com/releases/" } + + exclusiveContent { + forRepository { + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + } + filter { + includeGroup "maven.modrinth" + } + } } dependencies { @@ -18,6 +32,16 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + + modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config}") { + exclude(group: "net.fabricmc.fabric-api") + } + + modApi "com.terraformersmc:modmenu:${project.modmenu}" + + // only in classpath + modCompileOnly "maven.modrinth:autohud:${project.autohud_version}" } processResources { diff --git a/gradle.properties b/gradle.properties index 330117c..ca13336 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,3 +12,9 @@ loader_version = 0.14.21 # Dependencies fabric_version = 0.85.0+1.20.1 + +# These can be easily found here https://linkie.shedaniel.dev/dependencies +cloth_config = 11.1.106 +modmenu = 7.1.0 + +autohud_version = 6.1+1.20 diff --git a/src/main/java/com/turtlearmymc/slotswap/SwapManager.java b/src/main/java/com/turtlearmymc/slotswap/SwapManager.java index 6740262..b393dea 100644 --- a/src/main/java/com/turtlearmymc/slotswap/SwapManager.java +++ b/src/main/java/com/turtlearmymc/slotswap/SwapManager.java @@ -1,11 +1,17 @@ package com.turtlearmymc.slotswap; +import com.turtlearmymc.slotswap.compat.LoadCompat; +import com.turtlearmymc.slotswap.config.SwapConfig; +import me.shedaniel.autoconfig.AutoConfig; +import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.screen.slot.SlotActionType; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; @@ -16,9 +22,12 @@ public class SwapManager implements ClientModInitializer { public static final int ROW_COUNT = PlayerInventory.MAIN_SIZE / PlayerInventory.getHotbarSize(); private static int currentRow; private static boolean active; + public static SwapConfig CONFIG; private static @Nullable KeyBinding selectSlotKey = null; + private static LoadCompat.Action compatibilityAction; + public static boolean isSelectKeyDown() { if (selectSlotKey != null) { return selectSlotKey.isPressed(); @@ -44,6 +53,7 @@ public static void tryStartSwap() { active = true; currentRow = 0; } + compatibilityAction.invoke(); } public static void cancel() { @@ -54,6 +64,10 @@ public static int getCurrentRow() { return active ? currentRow : 0; } + private static ClientPlayerInteractionManager getInteractionManager() { + return MinecraftClient.getInstance().interactionManager; + } + public static void scroll(PlayerInventory inv, int rowDelta) { tryStartSwap(); final int col = inv.selectedSlot; @@ -69,16 +83,62 @@ public static void scroll(PlayerInventory inv, int rowDelta) { } } + private static void swapSlots(PlayerInventory inv, int slotA, int slotB) { + + // clickSlot has a weird indexing: + // first slot arg is slot index, what is kinda random + // https://videa.hu/videok/film-animacio/laputa-az-egi-palota-124-perc-1986-KZZvhnBm2KnCxTNx?start=1920.560579 + + // second item is normal slot index + + final int slotAIndex = slotA < 9 ? slotA + PlayerInventory.MAIN_SIZE : slotA; + getInteractionManager().clickSlot(0, slotAIndex, slotB, SlotActionType.SWAP, MinecraftClient.getInstance().player); + } + + public static void tryDoSwap(PlayerInventory inv) { if (!active) return; active = false; if (currentRow == 0) return; // Hotbar selected - final int slot = rowColToSlot(currentRow, inv.selectedSlot); - MinecraftClient.getInstance().interactionManager.pickFromInventory(slot); + switch (CONFIG.swapMode) { + case COMPATIBLE -> { + final int slot = rowColToSlot(currentRow, inv.selectedSlot); + getInteractionManager().pickFromInventory(slot); + } + case SWAP -> { + final int slotA = rowColToSlot(currentRow, inv.selectedSlot); + final int slotB = rowColToSlot(0, inv.selectedSlot); + swapSlots(inv, slotA, slotB); + } + case SHIFT -> { + final int column = inv.selectedSlot; + final int distance = currentRow; + + for (int cycleStart = 0, nMoved = 0; nMoved != ROW_COUNT; cycleStart++) { + int i = cycleStart; + int displaced = i; + do { + i += distance; + if (i >= ROW_COUNT) + i -= ROW_COUNT; + swapSlots(inv, rowColToSlot(displaced, column), rowColToSlot(i, column)); + displaced = i; + nMoved++; + } while ((i + distance) % ROW_COUNT != cycleStart); + nMoved++; + } + } + } } @Override public void onInitializeClient() { + + AutoConfig.register(SwapConfig.class, JanksonConfigSerializer::new); + CONFIG = AutoConfig.getConfigHolder(SwapConfig.class).get(); + + compatibilityAction = LoadCompat.getActivationAction(); + selectSlotKey = KeyBindingHelper.registerKeyBinding(new KeyBinding( "key.slotswap.swap", InputUtil.Type.KEYSYM, diff --git a/src/main/java/com/turtlearmymc/slotswap/compat/AutoHudCompatibility.java b/src/main/java/com/turtlearmymc/slotswap/compat/AutoHudCompatibility.java new file mode 100644 index 0000000..f8cf6ec --- /dev/null +++ b/src/main/java/com/turtlearmymc/slotswap/compat/AutoHudCompatibility.java @@ -0,0 +1,11 @@ +package com.turtlearmymc.slotswap.compat; + +import mod.crend.autohud.component.Component; + +public class AutoHudCompatibility implements LoadCompat.Action { + + @Override + public void invoke() { + Component.Hotbar.reveal(); + } +} diff --git a/src/main/java/com/turtlearmymc/slotswap/compat/LoadCompat.java b/src/main/java/com/turtlearmymc/slotswap/compat/LoadCompat.java new file mode 100644 index 0000000..3f7139f --- /dev/null +++ b/src/main/java/com/turtlearmymc/slotswap/compat/LoadCompat.java @@ -0,0 +1,28 @@ +package com.turtlearmymc.slotswap.compat; + +import net.fabricmc.loader.api.FabricLoader; +import org.jetbrains.annotations.NotNull; + +public class LoadCompat { + + + @NotNull + public static Action getActivationAction() { + + return getAutoHudAction(); + } + + private static Action getAutoHudAction() { + if (FabricLoader.getInstance().isModLoaded("autohud")) { + return new AutoHudCompatibility(); + } else { + return () -> {}; + } + } + + + @FunctionalInterface + public interface Action { + void invoke(); + } +} diff --git a/src/main/java/com/turtlearmymc/slotswap/config/ModMenuEntry.java b/src/main/java/com/turtlearmymc/slotswap/config/ModMenuEntry.java new file mode 100644 index 0000000..04b1b2b --- /dev/null +++ b/src/main/java/com/turtlearmymc/slotswap/config/ModMenuEntry.java @@ -0,0 +1,12 @@ +package com.turtlearmymc.slotswap.config; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import me.shedaniel.autoconfig.AutoConfig; + +public class ModMenuEntry implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return parent -> AutoConfig.getConfigScreen(SwapConfig.class, parent).get(); + } +} diff --git a/src/main/java/com/turtlearmymc/slotswap/config/SwapConfig.java b/src/main/java/com/turtlearmymc/slotswap/config/SwapConfig.java new file mode 100644 index 0000000..92c8830 --- /dev/null +++ b/src/main/java/com/turtlearmymc/slotswap/config/SwapConfig.java @@ -0,0 +1,29 @@ +package com.turtlearmymc.slotswap.config; + +import me.shedaniel.autoconfig.ConfigData; +import me.shedaniel.autoconfig.annotation.Config; +import me.shedaniel.autoconfig.annotation.ConfigEntry; + +@Config(name = "slotswap") +public class SwapConfig implements ConfigData { + + @ConfigEntry.Gui.Tooltip + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + public SwapMode swapMode = SwapMode.COMPATIBLE; + + public enum SwapMode { + /** + * Swap items using vanilla pick-item packet, likely safe to use everywhere but having issues + */ + COMPATIBLE, + + /** + * Use PlayerHandler swap packet, precisely swaps the items, may trigger anti-cheat + */ + SWAP, + /** + * Use same method as SWAP, but instead of swapping items, it cyclic-shift the column, it shouldn't shuffle items in inventory. + */ + SHIFT, + } +} diff --git a/src/main/resources/assets/slotswap/lang/en_US.json b/src/main/resources/assets/slotswap/lang/en_US.json deleted file mode 100644 index 9a730ea..0000000 --- a/src/main/resources/assets/slotswap/lang/en_US.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key.slotswap.swap": "Slot swap" -} \ No newline at end of file diff --git a/src/main/resources/assets/slotswap/lang/en_us.json b/src/main/resources/assets/slotswap/lang/en_us.json new file mode 100644 index 0000000..141229e --- /dev/null +++ b/src/main/resources/assets/slotswap/lang/en_us.json @@ -0,0 +1,5 @@ +{ + "key.slotswap.swap": "Slot swap", + "text.autoconfig.slotswap.option.swapMode": "Swap mode", + "text.autoconfig.slotswap.option.swapMode.@Tooltip": "COMPATIBLE: Item swap with item pick packet, it may swap items randomly but is unlikely to trigger anti-cheat\nSWAP: Simply swap the item with the selected\nSHIFT: Shift the whole column up/down when swapping items" +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index ab8858c..4c78087 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -20,6 +20,9 @@ "entrypoints": { "client": [ "com.turtlearmymc.slotswap.SwapManager" + ], + "modmenu": [ + "com.turtlearmymc.slotswap.config.ModMenuEntry" ] }, "mixins": [