diff --git a/src/main/java/org/uiutils/GithubRelease.java b/src/main/java/org/uiutils/GithubRelease.java new file mode 100644 index 0000000..afb56a6 --- /dev/null +++ b/src/main/java/org/uiutils/GithubRelease.java @@ -0,0 +1,12 @@ +package org.uiutils; + +import com.google.gson.annotations.SerializedName; + +public class GithubRelease { + @SerializedName("tag_name") + private String tagName; + + public String getTagName() { + return tagName; + } +} diff --git a/src/main/java/org/uiutils/MainClient.java b/src/main/java/org/uiutils/MainClient.java new file mode 100644 index 0000000..344549c --- /dev/null +++ b/src/main/java/org/uiutils/MainClient.java @@ -0,0 +1,537 @@ +package org.uiutils; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +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.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; +import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.uiutils.mixin.accessor.ClientConnectionAccessor; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.StringSelection; +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; +import java.util.Vector; + +public class MainClient implements ClientModInitializer { + public static Font monospace; + public static Color darkWhite; + + public static KeyBinding restoreScreenKey; + + public static Logger LOGGER = LoggerFactory.getLogger("ui-utils"); + public static MinecraftClient mc = MinecraftClient.getInstance(); + @Override + public void onInitializeClient() { + UpdateUtils.checkForUpdates(); + + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac") || os.contains("darwin") || os.contains("osx")) { + SharedVariables.isMac = true; + } + + // register "restore screen" key + restoreScreenKey = KeyBindingHelper.registerKeyBinding(new KeyBinding("Restore Screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_V, "UI Utils")); + + // register event for END_CLIENT_TICK + ClientTickEvents.END_CLIENT_TICK.register((client) -> { + // detect if the "restore screen" keybinding is pressed + while (restoreScreenKey.wasPressed()) { + if (SharedVariables.storedScreen != null && SharedVariables.storedScreenHandler != null && client.player != null) { + client.setScreen(SharedVariables.storedScreen); + client.player.currentScreenHandler = SharedVariables.storedScreenHandler; + } + } + }); + + // set java.awt.headless to false if os is not mac (allows for JFrame guis to be used) + if (!SharedVariables.isMac) { + System.setProperty("java.awt.headless", "false"); + monospace = new Font(Font.MONOSPACED, Font.PLAIN, 10); + darkWhite = new Color(220, 220, 220); + } + } + + @SuppressWarnings("all") + public static void createText(MinecraftClient mc, DrawContext context, TextRenderer textRenderer) { + // display the current gui's sync id, revision + context.drawText(textRenderer, "Sync Id: " + mc.player.currentScreenHandler.syncId, 200, 5, Color.WHITE.getRGB(), false); + context.drawText(textRenderer, "Revision: " + mc.player.currentScreenHandler.getRevision(), 200, 35, Color.WHITE.getRGB(), false); + } + + // bro are you ever going to clean this up? + // this code is very messy, ill clean it up if you dont + // -- MrBreakNFix + public static void createWidgets(MinecraftClient mc, Screen screen) { + // register "close without packet" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { + // closes the current gui without sending a packet to the current server + mc.setScreen(null); + }).width(115).position(5, 5).build()); + + // register "de-sync" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("De-sync"), (button) -> { + // keeps the current gui open client-side and closed server-side + if (mc.getNetworkHandler() != null && mc.player != null) { + mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId)); + } else { + LOGGER.warn("Minecraft network handler or player was null while using 'De-sync' in UI Utils."); + } + }).width(115).position(5, 35).build()); + + // register "send packets" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("Send packets: " + SharedVariables.sendUIPackets), (button) -> { + // tells the client if it should send any gui related packets + SharedVariables.sendUIPackets = !SharedVariables.sendUIPackets; + button.setMessage(Text.of("Send packets: " + SharedVariables.sendUIPackets)); + }).width(115).position(5, 65).build()); + + // register "delay packets" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("Delay packets: " + SharedVariables.delayUIPackets), (button) -> { + // toggles a setting to delay all gui related packets to be used later when turning this setting off + SharedVariables.delayUIPackets = !SharedVariables.delayUIPackets; + button.setMessage(Text.of("Delay packets: " + SharedVariables.delayUIPackets)); + if (!SharedVariables.delayUIPackets && !SharedVariables.delayedUIPackets.isEmpty() && mc.getNetworkHandler() != null) { + for (Packet packet : SharedVariables.delayedUIPackets) { + mc.getNetworkHandler().sendPacket(packet); + } + if (mc.player != null) { + mc.player.sendMessage(Text.of("Sent " + SharedVariables.delayedUIPackets.size() + " packets.")); + } + SharedVariables.delayedUIPackets.clear(); + } + }).width(115).position(5, 95).build()); + + // register "save gui" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("Save GUI"), (button) -> { + // saves the current gui to a variable to be accessed later + if (mc.player != null) { + SharedVariables.storedScreen = mc.currentScreen; + SharedVariables.storedScreenHandler = mc.player.currentScreenHandler; + } + }).width(115).position(5, 125).build()); + + // register "disconnect and send packets" button in all HandledScreens + screen.addDrawableChild(ButtonWidget.builder(Text.of("Disconnect and send packets"), (button) -> { + // sends all "delayed" gui related packets before disconnecting, use: potential race conditions on non-vanilla servers + SharedVariables.delayUIPackets = false; + if (mc.getNetworkHandler() != null) { + for (Packet packet : SharedVariables.delayedUIPackets) { + mc.getNetworkHandler().sendPacket(packet); + } + mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); + } else { + LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while client is disconnecting."); + } + SharedVariables.delayedUIPackets.clear(); + }).width(160).position(5, 155).build()); + + // register "fabricate packet" button in all HandledScreens + ButtonWidget fabricatePacketButton = ButtonWidget.builder(Text.of("Fabricate packet"), (button) -> { + // creates a gui allowing you to fabricate packets + + JFrame frame = new JFrame("Choose Packet"); + frame.setBounds(0, 0, 450, 100); + frame.setResizable(false); + frame.setLocationRelativeTo(null); + frame.setLayout(null); + + JButton clickSlotButton = getPacketOptionButton("Click Slot"); + clickSlotButton.setBounds(100, 25, 110, 20); + clickSlotButton.addActionListener((event) -> { + // im too lazy to comment everything here just read the code yourself + frame.setVisible(false); + + JFrame clickSlotFrame = new JFrame("Click Slot Packet"); + clickSlotFrame.setBounds(0, 0, 450, 300); + clickSlotFrame.setResizable(false); + clickSlotFrame.setLocationRelativeTo(null); + clickSlotFrame.setLayout(null); + + JLabel syncIdLabel = new JLabel("Sync Id:"); + syncIdLabel.setFocusable(false); + syncIdLabel.setFont(monospace); + syncIdLabel.setBounds(25, 25, 100, 20); + + JLabel revisionLabel = new JLabel("Revision:"); + revisionLabel.setFocusable(false); + revisionLabel.setFont(monospace); + revisionLabel.setBounds(25, 50, 100, 20); + + JLabel slotLabel = new JLabel("Slot:"); + slotLabel.setFocusable(false); + slotLabel.setFont(monospace); + slotLabel.setBounds(25, 75, 100, 20); + + JLabel buttonLabel = new JLabel("Button:"); + buttonLabel.setFocusable(false); + buttonLabel.setFont(monospace); + buttonLabel.setBounds(25, 100, 100, 20); + + JLabel actionLabel = new JLabel("Action:"); + actionLabel.setFocusable(false); + actionLabel.setFont(monospace); + actionLabel.setBounds(25, 125, 100, 20); + + JLabel timesToSendLabel = new JLabel("Times to send:"); + timesToSendLabel.setFocusable(false); + timesToSendLabel.setFont(monospace); + timesToSendLabel.setBounds(25, 190, 100, 20); + + JTextField syncIdField = new JTextField(1); + syncIdField.setFont(monospace); + syncIdField.setBounds(125, 25, 100, 20); + + JTextField revisionField = new JTextField(1); + revisionField.setFont(monospace); + revisionField.setBounds(125, 50, 100, 20); + + JTextField slotField = new JTextField(1); + slotField.setFont(monospace); + slotField.setBounds(125, 75, 100, 20); + + JTextField buttonField = new JTextField(1); + buttonField.setFont(monospace); + buttonField.setBounds(125, 100, 100, 20); + + JComboBox actionField = new JComboBox<>(new Vector<>(ImmutableList.of( + "PICKUP", + "QUICK_MOVE", + "SWAP", + "CLONE", + "THROW", + "QUICK_CRAFT", + "PICKUP_ALL" + ))); + actionField.setFocusable(false); + actionField.setEditable(false); + actionField.setBorder(BorderFactory.createEmptyBorder()); + actionField.setBackground(darkWhite); + actionField.setFont(monospace); + actionField.setBounds(125, 125, 100, 20); + + JLabel statusLabel = new JLabel(); + statusLabel.setVisible(false); + statusLabel.setFocusable(false); + statusLabel.setFont(monospace); + statusLabel.setBounds(210, 150, 190, 20); + + JCheckBox delayBox = new JCheckBox("Delay"); + delayBox.setBounds(115, 150, 85, 20); + delayBox.setSelected(false); + delayBox.setFont(monospace); + delayBox.setFocusable(false); + + JTextField timesToSendField = new JTextField("1"); + timesToSendField.setFont(monospace); + timesToSendField.setBounds(125, 190, 100, 20); + + JButton sendButton = new JButton("Send"); + sendButton.setFocusable(false); + sendButton.setBounds(25, 150, 75, 20); + sendButton.setBorder(BorderFactory.createEtchedBorder()); + sendButton.setBackground(darkWhite); + sendButton.setFont(monospace); + sendButton.addActionListener((event0) -> { + if ( + MainClient.isInteger(syncIdField.getText()) && + MainClient.isInteger(revisionField.getText()) && + MainClient.isInteger(slotField.getText()) && + MainClient.isInteger(buttonField.getText()) && + MainClient.isInteger(timesToSendField.getText()) && + actionField.getSelectedItem() != null) { + int syncId = Integer.parseInt(syncIdField.getText()); + int revision = Integer.parseInt(revisionField.getText()); + int slot = Integer.parseInt(slotField.getText()); + int button0 = Integer.parseInt(buttonField.getText()); + SlotActionType action = MainClient.stringToSlotActionType(actionField.getSelectedItem().toString()); + int timesToSend = Integer.parseInt(timesToSendField.getText()); + + if (action != null) { + ClickSlotC2SPacket packet = new ClickSlotC2SPacket(syncId, revision, slot, button0, action, ItemStack.EMPTY, new Int2ObjectArrayMap<>()); + try { + Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); + for (int i = 0; i < timesToSend; i++) { + toRun.run(); + } + } catch (Exception e) { + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("You must be connected to a server!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + return; + } + statusLabel.setVisible(true); + statusLabel.setForeground(Color.GREEN.darker()); + statusLabel.setText("Sent successfully!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid arguments!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + } + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid arguments!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + } + }); + + clickSlotFrame.add(syncIdLabel); + clickSlotFrame.add(revisionLabel); + clickSlotFrame.add(slotLabel); + clickSlotFrame.add(buttonLabel); + clickSlotFrame.add(actionLabel); + clickSlotFrame.add(timesToSendLabel); + clickSlotFrame.add(syncIdField); + clickSlotFrame.add(revisionField); + clickSlotFrame.add(slotField); + clickSlotFrame.add(buttonField); + clickSlotFrame.add(actionField); + clickSlotFrame.add(sendButton); + clickSlotFrame.add(statusLabel); + clickSlotFrame.add(delayBox); + clickSlotFrame.add(timesToSendField); + clickSlotFrame.setVisible(true); + }); + + JButton buttonClickButton = getPacketOptionButton("Button Click"); + buttonClickButton.setBounds(250, 25, 110, 20); + buttonClickButton.addActionListener((event) -> { + frame.setVisible(false); + + JFrame buttonClickFrame = new JFrame("Button Click Packet"); + buttonClickFrame.setBounds(0, 0, 450, 250); + buttonClickFrame.setResizable(false); + buttonClickFrame.setLocationRelativeTo(null); + buttonClickFrame.setLayout(null); + + JLabel syncIdLabel = new JLabel("Sync Id:"); + syncIdLabel.setFocusable(false); + syncIdLabel.setFont(monospace); + syncIdLabel.setBounds(25, 25, 100, 20); + + JLabel buttonIdLabel = new JLabel("Button Id:"); + buttonIdLabel.setFocusable(false); + buttonIdLabel.setFont(monospace); + buttonIdLabel.setBounds(25, 50, 100, 20); + + JTextField syncIdField = new JTextField(1); + syncIdField.setFont(monospace); + syncIdField.setBounds(125, 25, 100, 20); + + JTextField buttonIdField = new JTextField(1); + buttonIdField.setFont(monospace); + buttonIdField.setBounds(125, 50, 100, 20); + + JLabel statusLabel = new JLabel(); + statusLabel.setVisible(false); + statusLabel.setFocusable(false); + statusLabel.setFont(monospace); + statusLabel.setBounds(210, 95, 190, 20); + + JCheckBox delayBox = new JCheckBox("Delay"); + delayBox.setBounds(115, 95, 85, 20); + delayBox.setSelected(false); + delayBox.setFont(monospace); + delayBox.setFocusable(false); + + JLabel timesToSendLabel = new JLabel("Times to send:"); + timesToSendLabel.setFocusable(false); + timesToSendLabel.setFont(monospace); + timesToSendLabel.setBounds(25, 130, 100, 20); + + JTextField timesToSendField = new JTextField("1"); + timesToSendField.setFont(monospace); + timesToSendField.setBounds(125, 130, 100, 20); + + JButton sendButton = new JButton("Send"); + sendButton.setFocusable(false); + sendButton.setBounds(25, 95, 75, 20); + sendButton.setBorder(BorderFactory.createEtchedBorder()); + sendButton.setBackground(darkWhite); + sendButton.setFont(monospace); + sendButton.addActionListener((event0) -> { + if ( + MainClient.isInteger(syncIdField.getText()) && + MainClient.isInteger(buttonIdField.getText()) && + MainClient.isInteger(timesToSendField.getText())) { + int syncId = Integer.parseInt(syncIdField.getText()); + int buttonId = Integer.parseInt(buttonIdField.getText()); + int timesToSend = Integer.parseInt(timesToSendField.getText()); + + ButtonClickC2SPacket packet = new ButtonClickC2SPacket(syncId, buttonId); + try { + Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); + for (int i = 0; i < timesToSend; i++) { + toRun.run(); + } + } catch (Exception e) { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("You must be connected to a server!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + return; + } + statusLabel.setVisible(true); + statusLabel.setForeground(Color.GREEN.darker()); + statusLabel.setText("Sent successfully!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid arguments!"); + MainClient.queueTask(() -> { + statusLabel.setVisible(false); + statusLabel.setText(""); + }, 1500L); + } + }); + + buttonClickFrame.add(syncIdLabel); + buttonClickFrame.add(buttonIdLabel); + buttonClickFrame.add(syncIdField); + buttonClickFrame.add(timesToSendLabel); + buttonClickFrame.add(buttonIdField); + buttonClickFrame.add(sendButton); + buttonClickFrame.add(statusLabel); + buttonClickFrame.add(delayBox); + buttonClickFrame.add(timesToSendField); + buttonClickFrame.setVisible(true); + }); + + frame.add(clickSlotButton); + frame.add(buttonClickButton); + frame.setVisible(true); + }).width(115).position(5, 185).build(); + fabricatePacketButton.active = !SharedVariables.isMac; + screen.addDrawableChild(fabricatePacketButton); + + screen.addDrawableChild(ButtonWidget.builder(Text.of("Copy GUI Title JSON"), (button) -> { + try { + if (mc.currentScreen == null) { + throw new IllegalStateException("The current minecraft screen (mc.currentScreen) is null"); + } + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(Text.Serialization.toJsonString(mc.currentScreen.getTitle(), Objects.requireNonNull(MinecraftClient.getInstance().getServer()).getRegistryManager())), null); + } catch (IllegalStateException e) { + LOGGER.error("Error while copying title JSON to clipboard", e); + } + }).width(115).position(5, 215).build()); + } + + @NotNull + private static JButton getPacketOptionButton(String label) { + JButton button = new JButton(label); + button.setFocusable(false); + button.setBorder(BorderFactory.createEtchedBorder()); + button.setBackground(darkWhite); + button.setFont(monospace); + return button; + } + + @NotNull + private static Runnable getFabricatePacketRunnable(MinecraftClient mc, boolean delay, Packet packet) { + Runnable toRun; + if (delay) { + toRun = () -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().sendPacket(packet); + } else { + LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); + } + }; + } else { + toRun = () -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().sendPacket(packet); + } else { + LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); + } + ((ClientConnectionAccessor) mc.getNetworkHandler().getConnection()).getChannel().writeAndFlush(packet); + }; + } + return toRun; + } + + public static boolean isInteger(String string) { + try { + Integer.parseInt(string); + return true; + } catch (Exception e) { + return false; + } + } + + public static SlotActionType stringToSlotActionType(String string) { + // converts a string to SlotActionType + return switch (string) { + case "PICKUP" -> SlotActionType.PICKUP; + case "QUICK_MOVE" -> SlotActionType.QUICK_MOVE; + case "SWAP" -> SlotActionType.SWAP; + case "CLONE" -> SlotActionType.CLONE; + case "THROW" -> SlotActionType.THROW; + case "QUICK_CRAFT" -> SlotActionType.QUICK_CRAFT; + case "PICKUP_ALL" -> SlotActionType.PICKUP_ALL; + default -> null; + }; + } + + public static void queueTask(Runnable runnable, long delayMs) { + // queues a task for minecraft to run + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + MinecraftClient.getInstance().send(runnable); + } + }; + timer.schedule(task, delayMs); + } + + public static String getModVersion(String modId) { + ModMetadata modMetadata = FabricLoader.getInstance().getModContainer(modId).get().getMetadata(); + + return modMetadata.getVersion().getFriendlyString(); + } +} diff --git a/src/main/java/org/uiutils/SharedVariables.java b/src/main/java/org/uiutils/SharedVariables.java new file mode 100644 index 0000000..6bd7139 --- /dev/null +++ b/src/main/java/org/uiutils/SharedVariables.java @@ -0,0 +1,23 @@ +package org.uiutils; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.network.packet.Packet; +import net.minecraft.screen.ScreenHandler; + +import java.util.ArrayList; + +public class SharedVariables { + public static boolean sendUIPackets = true; + public static boolean delayUIPackets = false; + public static boolean shouldEditSign = true; + + public static ArrayList> delayedUIPackets = new ArrayList<>(); + + public static Screen storedScreen = null; + public static ScreenHandler storedScreenHandler = null; + + public static boolean enabled = true; + public static boolean isMac = false; + public static boolean bypassResourcePack = false; + public static boolean resourcePackForceDeny = false; +} diff --git a/src/main/java/org/uiutils/UpdateUtils.java b/src/main/java/org/uiutils/UpdateUtils.java new file mode 100644 index 0000000..d57d050 --- /dev/null +++ b/src/main/java/org/uiutils/UpdateUtils.java @@ -0,0 +1,86 @@ +package org.uiutils; + +import com.google.gson.Gson; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import org.uiutils.gui.UpdateScreen; + +import java.awt.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Level; + +import static org.uiutils.MainClient.getModVersion; + +public class UpdateUtils { + + public static boolean isOutdated; + public static String version; + public static boolean messageShown; + public static final String currentVersion = getModVersion("uiutils"); + + public static void checkForUpdates() { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Callable task = () -> { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://api.github.com/repos/Coderx-Gamer/ui-utils/releases/latest")) + .header("Accept", "application/vnd.github.v3+json") + .build(); + + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + Gson gson = new Gson(); + GithubRelease release = gson.fromJson(response.body(), GithubRelease.class); + return release.getTagName(); + } else { + MainClient.LOGGER.error("Failed to fetch the latest version. Status code: " + response.statusCode()); + return null; + } + } catch (IOException | InterruptedException e) { + MainClient.LOGGER.error("Failed to fetch the latest version: " + e); + return null; + } + }; + + Future future = executorService.submit(task); + try { + String latestVersion = future.get(); + MainClient.LOGGER.info("Latest version: " + latestVersion + " Current version: " + currentVersion); + version = latestVersion; + if (latestVersion != null && !latestVersion.equals(currentVersion)) { + isOutdated = true; + } + + } catch (Exception e) { + MainClient.LOGGER.error("Failed to check for updates: " + e); + } finally { + executorService.shutdown(); + } + } + + public static void downloadUpdate() { + MainClient.LOGGER.info("Opening download link..."); + try { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(new URI("https://ui-utils.com?ref=ingame")); + } else { + Runtime runtime = Runtime.getRuntime(); + runtime.exec(new String[]{"xdg-open", "https://ui-utils.com?ref=ingame"}); + } + } catch (IOException | URISyntaxException e) { + MainClient.LOGGER.info(e.getLocalizedMessage(), Level.SEVERE); + } + MinecraftClient.getInstance().setScreen(new UpdateScreen(Text.empty())); + } +} + diff --git a/src/main/java/org/uiutils/gui/UpdateScreen.java b/src/main/java/org/uiutils/gui/UpdateScreen.java new file mode 100644 index 0000000..50c772e --- /dev/null +++ b/src/main/java/org/uiutils/gui/UpdateScreen.java @@ -0,0 +1,36 @@ +package org.uiutils.gui; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextWidget; +import net.minecraft.text.Text; + +public class UpdateScreen extends Screen { + + public UpdateScreen(Text title) { + super(title); + } + + @Override + protected void init() { + super.init(); + Text message1 = Text.of("In order to update UI-Utils, first quit the game then"); + Text message2 = Text.of("delete the old UI-Utils jar file, and replace it with the new one you got on the website."); + int centerX = this.width / 2; + + this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message1) / 2, 80, textRenderer.getWidth(message1), 20, message1, this.textRenderer)); + this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message2) / 2, 95, textRenderer.getWidth(message2), 20, Text.of(message2), this.textRenderer)); + + int quitX = centerX - 85; + int backX = centerX + 5; + + this.addDrawableChild(ButtonWidget.builder(Text.of("Quit"), (button) -> { + this.client.stop(); + }).width(80).position(quitX, 145).build()); + + this.addDrawableChild(ButtonWidget.builder(Text.of("Back"), (button) -> { + this.client.setScreen(null); + }).width(80).position(backX, 145).build()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/uiutils/mixin/BookEditScreenMixin.java b/src/main/java/org/uiutils/mixin/BookEditScreenMixin.java new file mode 100644 index 0000000..111cf57 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/BookEditScreenMixin.java @@ -0,0 +1,67 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.BookEditScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.uiutils.MainClient; +import org.uiutils.SharedVariables; + +import java.util.regex.Pattern; + +@Mixin(BookEditScreen.class) +public class BookEditScreenMixin extends Screen { + protected BookEditScreenMixin(Text title) { + super(title); + } + @Unique + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + private TextFieldWidget addressField; + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + if (SharedVariables.enabled) { + MainClient.createWidgets(mc, this); + + // create chat box + this.addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER) { + if (this.getText().equals("^toggleuiutils")) { + SharedVariables.enabled = !SharedVariables.enabled; + if (mc.player != null) { + mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + ".")); + } + return false; + } + + if (mc.getNetworkHandler() != null) { + if (this.getText().startsWith("/")) { + mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); + } else { + mc.getNetworkHandler().sendChatMessage(this.getText()); + } + } else { + MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); + } + + this.setText(""); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + }; + this.addressField.setText(""); + this.addressField.setMaxLength(255); + + this.addDrawableChild(this.addressField); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/BookScreenMixin.java b/src/main/java/org/uiutils/mixin/BookScreenMixin.java new file mode 100644 index 0000000..3d7de6e --- /dev/null +++ b/src/main/java/org/uiutils/mixin/BookScreenMixin.java @@ -0,0 +1,67 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.BookScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.uiutils.MainClient; +import org.uiutils.SharedVariables; + +import java.util.regex.Pattern; + +@Mixin(BookScreen.class) +public class BookScreenMixin extends Screen { + protected BookScreenMixin(Text title) { + super(title); + } + @Unique + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + private TextFieldWidget addressField; + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + if (SharedVariables.enabled) { + MainClient.createWidgets(mc, this); + + // create chat box + this.addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER) { + if (this.getText().equals("^toggleuiutils")) { + SharedVariables.enabled = !SharedVariables.enabled; + if (mc.player != null) { + mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + ".")); + } + return false; + } + + if (mc.getNetworkHandler() != null) { + if (this.getText().startsWith("/")) { + mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); + } else { + mc.getNetworkHandler().sendChatMessage(this.getText()); + } + } else { + MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); + } + + this.setText(""); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + }; + this.addressField.setText(""); + this.addressField.setMaxLength(255); + + this.addDrawableChild(this.addressField); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/ChatScreenMixin.java b/src/main/java/org/uiutils/mixin/ChatScreenMixin.java new file mode 100644 index 0000000..5907c2b --- /dev/null +++ b/src/main/java/org/uiutils/mixin/ChatScreenMixin.java @@ -0,0 +1,29 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ChatScreen; +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.uiutils.MainClient; +import org.uiutils.SharedVariables; + +@Mixin(ChatScreen.class) +public class ChatScreenMixin { + @Inject(at = @At("HEAD"), method = "sendMessage", cancellable = true) + public void sendMessage(String chatText, boolean addToHistory, CallbackInfo ci) { + if (chatText.equals("^toggleuiutils")) { + SharedVariables.enabled = !SharedVariables.enabled; + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + ".")); + } else { + MainClient.LOGGER.warn("Minecraft player was nulling while enabling / disabling UI Utils."); + } + MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText); + MinecraftClient.getInstance().setScreen(null); + ci.cancel(); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/ClientCommonNetworkHandlerMixin.java b/src/main/java/org/uiutils/mixin/ClientCommonNetworkHandlerMixin.java new file mode 100644 index 0000000..930b459 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/ClientCommonNetworkHandlerMixin.java @@ -0,0 +1,39 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientCommonNetworkHandler; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.common.ResourcePackStatusC2SPacket; +import net.minecraft.network.packet.s2c.common.ResourcePackSendS2CPacket; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.uiutils.MainClient; +import org.uiutils.SharedVariables; + +@Mixin(ClientCommonNetworkHandler.class) +public abstract class ClientCommonNetworkHandlerMixin { + @Shadow + @Final + protected MinecraftClient client; + + @Shadow + public abstract void sendPacket(Packet packet); + + @Inject(at = @At("HEAD"), method = "onResourcePackSend", cancellable = true) + public void onResourcePackSend(ResourcePackSendS2CPacket packet, CallbackInfo ci) { + if (SharedVariables.bypassResourcePack && (packet.required() || SharedVariables.resourcePackForceDeny)) { + this.sendPacket(new ResourcePackStatusC2SPacket(MinecraftClient.getInstance().getSession().getUuidOrNull(), ResourcePackStatusC2SPacket.Status.ACCEPTED)); + this.sendPacket(new ResourcePackStatusC2SPacket(MinecraftClient.getInstance().getSession().getUuidOrNull(), ResourcePackStatusC2SPacket.Status.SUCCESSFULLY_LOADED)); + MainClient.LOGGER.info( + "[UI Utils]: Required Resource Pack Bypassed, Message: " + + (packet.prompt().isEmpty() ? "" : packet.prompt().toString()) + + ", URL: " + (packet.url() == null ? "" : packet.url()) + ); + ci.cancel(); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/ClientConnectionMixin.java b/src/main/java/org/uiutils/mixin/ClientConnectionMixin.java new file mode 100644 index 0000000..41b9b24 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/ClientConnectionMixin.java @@ -0,0 +1,39 @@ +package org.uiutils.mixin; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; +import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +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.uiutils.SharedVariables; + +@Mixin(ClientConnection.class) +public class ClientConnectionMixin { + + // called when sending any packet + @Inject(at = @At("HEAD"), method = "sendImmediately", cancellable = true) + public void sendImmediately(Packet packet, PacketCallbacks callbacks, boolean flush, CallbackInfo ci) { + // checks for if packets should be sent and if the packet is a gui related packet + if (!SharedVariables.sendUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { + ci.cancel(); + return; + } + + // checks for if packets should be delayed and if the packet is a gui related packet and is added to a list + if (SharedVariables.delayUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { + SharedVariables.delayedUIPackets.add(packet); + ci.cancel(); + } + + // cancels sign update packets if sign editing is disabled and re-enables sign editing + if (!SharedVariables.shouldEditSign && (packet instanceof UpdateSignC2SPacket)) { + SharedVariables.shouldEditSign = true; + ci.cancel(); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/HandledScreenMixin.java b/src/main/java/org/uiutils/mixin/HandledScreenMixin.java new file mode 100644 index 0000000..d7d0de3 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/HandledScreenMixin.java @@ -0,0 +1,119 @@ +package org.uiutils.mixin; + +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.HandledScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +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 org.uiutils.MainClient; +import org.uiutils.SharedVariables; + +import java.util.regex.Pattern; + +@Mixin(HandledScreen.class) +public abstract class HandledScreenMixin extends Screen { + private HandledScreenMixin() { + super(null); + } + + @Shadow + protected abstract boolean handleHotbarKeyPressed(int keyCode, int scanCode); + @Shadow + protected abstract void onMouseClick(Slot slot, int slotId, int button, SlotActionType actionType); + @Shadow + @Nullable + protected Slot focusedSlot; + + @Unique + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + @Unique + private TextFieldWidget addressField; + + // called when creating a HandledScreen + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + if (SharedVariables.enabled) { + MainClient.createWidgets(mc, this); + + // create chat box + this.addressField = new TextFieldWidget(this.textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER) { + if (this.getText().equals("^toggleuiutils")) { + SharedVariables.enabled = !SharedVariables.enabled; + if (mc.player != null) { + mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + ".")); + } + return false; + } + + if (mc.getNetworkHandler() != null) { + if (this.getText().startsWith("/")) { + mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); + } else { + mc.getNetworkHandler().sendChatMessage(this.getText()); + } + } else { + MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); + } + + this.setText(""); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + }; + this.addressField.setText(""); + this.addressField.setMaxLength(256); + + this.addDrawableChild(this.addressField); + } + } + + @Inject(at = @At("HEAD"), method = "keyPressed", cancellable = true) + public void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { + cir.cancel(); + if (super.keyPressed(keyCode, scanCode, modifiers)) { + cir.setReturnValue(true); + } else if (MainClient.mc.options.inventoryKey.matchesKey(keyCode, scanCode) && (this.addressField == null || !this.addressField.isSelected())) { + // Crashes if address field does not exist (because of ui utils disabled, this is a temporary fix.) + this.close(); + cir.setReturnValue(true); + } else { + this.handleHotbarKeyPressed(keyCode, scanCode); + if (this.focusedSlot != null && this.focusedSlot.hasStack()) { + if (mc.options.pickItemKey.matchesKey(keyCode, scanCode)) { + this.onMouseClick(this.focusedSlot, this.focusedSlot.id, 0, SlotActionType.CLONE); + } else if (mc.options.dropKey.matchesKey(keyCode, scanCode)) { + this.onMouseClick(this.focusedSlot, this.focusedSlot.id, hasControlDown() ? 1 : 0, SlotActionType.THROW); + } + } + + cir.setReturnValue(true); + } + } + + // inject at the end of the render method + @Inject(at = @At("TAIL"), method = "render") + public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + // display sync id, revision, if ui utils is enabled + // this hurts me physically to look at this in a render method :( + // im too lazy to fix it tho :D + if (SharedVariables.enabled) { + MainClient.createText(mc, context, this.textRenderer); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/MultiplayerScreenMixin.java b/src/main/java/org/uiutils/mixin/MultiplayerScreenMixin.java new file mode 100644 index 0000000..cca2597 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/MultiplayerScreenMixin.java @@ -0,0 +1,35 @@ +package org.uiutils.mixin; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +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.uiutils.SharedVariables; + +@Mixin(MultiplayerScreen.class) +public class MultiplayerScreenMixin extends Screen { + private MultiplayerScreenMixin() { + super(null); + } + + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + if (SharedVariables.enabled) { + // Create "Bypass Resource Pack" option + this.addDrawableChild(ButtonWidget.builder(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF")), (button) -> { + SharedVariables.bypassResourcePack = !SharedVariables.bypassResourcePack; + button.setMessage(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF"))); + }).width(160).position(this.width - 170, this.height - 50).build()); + + // Create "Force Deny" option + this.addDrawableChild(ButtonWidget.builder(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF")), (button) -> { + SharedVariables.resourcePackForceDeny = !SharedVariables.resourcePackForceDeny; + button.setMessage(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF"))); + }).width(160).position(this.width - 170, this.height - 25).build()); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/ScreenMixin.java b/src/main/java/org/uiutils/mixin/ScreenMixin.java new file mode 100644 index 0000000..0681970 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/ScreenMixin.java @@ -0,0 +1,95 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.LecternScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.uiutils.MainClient; +import org.uiutils.SharedVariables; +import org.uiutils.mixin.accessor.ScreenAccessor; + +import java.util.regex.Pattern; + +@SuppressWarnings("all") +@Mixin(Screen.class) +public abstract class ScreenMixin { + @Shadow + public abstract T addDrawableChild(T drawableElement); + + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + private TextFieldWidget addressField; + private boolean initialized = false; + + // inject at the end of the render method (if instanceof LecternScreen) + @Inject(at = @At("TAIL"), method = "init(Lnet/minecraft/client/MinecraftClient;II)V") + public void init(MinecraftClient client, int width, int height, CallbackInfo ci) { + // check if the current gui is a lectern gui and if ui-utils is enabled + if (mc.currentScreen instanceof LecternScreen screen && SharedVariables.enabled) { + // setup widgets + if (/*!this.initialized*/ true) { // bro why did you do this cxg :skull: + // check if the current gui is a lectern gui and ui-utils is enabled + // if you do not message me about this @coderx-gamer you are not reading my commits + // why would you read them anyway tbh + // ill clean this up later if you dont fix it + + TextRenderer textRenderer = ((ScreenAccessor) this).getTextRenderer(); + MainClient.createWidgets(mc, screen); + + // create chat box + this.addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER) { + if (this.getText().equals("^toggleuiutils")) { + SharedVariables.enabled = !SharedVariables.enabled; + if (mc.player != null) { + mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + ".")); + } + return false; + } + + if (mc.getNetworkHandler() != null) { + if (this.getText().startsWith("/")) { + mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); + } else { + mc.getNetworkHandler().sendChatMessage(this.getText()); + } + } else { + MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); + } + + this.setText(""); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + }; + this.addressField.setText(""); + this.addressField.setMaxLength(255); + + this.addDrawableChild(this.addressField); + this.initialized = true; + } + } + } + + @Inject(at = @At("TAIL"), method = "render") + public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + // display sync id, revision, if ui utils is enabled + if (SharedVariables.enabled && mc.player != null && mc.currentScreen instanceof LecternScreen) { + MainClient.createText(mc, context, ((ScreenAccessor) this).getTextRenderer()); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/SignEditScreenMixin.java b/src/main/java/org/uiutils/mixin/SignEditScreenMixin.java new file mode 100644 index 0000000..b36bb20 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/SignEditScreenMixin.java @@ -0,0 +1,42 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.SignEditScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.uiutils.SharedVariables; + +@Mixin(SignEditScreen.class) +public class SignEditScreenMixin extends Screen { + protected SignEditScreenMixin(Text title) { + super(title); + } + + @Unique + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + // called when any sign edit screen is created + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + + // register "close without packet" button for SignEditScreen if ui utils is enabled + if (SharedVariables.enabled) { + addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { + // disables sign editing and closes the current gui without sending a packet + SharedVariables.shouldEditSign = false; + mc.setScreen(null); + }).width(115).position(5, 5).build()); + addDrawableChild(ButtonWidget.builder(Text.of("Disconnect"), (button) -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); + } + }).width(115).position(5, 35).build()); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/SleepingChatScreenMixin.java b/src/main/java/org/uiutils/mixin/SleepingChatScreenMixin.java new file mode 100644 index 0000000..779767e --- /dev/null +++ b/src/main/java/org/uiutils/mixin/SleepingChatScreenMixin.java @@ -0,0 +1,33 @@ +package org.uiutils.mixin; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.SleepingChatScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +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.uiutils.SharedVariables; + +@Mixin(SleepingChatScreen.class) +public class SleepingChatScreenMixin extends Screen { + protected SleepingChatScreenMixin(Text title) { + super(title); + } + + // called when SleepingChatScreen is created + @Inject(at = @At("TAIL"), method = "init") + public void init(CallbackInfo ci) { + // register "client wake up" button for SleepingChatScreen if ui utils is enabled + if (SharedVariables.enabled) { + addDrawableChild(ButtonWidget.builder(Text.of("Client wake up"), (button) -> { + // wakes the player up client-side + if (this.client != null && this.client.player != null) { + this.client.player.wakeUp(); + this.client.setScreen(null); + } + }).width(115).position(5, 5).build()); + } + } +} diff --git a/src/main/java/org/uiutils/mixin/TitleScreenMixin.java b/src/main/java/org/uiutils/mixin/TitleScreenMixin.java new file mode 100644 index 0000000..b9d8661 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/TitleScreenMixin.java @@ -0,0 +1,54 @@ +package org.uiutils.mixin; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ButtonTextures; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextWidget; +import net.minecraft.client.gui.widget.TexturedButtonWidget; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.client.toast.ToastManager; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +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.uiutils.UpdateUtils; + +@Mixin(TitleScreen.class) +public class TitleScreenMixin extends Screen { + protected TitleScreenMixin(Text title) { + super(title); + } + + @Inject(at = @At("RETURN"), method = "initWidgetsNormal(II)V") + private void onInitWidgetsNormal(int Y, int spacingY, CallbackInfo ci) { + if (UpdateUtils.isOutdated) { + if (!UpdateUtils.messageShown) { + MinecraftClient client = MinecraftClient.getInstance(); + ToastManager toastManager = client.getToastManager(); + Text title = Text.of("UI-Utils " + UpdateUtils.version + " is out!"); + Text description = Text.of("Download it from the top left corner!"); + SystemToast.add(toastManager, new SystemToast.Type(30000L), title, description); + UpdateUtils.messageShown = true; + } + + Text message = Text.of("Download UI-Utils " + UpdateUtils.version + "!"); + + this.addDrawableChild(new TextWidget(40 - 15, 5, textRenderer.getWidth(message), textRenderer.fontHeight, message, textRenderer)); + + ButtonWidget downloadUpdateButton = new TexturedButtonWidget(5, 5 - 3, + 15, 15, + new ButtonTextures( + new Identifier("uiutils", "update"), + new Identifier("uiutils", "update_selected") + ), + (button) -> UpdateUtils.downloadUpdate(), + Text.of("Download Update")); + this.addDrawableChild(downloadUpdateButton); + + } + } +} diff --git a/src/main/java/org/uiutils/mixin/accessor/ClientConnectionAccessor.java b/src/main/java/org/uiutils/mixin/accessor/ClientConnectionAccessor.java new file mode 100644 index 0000000..0f00f37 --- /dev/null +++ b/src/main/java/org/uiutils/mixin/accessor/ClientConnectionAccessor.java @@ -0,0 +1,12 @@ +package org.uiutils.mixin.accessor; + +import io.netty.channel.Channel; +import net.minecraft.network.ClientConnection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientConnection.class) +public interface ClientConnectionAccessor { + @Accessor + Channel getChannel(); +} diff --git a/src/main/java/org/uiutils/mixin/accessor/ScreenAccessor.java b/src/main/java/org/uiutils/mixin/accessor/ScreenAccessor.java new file mode 100644 index 0000000..69a0d8b --- /dev/null +++ b/src/main/java/org/uiutils/mixin/accessor/ScreenAccessor.java @@ -0,0 +1,12 @@ +package org.uiutils.mixin.accessor; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.screen.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Screen.class) +public interface ScreenAccessor { + @Accessor + TextRenderer getTextRenderer(); +}