+ * For non-translated buttons, the translationKey parameter should be the + * raw button text instead. + */ + public static Button findButton(Minecraft mc, String translationKey) + { + String message = I18n.get(translationKey); + + for(Renderable drawable : mc.screen.renderables) + if(drawable instanceof Button button + && button.getMessage().getString().equals(message)) + return button; + + throw new RuntimeException(message + " button could not be found"); + } + + /** + * Looks for the given button at the given coordinates and fails if it is + * not there. + */ + public static void checkButtonPosition(Button button, int expectedX, + int expectedY) + { + String buttonName = button.getMessage().getString(); + + if(button.getX() != expectedX) + throw new RuntimeException(buttonName + + " button is at the wrong X coordinate. Expected X: " + + expectedX + ", actual X: " + button.getX()); + + if(button.getY() != expectedY) + throw new RuntimeException(buttonName + + " button is at the wrong Y coordinate. Expected Y: " + + expectedY + ", actual Y: " + button.getY()); + } + + /** + * Clicks the button with the given translation key, or fails after 10 + * seconds. + * + *
+ * For non-translated buttons, the translationKey parameter should be the + * raw button text instead. + */ + public static void clickButton(String translationKey) + { + String buttonText = I18n.get(translationKey); + + waitUntil("button saying " + buttonText + " is visible", mc -> { + Screen screen = mc.screen; + if(screen == null) + return false; + + for(Renderable drawable : screen.renderables) + { + if(!(drawable instanceof AbstractWidget widget)) + continue; + + if(widget instanceof Button button + && buttonText.equals(button.getMessage().getString())) + { + button.onPress(); + return true; + } + + if(widget instanceof CycleButton> button + && buttonText.equals(button.name.getString())) + { + button.onPress(); + return true; + } + } + + return false; + }); + } + + /** + * Types the given text into the nth text field on the current screen, or + * fails after 10 seconds. + */ + public static void setTextFieldText(int index, String text) + { + waitUntil("text field #" + index + " is visible", mc -> { + Screen screen = mc.screen; + if(screen == null) + return false; + + int i = 0; + for(Renderable drawable : screen.renderables) + { + if(!(drawable instanceof EditBox textField)) + continue; + + if(i == index) + { + textField.setValue(text); + return true; + } + + i++; + } + + return false; + }); + } + + public static void closeScreen() + { + submitAndWait(mc -> mc.setScreen(null)); + } + + public static void openGameMenu() + { + submitAndWait(mc -> mc.setScreen(new PauseScreen(true))); + } + + public static void openInventory() + { + submitAndWait(mc -> mc.setScreen(new InventoryScreen(mc.player))); + } + + public static void toggleDebugHud() + { + submitAndWait(mc -> mc.gui.getDebugOverlay().toggleOverlay()); + } + + public static void setPerspective(CameraType perspective) + { + submitAndWait(mc -> mc.options.setCameraType(perspective)); + } + + public static void dismissTutorialToasts() + { + submitAndWait(mc -> mc.getTutorial().setStep(TutorialSteps.NONE)); + } + + public static void clearChat() + { + submitAndWait(mc -> mc.gui.getChat().clearMessages(true)); + } + + /** + * Runs the given chat command and waits one tick for the action to + * complete. + * + *
+ * Do not put a / at the start of the command. + */ + public static void runChatCommand(String command) + { + System.out.println("Running command: /" + command); + submitAndWait(mc -> { + ClientPacketListener netHandler = mc.getConnection(); + + // Validate command using client-side command dispatcher + ParseResults> results = netHandler.getCommands().parse(command, + netHandler.getSuggestionsProvider()); + + // Command is invalid, fail the test + if(!results.getExceptions().isEmpty()) + { + StringBuilder errors = + new StringBuilder("Invalid command: " + command); + for(CommandSyntaxException e : results.getExceptions().values()) + errors.append("\n").append(e.getMessage()); + + throw new RuntimeException(errors.toString()); + } + + // Command is valid, send it + netHandler.sendCommand(command); + }); + waitForWorldTicks(1); + } + + public static void assertOneItemInSlot(int slot, Item item) + { + submitAndWait(mc -> { + ItemStack stack = mc.player.getInventory().getItem(slot); + if(!stack.is(item) || stack.getCount() != 1) + throw new RuntimeException("Expected 1 " + + item.getName(stack).getString() + " at slot " + slot + + ", found " + stack.getCount() + " " + + stack.getItem().getName(stack).getString() + " instead"); + }); + } + + public static void assertNoItemInSlot(int slot) + { + submitAndWait(mc -> { + ItemStack stack = mc.player.getInventory().getItem(slot); + if(!stack.isEmpty()) + throw new RuntimeException("Expected no item in slot " + slot + + ", found " + stack.getCount() + " " + + stack.getItem().getName(stack).getString() + " instead"); + }); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..2eab375 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,3 @@ +public net.minecraft.client.gui.screens.Screen renderables # renderables +public net.minecraft.client.gui.screens.TitleScreen fading # fading +public net.minecraft.client.gui.components.CycleButton name # name