From 272c1059ef81deab8aba0642c14093f3f529c4eb Mon Sep 17 00:00:00 2001 From: retucio Date: Wed, 19 Aug 2026 22:57:41 +0200 Subject: [PATCH] v5.7 --- cambios.md | 30 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- logs/latest.log | 0 src/main/java/me/retucio/sputnik/Sputnik.java | 11 +- .../me/retucio/sputnik/command/Command.java | 17 +- .../sputnik/command/CommandManager.java | 3 + .../sputnik/command/commands/BindCommand.java | 2 +- .../command/commands/ClickSlotCommand.java | 3 +- .../command/commands/ConfigCommand.java | 3 +- .../commands/CopyScreenshotCommand.java | 3 +- .../command/commands/EnderChestCommand.java | 2 +- .../command/commands/FriendCommand.java | 2 +- .../commands/GarbageCleanerCommand.java | 4 +- .../sputnik/command/commands/HelpCommand.java | 28 + .../command/commands/MacroCommand.java | 114 ++++ .../command/commands/PanoramaCommand.java | 2 +- .../sputnik/command/commands/PeekCommand.java | 3 +- .../command/commands/PluginsCommand.java | 2 +- .../command/commands/PrefixCommand.java | 2 +- .../command/commands/ProfileCommand.java | 92 ++++ .../command/commands/PurgeCommand.java | 3 +- .../command/commands/RepeatCommand.java | 3 +- .../command/commands/RepeatWaitCommand.java | 3 +- .../commands/SaveScreenshotCommand.java | 2 +- .../sputnik/command/commands/SendCommand.java | 3 +- .../command/commands/SpectateCommand.java | 3 +- .../command/commands/ToggleCommand.java | 3 +- .../sputnik/command/commands/WaitCommand.java | 3 +- .../sputnik/config/BinarySerializer.java | 42 +- .../retucio/sputnik/config/ClientConfig.java | 20 +- .../retucio/sputnik/config/ConfigManager.java | 76 ++- .../sputnik/config/ProfileManager.java | 99 ++++ .../java/me/retucio/sputnik/macro/Macro.java | 53 ++ .../retucio/sputnik/macro/MacroManager.java | 60 +++ .../me/retucio/sputnik/module/Module.java | 1 + .../retucio/sputnik/module/ModuleManager.java | 1 + .../sputnik/module/modules/camera/Autism.java | 17 +- .../module/modules/camera/Rotations.java | 4 +- .../module/modules/combat/CrystalAura.java | 29 +- .../module/modules/combat/SpearKill.java | 7 +- .../modules/movement/InfiniteElytra.java | 5 +- .../module/modules/movement/Scaffold.java | 4 +- .../modules/network/PacketCanceller.java | 44 ++ .../module/modules/network/PacketDelay.java | 2 - .../module/modules/network/PacketMine.java | 31 +- .../sputnik/module/modules/player/NoFall.java | 15 +- .../sputnik/module/modules/world/Racist.java | 7 +- .../me/retucio/sputnik/ui/hud/HudEditor.java | 155 ++++++ .../sputnik/ui/hud/HudEditorScreen.java | 153 +----- .../retucio/sputnik/ui/hud/HudRenderer.java | 6 +- .../sputnik/ui/hud/ImageHudElement.java | 2 +- .../sputnik/ui/hud/TextHudElement.java | 2 +- .../retucio/sputnik/ui/screen/ClickGui.java | 118 ++++- .../me/retucio/sputnik/ui/screen/Tab.java | 27 + .../sputnik/ui/screen/tabs/MacrosTab.java | 247 +++++++++ .../sputnik/ui/screen/tabs/ProfilesTab.java | 158 ++++++ .../sputnik/ui/widgets/misc/TabBarWidget.java | 90 ++++ .../ui/widgets/misc/TextFieldWidget.java | 118 +++++ .../me/retucio/sputnik/util/ChatUtil.java | 2 + .../me/retucio/sputnik/util/EntityUtil.java | 20 - .../java/me/retucio/sputnik/util/Lists.java | 45 +- .../me/retucio/sputnik/util/NetworkUtil.java | 22 +- .../me/retucio/sputnik/util/RotationUtil.java | 490 ++++++++++++++++++ .../resources/assets/sputnik/lang/en_us.json | 51 +- .../resources/assets/sputnik/lang/es_es.json | 41 ++ 66 files changed, 2300 insertions(+), 314 deletions(-) create mode 100644 logs/latest.log create mode 100644 src/main/java/me/retucio/sputnik/command/commands/HelpCommand.java create mode 100644 src/main/java/me/retucio/sputnik/command/commands/MacroCommand.java create mode 100644 src/main/java/me/retucio/sputnik/command/commands/ProfileCommand.java create mode 100644 src/main/java/me/retucio/sputnik/config/ProfileManager.java create mode 100644 src/main/java/me/retucio/sputnik/macro/Macro.java create mode 100644 src/main/java/me/retucio/sputnik/macro/MacroManager.java create mode 100644 src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java create mode 100644 src/main/java/me/retucio/sputnik/ui/hud/HudEditor.java create mode 100644 src/main/java/me/retucio/sputnik/ui/screen/Tab.java create mode 100644 src/main/java/me/retucio/sputnik/ui/screen/tabs/MacrosTab.java create mode 100644 src/main/java/me/retucio/sputnik/ui/screen/tabs/ProfilesTab.java create mode 100644 src/main/java/me/retucio/sputnik/ui/widgets/misc/TabBarWidget.java create mode 100644 src/main/java/me/retucio/sputnik/ui/widgets/misc/TextFieldWidget.java create mode 100644 src/main/java/me/retucio/sputnik/util/RotationUtil.java diff --git a/cambios.md b/cambios.md index 4769871..5181cda 100644 --- a/cambios.md +++ b/cambios.md @@ -1,29 +1,31 @@ -# v5.5 +# v5.7 ## novedades +actualización a gradle 9.7.0 + +interfaz reorganizada en pestañas: módulos, hud, perfiles y macros + +perfiles de configuración: guarda varias configs con nombre y cambia entre ellas sin reiniciar + +macros: asigna una tecla a una lista de comandos o mensajes + ### adiciones #### módulos nuevos: -- autoataque: mantén el botón de ataque pulsado para atacar automáticamente +- cancelador de paquetes: evita que se envíen o reciban ciertos paquetes #### comandos nuevos: +- `$?`: muestra los comandos disponibles y los describe +- `$perfil`: gestiona los perfiles de configuración +- `$macro`: gestiona las macros + ### arreglos / mejoras -- arreglado el guardado al cerrar el juego: petaba antes de escribir nada, así que se perdía - todo lo cambiado desde el último autoguardado y al volver a entrar salían los ajustes por defecto -- el autoguardado cuenta desde el último guardado y no desde el último cambio, que tocando - ajustes seguido no llegaba a guardar nunca -- guardando dos veces seguidas ya no se descarta la segunda (era la más reciente) -- las teclas de los módulos ya no se pisan entre ellas al cargar la config. -- si algo falla al aplicar los ajustes ya no se queda el mod entero sin guardar -- las listas (entidades, ítems, bloques...) se traducen al idioma del juego en vez de salir - siempre en inglés, y se guardan por nombre interno para que no dependan del idioma -- traducidos los títulos de los marcos, las descripciones de los módulos y los mensajes del - arranque, que salían en español o directamente como la clave de traducción -- añadidas las traducciones que faltaban del autoataque +- las rotaciones de los módulos se reparten por prioridad en vez de pisarse entre ellas +- el editor del hud ya es una pestaña más de la interfaz, además de su propia pantalla ## problemas reconocidos diff --git a/gradle.properties b/gradle.properties index f4b5ab9..1a04fc5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ discord_rpc_version=1.0.3 neutrino_version=1.1.0 jdk_version=25 -mod_version=5.6 +mod_version=5.7 maven_group=me.retucio archives_base_name=sputnik \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a351597..1e922f4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/logs/latest.log b/logs/latest.log new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/me/retucio/sputnik/Sputnik.java b/src/main/java/me/retucio/sputnik/Sputnik.java index b23b7ef..7ca5cb4 100644 --- a/src/main/java/me/retucio/sputnik/Sputnik.java +++ b/src/main/java/me/retucio/sputnik/Sputnik.java @@ -7,6 +7,8 @@ import me.retucio.sputnik.command.CommandManager; import me.retucio.sputnik.command.commands.BindCommand; import me.retucio.sputnik.config.ConfigManager; +import me.retucio.sputnik.config.ProfileManager; +import me.retucio.sputnik.macro.MacroManager; import me.retucio.sputnik.event.ShutdownEvent; import me.retucio.sputnik.event.interact.OpenScreenEvent; @@ -17,6 +19,7 @@ import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.client.Hud; +import me.retucio.sputnik.ui.hud.HudEditor; import me.retucio.sputnik.ui.hud.HudRenderer; import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.hud.HudEditorScreen; @@ -99,12 +102,17 @@ public class Sputnik implements ClientModInitializer { EVENT_BUS.subscribe(MiscUtil.class); EVENT_BUS.subscribe(NetworkUtil.class); EVENT_BUS.subscribe(RenderUtil.class); + EVENT_BUS.subscribe(RotationUtil.class); EVENT_BUS.subscribe(BlockIterator.class); Lists.init(); HudRenderer.INSTANCE = new HudRenderer(); + HudEditor.INSTANCE = new HudEditor(); + + MacroManager.INSTANCE = new MacroManager(); + ProfileManager.INSTANCE = new ProfileManager(); CapeManager.INSTANCE = new CapeManager(); EVENT_BUS.post(new LoadCapeManagerEvent()); @@ -151,11 +159,12 @@ public class Sputnik implements ClientModInitializer { // prevenir interrumpir combos de F3 if (key == GLFW.GLFW_KEY_F3) return; - boolean anyFocused = isAnySettingButtonFocused() || ClickGui.INSTANCE.getSearchBar().isFocused(); + boolean anyFocused = isAnySettingButtonFocused() || ClickGui.INSTANCE.isCapturingInput(); ClickGui.INSTANCE.setAnyFocused(anyFocused); if (action != GLFW.GLFW_RELEASE) { if (BindCommand.onKeyPress(key)) return; + if (action == GLFW.GLFW_PRESS && MacroManager.INSTANCE.onKeyPress(key)) return; if (action == GLFW.GLFW_PRESS) { handleModuleToggle(key, anyFocused); handleClickGuiKey(key, anyFocused); diff --git a/src/main/java/me/retucio/sputnik/command/Command.java b/src/main/java/me/retucio/sputnik/command/Command.java index 4b2a488..0a4bd53 100644 --- a/src/main/java/me/retucio/sputnik/command/Command.java +++ b/src/main/java/me/retucio/sputnik/command/Command.java @@ -9,6 +9,7 @@ import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.Commands; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.data.registries.VanillaRegistries; +import net.minecraft.network.chat.Component; import java.util.List; @@ -20,12 +21,14 @@ public abstract class Command { protected static final Minecraft mc = Minecraft.getInstance(); - private final String name, description; + private final String name, descriptionKey; private final List aliases; - public Command(String name, String description, String... aliases) { + // la descripción se guarda como clave de traducción, no como texto ya resuelto: los comandos + // se construyen antes de que se cargue el idioma, así que hay que traducirla al mostrarla + public Command(String name, String descriptionKey, String... aliases) { this.name = name; - this.description = description; + this.descriptionKey = descriptionKey; this.aliases = List.of(aliases); } @@ -56,8 +59,12 @@ public abstract class Command { return name; } - public String getDescription() { - return description; + public String getDescriptionKey() { + return descriptionKey; + } + + public Component getDescription() { + return Component.translatable(descriptionKey); } public List getAliases() { diff --git a/src/main/java/me/retucio/sputnik/command/CommandManager.java b/src/main/java/me/retucio/sputnik/command/CommandManager.java index 38c6e36..efd93ce 100644 --- a/src/main/java/me/retucio/sputnik/command/CommandManager.java +++ b/src/main/java/me/retucio/sputnik/command/CommandManager.java @@ -38,10 +38,13 @@ public class CommandManager { addCommand(new EnderChestCommand()); addCommand(new FriendCommand()); addCommand(new GarbageCleanerCommand()); + addCommand(new HelpCommand()); + addCommand(new MacroCommand()); addCommand(new PanoramaCommand()); addCommand(new PeekCommand()); addCommand(new PluginsCommand()); addCommand(new PrefixCommand()); + addCommand(new ProfileCommand()); addCommand(new PurgeCommand()); addCommand(new RepeatCommand()); addCommand(new RepeatWaitCommand()); diff --git a/src/main/java/me/retucio/sputnik/command/commands/BindCommand.java b/src/main/java/me/retucio/sputnik/command/commands/BindCommand.java index ac6cac1..bd9125c 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/BindCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/BindCommand.java @@ -24,7 +24,7 @@ public class BindCommand extends Command { private static Module listeningModule = null; public BindCommand() { - super("bind", Component.translatable("sputnik.commands.bind.desc").getString(), "keybind", "kb", "tecla"); + super("bind", "sputnik.commands.bind.desc", "keybind", "kb", "tecla"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/ClickSlotCommand.java b/src/main/java/me/retucio/sputnik/command/commands/ClickSlotCommand.java index fafd8e0..a851e82 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/ClickSlotCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/ClickSlotCommand.java @@ -7,14 +7,13 @@ import me.retucio.sputnik.command.args.EnumArgumentType; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.ContainerScreen; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import net.minecraft.world.inventory.ContainerInput; // https://cdn.discordapp.com/attachments/1456079799045980400/1457154232737206375/dupersunited-public-addon-1.21.11-3.1.3.jar?ex=69735be1&is=69720a61&hm=77fb6faee49762e0a812661f0d98931e89cb725db711c45722249d0efe3b91e4& public class ClickSlotCommand extends Command { public ClickSlotCommand() { - super("clickslot", Component.translatable("sputnik.commands.clickslot.desc").getString(), "cs"); + super("clickslot", "sputnik.commands.clickslot.desc", "cs"); } public void build(LiteralArgumentBuilder builder) { diff --git a/src/main/java/me/retucio/sputnik/command/commands/ConfigCommand.java b/src/main/java/me/retucio/sputnik/command/commands/ConfigCommand.java index e75b12f..52afc27 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/ConfigCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/ConfigCommand.java @@ -9,12 +9,11 @@ import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.widgets.panels.SettingsPanel; import net.minecraft.commands.SharedSuggestionProvider; import me.retucio.sputnik.module.Module; -import net.minecraft.network.chat.Component; public class ConfigCommand extends Command { public ConfigCommand() { - super("config", Component.translatable("sputnik.commands.config.desc").getString(), "ajustes", "settings"); + super("config", "sputnik.commands.config.desc", "ajustes", "settings"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/CopyScreenshotCommand.java b/src/main/java/me/retucio/sputnik/command/commands/CopyScreenshotCommand.java index 2494fdd..3361c93 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/CopyScreenshotCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/CopyScreenshotCommand.java @@ -5,13 +5,12 @@ import me.retucio.sputnik.command.Command; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.misc.ScreenshotPlus; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; // se usa principalmente para el módulo ScreenshotPlus, pero también se puede usar sin él supongo public class CopyScreenshotCommand extends Command { public CopyScreenshotCommand() { - super("copiarcaptura", Component.translatable("sputnik.commands.copyscreenshot.desc").getString(), "copyscreenshot"); + super("copiarcaptura", "sputnik.commands.copyscreenshot.desc", "copyscreenshot"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/EnderChestCommand.java b/src/main/java/me/retucio/sputnik/command/commands/EnderChestCommand.java index 1049699..619e240 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/EnderChestCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/EnderChestCommand.java @@ -16,7 +16,7 @@ public class EnderChestCommand extends Command { private boolean shouldOpenEchest = false; public EnderChestCommand() { - super("echest", Component.translatable("sputnik.commands.echest.desc").getString(), "ec", "enderchest"); + super("echest", "sputnik.commands.echest.desc", "ec", "enderchest"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/FriendCommand.java b/src/main/java/me/retucio/sputnik/command/commands/FriendCommand.java index d7cc0aa..e0307f8 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/FriendCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/FriendCommand.java @@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Player; public class FriendCommand extends Command { public FriendCommand() { - super("friend", Component.translatable("sputnik.commands.friend.desc").getString(), "amigo", "f"); + super("friend", "sputnik.commands.friend.desc", "amigo", "f"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/GarbageCleanerCommand.java b/src/main/java/me/retucio/sputnik/command/commands/GarbageCleanerCommand.java index ea1bc3b..33bcbc6 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/GarbageCleanerCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/GarbageCleanerCommand.java @@ -9,12 +9,12 @@ import net.minecraft.network.chat.Component; public class GarbageCleanerCommand extends Command { public GarbageCleanerCommand() { - super("gc", Component.translatable("sputnik.commands.gc.desc").getString(), "garbagecollect"); + super("gc", "sputnik.commands.gc.desc", "garbagecollect"); } @Override public void build(LiteralArgumentBuilder builder) { - builder.executes(context -> { + builder.executes(ctx -> { System.gc(); ChatUtil.info(Component.translatable("sputnik.commands.gc.collected")); return SUCCESS; diff --git a/src/main/java/me/retucio/sputnik/command/commands/HelpCommand.java b/src/main/java/me/retucio/sputnik/command/commands/HelpCommand.java new file mode 100644 index 0000000..8e72c24 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/command/commands/HelpCommand.java @@ -0,0 +1,28 @@ +package me.retucio.sputnik.command.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.retucio.sputnik.command.Command; +import me.retucio.sputnik.command.CommandManager; +import me.retucio.sputnik.util.ChatUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; + +public class HelpCommand extends Command { + + public HelpCommand() { + super("help", Component.translatable("sputnik.commands.help.desc").getString(), "?", "ayuda", "h"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.executes(ctx -> { + for (Command command : CommandManager.commands) { + ChatUtil.info(CommandManager.INSTANCE.getPrefix() + + ChatFormatting.BOLD + command.getName() + ": " + + ChatFormatting.RESET + ChatFormatting.GRAY + command.getDescription().getString()); + } + return SUCCESS; + }); + } +} diff --git a/src/main/java/me/retucio/sputnik/command/commands/MacroCommand.java b/src/main/java/me/retucio/sputnik/command/commands/MacroCommand.java new file mode 100644 index 0000000..645e7ad --- /dev/null +++ b/src/main/java/me/retucio/sputnik/command/commands/MacroCommand.java @@ -0,0 +1,114 @@ +package me.retucio.sputnik.command.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import me.retucio.sputnik.command.Command; +import me.retucio.sputnik.macro.Macro; +import me.retucio.sputnik.macro.MacroManager; +import me.retucio.sputnik.util.ChatUtil; +import me.retucio.sputnik.util.KeyUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; + +import java.util.List; + + +public class MacroCommand extends Command { + + private static final SuggestionProvider MACROS = (ctx, builder) -> + SharedSuggestionProvider.suggest( + MacroManager.INSTANCE.getMacros().stream().map(Macro::getName), builder); + + public MacroCommand() { + super("macro", "sputnik.commands.macro.desc", "macros"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .executes(ctx -> { + list(); + return SUCCESS; + }) + .then(literal("list").executes(ctx -> { + list(); + return SUCCESS; + })) + .then(literal("add") + .then(argument("nombre", StringArgumentType.word()).executes(ctx -> { + String name = ctx.getArgument("nombre", String.class); + + if (MacroManager.INSTANCE.add(new Macro(name))) + ChatUtil.info(Component.translatable("sputnik.commands.macro.added", + ChatFormatting.AQUA + name)); + else + ChatUtil.error(Component.translatable("sputnik.commands.macro.exists", name)); + return SUCCESS; + }))) + .then(literal("remove") + .then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> { + Macro macro = get(ctx.getArgument("nombre", String.class)); + if (macro == null) return SUCCESS; + + MacroManager.INSTANCE.remove(macro); + ChatUtil.info(Component.translatable("sputnik.commands.macro.removed", + ChatFormatting.AQUA + macro.getName())); + return SUCCESS; + }))) + .then(literal("line") + .then(argument("nombre", StringArgumentType.word()).suggests(MACROS) + .then(argument("línea", StringArgumentType.greedyString()).executes(ctx -> { + Macro macro = get(ctx.getArgument("nombre", String.class)); + if (macro == null) return SUCCESS; + + String line = ctx.getArgument("línea", String.class); + macro.getLines().add(line); + MacroManager.INSTANCE.save(); + + ChatUtil.info(Component.translatable("sputnik.commands.macro.lineadded", + ChatFormatting.AQUA + macro.getName())); + return SUCCESS; + })))) + .then(literal("clear") + .then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> { + Macro macro = get(ctx.getArgument("nombre", String.class)); + if (macro == null) return SUCCESS; + + macro.getLines().clear(); + MacroManager.INSTANCE.save(); + + ChatUtil.info(Component.translatable("sputnik.commands.macro.cleared", + ChatFormatting.AQUA + macro.getName())); + return SUCCESS; + }))) + .then(literal("run") + .then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> { + Macro macro = get(ctx.getArgument("nombre", String.class)); + if (macro != null) macro.run(); + return SUCCESS; + }))); + } + + private static Macro get(String name) { + Macro macro = MacroManager.INSTANCE.getByName(name); + if (macro == null) ChatUtil.error(Component.translatable("sputnik.commands.macro.notfound", name)); + return macro; + } + + private static void list() { + List macros = MacroManager.INSTANCE.getMacros(); + + if (macros.isEmpty()) { + ChatUtil.info(Component.translatable("sputnik.commands.macro.none")); + return; + } + + ChatUtil.info(Component.translatable("sputnik.commands.macro.list", macros.size())); + for (Macro macro : macros) + ChatUtil.addMessage(ChatFormatting.GRAY + " - " + macro.getName() + + ChatFormatting.DARK_GRAY + " [" + KeyUtil.getKeyName(macro.getKey()) + "] " + + macro.getLines().size()); + } +} diff --git a/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java index 72cad26..a67cbef 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java @@ -57,7 +57,7 @@ public class PanoramaCommand extends Command { @Nullable Path dir = null; public PanoramaCommand() { - super("panorama", Component.translatable("sputnik.commands.panorama.desc").getString()); + super("panorama", "sputnik.commands.panorama.desc"); } private void takeScreenshot() { diff --git a/src/main/java/me/retucio/sputnik/command/commands/PeekCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PeekCommand.java index 1e878c3..0638843 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/PeekCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/PeekCommand.java @@ -6,7 +6,6 @@ import me.retucio.sputnik.command.args.PlayerArgumentType; import me.retucio.sputnik.util.MiscUtil; import net.minecraft.client.gui.screens.inventory.InventoryScreen; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import java.awt.*; @@ -14,7 +13,7 @@ import java.awt.*; public class PeekCommand extends Command { public PeekCommand() { - super("inventario", Component.translatable("sputnik.commands.peek.desc").getString(), "invsee", "peek", "inv"); + super("inventario", "sputnik.commands.peek.desc", "invsee", "peek", "inv"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/PluginsCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PluginsCommand.java index 9e5213e..08892ac 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/PluginsCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/PluginsCommand.java @@ -24,7 +24,7 @@ public class PluginsCommand extends Command { private boolean scanning = false; public PluginsCommand() { - super("plugins", Component.translatable("sputnik.commands.plugins.desc").getString(), "pl", "serverplugins"); + super("plugins", "sputnik.commands.plugins.desc", "pl", "serverplugins"); Sputnik.EVENT_BUS.subscribe(this); } diff --git a/src/main/java/me/retucio/sputnik/command/commands/PrefixCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PrefixCommand.java index 4ac3abe..14c85a2 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/PrefixCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/PrefixCommand.java @@ -13,7 +13,7 @@ import net.minecraft.network.chat.Component; public class PrefixCommand extends Command { public PrefixCommand() { - super("prefijo", Component.translatable("sputnik.commands.prefix.desc").getString(), "prefix"); + super("prefijo", "sputnik.commands.prefix.desc", "prefix"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/ProfileCommand.java b/src/main/java/me/retucio/sputnik/command/commands/ProfileCommand.java new file mode 100644 index 0000000..fe294c9 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/command/commands/ProfileCommand.java @@ -0,0 +1,92 @@ +package me.retucio.sputnik.command.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import me.retucio.sputnik.command.Command; +import me.retucio.sputnik.config.ConfigManager; +import me.retucio.sputnik.config.ProfileManager; +import me.retucio.sputnik.util.ChatUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; + +import java.util.List; + + +public class ProfileCommand extends Command { + + private static final SuggestionProvider PROFILES = (ctx, builder) -> + SharedSuggestionProvider.suggest(ProfileManager.INSTANCE.getProfileNames(), builder); + + public ProfileCommand() { + super("perfil", "sputnik.commands.profile.desc", "profile", "perfiles", "profiles"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .executes(ctx -> { + list(); + return SUCCESS; + }) + .then(literal("list").executes(ctx -> { + list(); + return SUCCESS; + })) + .then(literal("save") + .then(argument("nombre", StringArgumentType.word()).executes(ctx -> { + String name = ctx.getArgument("nombre", String.class); + + if (!ProfileManager.isValidName(name)) { + ChatUtil.error(Component.translatable("sputnik.commands.profile.invalidname", name)); + return SUCCESS; + } + + if (ProfileManager.INSTANCE.save(name)) { + ConfigManager.INSTANCE.save(); + ChatUtil.info(Component.translatable("sputnik.commands.profile.saved", + ChatFormatting.AQUA + name)); + } else { + ChatUtil.error(Component.translatable("sputnik.commands.profile.savefailed", name)); + } + return SUCCESS; + }))) + .then(literal("load") + .then(argument("nombre", StringArgumentType.word()).suggests(PROFILES).executes(ctx -> { + String name = ctx.getArgument("nombre", String.class); + + if (ProfileManager.INSTANCE.load(name)) + ChatUtil.info(Component.translatable("sputnik.commands.profile.loaded", + ChatFormatting.AQUA + name)); + else + ChatUtil.error(Component.translatable("sputnik.commands.profile.notfound", name)); + return SUCCESS; + }))) + .then(literal("delete") + .then(argument("nombre", StringArgumentType.word()).suggests(PROFILES).executes(ctx -> { + String name = ctx.getArgument("nombre", String.class); + + if (ProfileManager.INSTANCE.delete(name)) + ChatUtil.info(Component.translatable("sputnik.commands.profile.deleted", + ChatFormatting.AQUA + name)); + else + ChatUtil.error(Component.translatable("sputnik.commands.profile.notfound", name)); + return SUCCESS; + }))); + } + + private static void list() { + List profiles = ProfileManager.INSTANCE.getProfileNames(); + + if (profiles.isEmpty()) { + ChatUtil.info(Component.translatable("sputnik.commands.profile.none")); + return; + } + + String current = ProfileManager.INSTANCE.getCurrent(); + ChatUtil.info(Component.translatable("sputnik.commands.profile.list", profiles.size())); + for (String profile : profiles) + ChatUtil.addMessage((profile.equals(current) ? ChatFormatting.GREEN : ChatFormatting.GRAY) + " - " + profile); + } +} diff --git a/src/main/java/me/retucio/sputnik/command/commands/PurgeCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PurgeCommand.java index 3a0261e..5db98ff 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/PurgeCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/PurgeCommand.java @@ -7,7 +7,6 @@ import me.retucio.sputnik.util.interfaces.IChatComponent; import net.minecraft.client.gui.components.ChatComponent; import net.minecraft.client.multiplayer.chat.GuiMessage; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import java.awt.*; import java.util.List; @@ -15,7 +14,7 @@ import java.util.List; public class PurgeCommand extends Command { public PurgeCommand() { - super("purgar", Component.translatable("sputnik.commands.purge.desc").getString(), "purge"); + super("purgar", "sputnik.commands.purge.desc", "purge"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/RepeatCommand.java b/src/main/java/me/retucio/sputnik/command/commands/RepeatCommand.java index 9c17f9d..5db83d7 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/RepeatCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/RepeatCommand.java @@ -6,12 +6,11 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import me.retucio.sputnik.command.Command; import me.retucio.sputnik.util.ChatUtil; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; public class RepeatCommand extends Command { public RepeatCommand() { - super("repetir", Component.translatable("sputnik.commands.repeat.desc").getString(), "repeat"); + super("repetir", "sputnik.commands.repeat.desc", "repeat"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/RepeatWaitCommand.java b/src/main/java/me/retucio/sputnik/command/commands/RepeatWaitCommand.java index 0dfa221..c2bedd0 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/RepeatWaitCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/RepeatWaitCommand.java @@ -6,7 +6,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import me.retucio.sputnik.command.Command; import me.retucio.sputnik.util.ChatUtil; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -14,7 +13,7 @@ import java.util.concurrent.TimeUnit; public class RepeatWaitCommand extends Command { public RepeatWaitCommand() { - super("repesperar", Component.translatable("sputnik.commands.repeatwait.desc").getString(), "repeatwait"); + super("repesperar", "sputnik.commands.repeatwait.desc", "repeatwait"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/SaveScreenshotCommand.java b/src/main/java/me/retucio/sputnik/command/commands/SaveScreenshotCommand.java index 80290f9..296f023 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/SaveScreenshotCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/SaveScreenshotCommand.java @@ -14,7 +14,7 @@ import java.io.IOException; public class SaveScreenshotCommand extends Command { public SaveScreenshotCommand() { - super("guardarcaptura", Component.translatable("sputnik.commands.savescreenshot.desc").getString(), "savescreenshot"); + super("guardarcaptura", "sputnik.commands.savescreenshot.desc", "savescreenshot"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/SendCommand.java b/src/main/java/me/retucio/sputnik/command/commands/SendCommand.java index 884c3e6..8178b6f 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/SendCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/SendCommand.java @@ -6,7 +6,6 @@ import me.retucio.sputnik.command.Command; import me.retucio.sputnik.mixin.accessors.ClientPacketListenerAccessor; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import net.minecraft.network.chat.LastSeenMessagesTracker; import net.minecraft.network.chat.MessageSignature; import net.minecraft.network.chat.SignedMessageBody; @@ -19,7 +18,7 @@ import java.time.Instant; public class SendCommand extends Command { public SendCommand() { - super("enviar", Component.translatable("sputnik.commands.send.desc").getString(), "send"); + super("enviar", "sputnik.commands.send.desc", "send"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/SpectateCommand.java b/src/main/java/me/retucio/sputnik/command/commands/SpectateCommand.java index b5834cb..715f8d7 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/SpectateCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/SpectateCommand.java @@ -7,7 +7,6 @@ import me.retucio.sputnik.command.args.EntityArgumentType; import me.retucio.sputnik.event.input.KeyEvent; import me.retucio.sputnik.util.EntityUtil; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import net.minecraft.world.entity.Entity; import org.lwjgl.glfw.GLFW; @@ -16,7 +15,7 @@ import java.awt.*; public class SpectateCommand extends Command { public SpectateCommand() { - super("espectar", Component.translatable("sputnik.commands.spectate.desc").getString(), "spectate", "spec"); + super("espectar", "sputnik.commands.spectate.desc", "spectate", "spec"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/ToggleCommand.java b/src/main/java/me/retucio/sputnik/command/commands/ToggleCommand.java index 59b96fc..3d00c7f 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/ToggleCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/ToggleCommand.java @@ -6,7 +6,6 @@ import me.retucio.sputnik.command.args.ModuleArgumentType; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.ModuleManager; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import java.awt.*; @@ -14,7 +13,7 @@ import java.awt.*; public class ToggleCommand extends Command { public ToggleCommand() { - super("alternar", Component.translatable("sputnik.commands.toggle.desc").getString(), "toggle"); + super("alternar", "sputnik.commands.toggle.desc", "toggle"); } @Override diff --git a/src/main/java/me/retucio/sputnik/command/commands/WaitCommand.java b/src/main/java/me/retucio/sputnik/command/commands/WaitCommand.java index ed4561e..b06e5f9 100644 --- a/src/main/java/me/retucio/sputnik/command/commands/WaitCommand.java +++ b/src/main/java/me/retucio/sputnik/command/commands/WaitCommand.java @@ -6,7 +6,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import me.retucio.sputnik.command.Command; import me.retucio.sputnik.util.ChatUtil; import net.minecraft.commands.SharedSuggestionProvider; -import net.minecraft.network.chat.Component; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -17,7 +16,7 @@ public class WaitCommand extends Command { private static final ScheduledExecutorService DELAYED_EXECUTOR = new ScheduledThreadPoolExecutor(2); public WaitCommand() { - super("wait", Component.translatable("sputnik.commands.wait.desc").getString(), "esperar", "sleep", "delay"); + super("wait", "sputnik.commands.wait.desc", "esperar", "sleep", "delay"); } @Override diff --git a/src/main/java/me/retucio/sputnik/config/BinarySerializer.java b/src/main/java/me/retucio/sputnik/config/BinarySerializer.java index 0c5a597..64e3d89 100644 --- a/src/main/java/me/retucio/sputnik/config/BinarySerializer.java +++ b/src/main/java/me/retucio/sputnik/config/BinarySerializer.java @@ -14,7 +14,8 @@ import java.util.Map; public class BinarySerializer { private static final int MAGIC_NUMBER = 0x53505554; // "SPUT" - private static final int VERSION = 2; + private static final int VERSION = 3; + private static final int MIN_VERSION = 2; private static final byte TYPE_NULL = 0; private static final byte TYPE_BOOLEAN = 1; @@ -72,6 +73,8 @@ public class BinarySerializer { writeStringBooleanMap(out, config.hudVisibilities); writeStringStringMap(out, config.hudImagePaths); writeStringStringMap(out, config.friends); + writeMacros(out, config.macros); + writeString(out, config.currentProfile); if (config.searchBarPosition != null) { out.writeBoolean(true); @@ -104,9 +107,8 @@ public class BinarySerializer { "formato de archivo inválido: %s", magic)); } - // todo: sustituir por lógica de migración en vez de simplemente no aceptarlo int version = in.readInt(); - if (version != VERSION) { + if (version < MIN_VERSION || version > VERSION) { throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidversion", "versión inválida: %s", version)); } @@ -123,6 +125,9 @@ public class BinarySerializer { Map hudImagePaths = readStringStringMap(in); Map friends = readStringStringMap(in); + List macros = version >= 3 ? readMacros(in) : new ArrayList<>(); + String currentProfile = version >= 3 ? readString(in) : ""; + // leer pos. de la barra de búsqueda int[] searchBarPosition = in.readBoolean() ? new int[]{in.readInt(), in.readInt()} @@ -137,6 +142,8 @@ public class BinarySerializer { config.hudVisibilities = hudVisibilities; config.hudImagePaths = hudImagePaths; config.friends = friends; + config.macros = macros; + config.currentProfile = currentProfile == null ? "" : currentProfile; if (searchBarPosition != null) config.searchBarPosition = searchBarPosition; return config; @@ -386,6 +393,35 @@ public class BinarySerializer { return map; } + private static void writeMacros(DataOutputStream out, List macros) + throws IOException { + if (macros == null) { + out.writeInt(0); + return; + } + out.writeInt(macros.size()); + for (ClientConfig.MacroData macro : macros) { + writeString(out, macro.name()); + out.writeInt(macro.key()); + out.writeInt(macro.lines().size()); + for (String line : macro.lines()) writeString(out, line); + } + } + + private static List readMacros(DataInputStream in) throws IOException { + int size = in.readInt(); + List macros = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String name = readString(in); + int key = in.readInt(); + int lineCount = in.readInt(); + List lines = new ArrayList<>(lineCount); + for (int j = 0; j < lineCount; j++) lines.add(readString(in)); + macros.add(new ClientConfig.MacroData(name, key, lines)); + } + return macros; + } + private static void writeString(DataOutputStream out, String str) throws IOException { if (str == null) { out.writeInt(-1); diff --git a/src/main/java/me/retucio/sputnik/config/ClientConfig.java b/src/main/java/me/retucio/sputnik/config/ClientConfig.java index 0a34a77..07ca973 100644 --- a/src/main/java/me/retucio/sputnik/config/ClientConfig.java +++ b/src/main/java/me/retucio/sputnik/config/ClientConfig.java @@ -47,19 +47,31 @@ public class ClientConfig { // amigos: uuid -> nombre public Map friends = new HashMap<>(); + public List macros = new ArrayList<>(); + + // perfil cargado actualmente, vacío si es la config. suelta + public String currentProfile = ""; + public ClientConfig() { EVENT_BUS.subscribe(this); } + // mientras se aplica una config. los cambios vienen de ella, así que reescribirla es + // como poco redundante, y al cambiar de perfil pisaba lo que aún no se había aplicado + private static boolean isApplying() { + return ConfigManager.INSTANCE != null && ConfigManager.INSTANCE.isApplying(); + } + @EventListener public void onToggleModule(ToggleModuleEvent event) { + if (isApplying()) return; ConfigManager.INSTANCE.setModuleState(event.getModule()); } @SuppressWarnings("rawtypes") @EventListener public void onUpdateSetting(UpdateSettingEvent event) { - if (mc == null || !event.shouldSave()) return; + if (mc == null || !event.shouldSave() || isApplying()) return; // guardar ajustes con su respectivo tipo de valor, en formato "nombreMódulo:nombreAjuste" Object value = null; @@ -153,6 +165,7 @@ public class ClientConfig { @EventListener public void onAddFriend(FriendEvent.Add event) { + if (isApplying()) return; String uuid = event.getFriend().getUuid().toString(); String name = event.getFriend().getName(); if (!friends.containsKey(uuid)) { @@ -163,6 +176,7 @@ public class ClientConfig { @EventListener public void onRemoveFriend(FriendEvent.Remove event) { + if (isApplying()) return; friends.remove(event.getFriend().getUuid().toString()); ConfigManager.INSTANCE.markDirty(); } @@ -170,4 +184,8 @@ public class ClientConfig { public record PanelData(int x, int y, boolean extended) implements Serializable { @Serial private static final long serialVersionUID = 1L; } + + public record MacroData(String name, int key, List lines) implements Serializable { + @Serial private static final long serialVersionUID = 1L; + } } diff --git a/src/main/java/me/retucio/sputnik/config/ConfigManager.java b/src/main/java/me/retucio/sputnik/config/ConfigManager.java index ee891a1..6a9ecff 100644 --- a/src/main/java/me/retucio/sputnik/config/ConfigManager.java +++ b/src/main/java/me/retucio/sputnik/config/ConfigManager.java @@ -8,8 +8,11 @@ import me.retucio.sputnik.friend.FriendManager; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.client.ClientSettingsModule; +import me.retucio.sputnik.macro.Macro; +import me.retucio.sputnik.macro.MacroManager; import me.retucio.sputnik.module.setting.*; import me.retucio.sputnik.module.setting.settings.*; +import me.retucio.sputnik.ui.hud.HudElement; import me.retucio.sputnik.ui.hud.HudRenderer; import me.retucio.sputnik.ui.widgets.Panel; import me.retucio.sputnik.ui.widgets.panels.ExtendablePanel; @@ -25,7 +28,9 @@ import net.minecraft.network.chat.Component; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -183,6 +188,8 @@ public class ConfigManager { applySearchBarPosition(); applyExtendablePanels(); applyFriends(); + applyMacros(); + applyHudElements(); Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.applied", "ajustes aplicados")); } catch (Exception e) { @@ -199,11 +206,44 @@ public class ConfigManager { } // true mientras se está cargando la config. sobre los ajustes. lo mira KeySetting para no - // tratar las teclas guardadas como si el usuario las acabara de asignar + // tratar las teclas guardadas como si el usuario las acabara de asignar, y ClientConfig + // para no reescribir en la config. lo que se le está aplicando public boolean isApplying() { return applying; } + // sustituye la config. viva por otra y la vuelca sobre el estado actual del cliente + public void replaceConfig(ClientConfig newConfig) { + applying = true; + try { + resetLiveState(); + } finally { + applying = false; + } + + setConfig(newConfig); + loaded = false; + applyConfig(); + save(); + } + + // lo que no venga en la config. nueva tiene que quedarse en su valor por defecto, no en + // el que tuviera el perfil anterior + private void resetLiveState() { + if (ClickGui.INSTANCE != null) ClickGui.INSTANCE.closeAllSettingsPanels(); + + for (Module module : ModuleManager.INSTANCE.getModules()) { + module.setEnabled(false); + module.getSettings().forEach(Setting::reset); + } + ClientSettingsPanel.clientSettings.getSettings().forEach(Setting::reset); + + for (Friend friend : new ArrayList<>(FriendManager.INSTANCE.getFriends())) + FriendManager.INSTANCE.remove(friend); + + MacroManager.INSTANCE.setMacros(List.of()); + } + // obtener configuración actual public ClientConfig getConfig() { ensureConfig(); @@ -258,6 +298,20 @@ public class ConfigManager { markDirty(); } + public void setMacros(List macros) { + ensureConfig(); + config.macros = macros.stream() + .map(macro -> new ClientConfig.MacroData(macro.getName(), macro.getKey(), List.copyOf(macro.getLines()))) + .toList(); + markDirty(); + } + + public void setCurrentProfile(String name) { + ensureConfig(); + config.currentProfile = name; + markDirty(); + } + // ------------------ APLICAR AJUSTES ------------------ @@ -271,6 +325,26 @@ public class ConfigManager { "amigos aplicados")); } + public void applyMacros() { + MacroManager.INSTANCE.setMacros(config.macros.stream() + .map(data -> new Macro(data.name(), data.key(), data.lines())) + .toList()); + } + + // el HudRenderer coloca sus elementos al construirse, pero al cambiar de perfil ya existen + public void applyHudElements() { + if (HudRenderer.INSTANCE == null) return; + + for (HudElement element : HudRenderer.INSTANCE.getElements()) { + int[] position = config.hudPositions.get(element.getId()); + if (position != null && position.length == 2) element.setPosition(position[0], position[1]); + else element.resetPosition(); + + Boolean visible = config.hudVisibilities.get(element.getId()); + element.setVisible(visible == null || visible); + } + } + @SuppressWarnings({"rawtypes", "unchecked"}) public void applySetting(Setting setting) { String key = getSettingKey(setting); diff --git a/src/main/java/me/retucio/sputnik/config/ProfileManager.java b/src/main/java/me/retucio/sputnik/config/ProfileManager.java new file mode 100644 index 0000000..770bb77 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/config/ProfileManager.java @@ -0,0 +1,99 @@ +package me.retucio.sputnik.config; + +import me.retucio.sputnik.Sputnik; +import me.retucio.sputnik.util.I18nUtil; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + + +public class ProfileManager { + + public static final int MAX_NAME_LENGTH = 32; + + private static final File DIR = new File("sputnik/profiles"); + private static final String EXTENSION = ".dat"; + + public static ProfileManager INSTANCE; + + public List getProfileNames() { + File[] files = DIR.listFiles((dir, name) -> name.endsWith(EXTENSION)); + if (files == null) return List.of(); + + List names = new ArrayList<>(); + for (File file : files) { + String name = file.getName(); + names.add(name.substring(0, name.length() - EXTENSION.length())); + } + names.sort(Comparator.naturalOrder()); + return names; + } + + public boolean exists(String name) { + return fileOf(name).exists(); + } + + public String getCurrent() { + return ConfigManager.INSTANCE.getConfig().currentProfile; + } + + // vuelca la config. viva en el perfil y lo deja como actual + public boolean save(String name) { + if (!isValidName(name)) return false; + + if (!DIR.exists() && !DIR.mkdirs()) { + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.dir", + "no se pudo crear el directorio de perfiles")); + return false; + } + + ConfigManager.INSTANCE.setCurrentProfile(name); + try { + BinarySerializer.writeConfig(ConfigManager.INSTANCE.getConfig(), fileOf(name)); + return true; + } catch (IOException e) { + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.saving", + "error al guardar el perfil \"%s\"", name), e); + return false; + } + } + + public boolean load(String name) { + if (!exists(name)) return false; + + ClientConfig profile; + try { + profile = BinarySerializer.readConfig(fileOf(name)); + } catch (IOException e) { + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.loading", + "error al cargar el perfil \"%s\"", name), e); + return false; + } + + profile.currentProfile = name; + ConfigManager.INSTANCE.replaceConfig(profile); + return true; + } + + public boolean delete(String name) { + if (!exists(name)) return false; + if (!fileOf(name).delete()) return false; + + if (name.equals(getCurrent())) ConfigManager.INSTANCE.setCurrentProfile(""); + return true; + } + + public static boolean isValidName(String name) { + return name != null + && !name.isBlank() + && name.length() <= MAX_NAME_LENGTH + && name.matches("[a-zA-Z0-9 _-]+"); + } + + private static File fileOf(String name) { + return new File(DIR, name + EXTENSION); + } +} diff --git a/src/main/java/me/retucio/sputnik/macro/Macro.java b/src/main/java/me/retucio/sputnik/macro/Macro.java new file mode 100644 index 0000000..218c44d --- /dev/null +++ b/src/main/java/me/retucio/sputnik/macro/Macro.java @@ -0,0 +1,53 @@ +package me.retucio.sputnik.macro; + +import me.retucio.sputnik.util.ChatUtil; +import net.minecraft.client.Minecraft; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + + +public class Macro { + + private String name; + private int key; + private final List lines = new ArrayList<>(); + + private boolean searchMatch = true; + + public Macro(String name) { + this(name, GLFW.GLFW_KEY_UNKNOWN, List.of()); + } + + public Macro(String name, int key, List lines) { + this.name = name; + this.key = key; + this.lines.addAll(lines); + } + + // cada línea se manda como si el usuario la hubiera escrito en el chat, así que valen + // comandos del mod, comandos del servidor y mensajes normales + public void run() { + if (Minecraft.getInstance().player == null) return; + for (String line : lines) { + if (!line.isBlank()) ChatUtil.simulateChatMessage(line); + } + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public int getKey() { return key; } + public void setKey(int key) { this.key = key; } + + public List getLines() { return lines; } + + public void setLines(List lines) { + this.lines.clear(); + this.lines.addAll(lines); + } + + public boolean isSearchMatch() { return searchMatch; } + public void setSearchMatch(boolean searchMatch) { this.searchMatch = searchMatch; } +} diff --git a/src/main/java/me/retucio/sputnik/macro/MacroManager.java b/src/main/java/me/retucio/sputnik/macro/MacroManager.java new file mode 100644 index 0000000..0469dc7 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/macro/MacroManager.java @@ -0,0 +1,60 @@ +package me.retucio.sputnik.macro; + +import me.retucio.sputnik.config.ConfigManager; +import net.minecraft.client.Minecraft; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + + +public class MacroManager { + + public static MacroManager INSTANCE; + + private final List macros = new ArrayList<>(); + + public boolean add(Macro macro) { + if (getByName(macro.getName()) != null) return false; + macros.add(macro); + save(); + return true; + } + + public void remove(Macro macro) { + macros.remove(macro); + save(); + } + + public Macro getByName(String name) { + for (Macro macro : macros) { + if (macro.getName().equalsIgnoreCase(name)) return macro; + } + return null; + } + + public boolean onKeyPress(int key) { + if (key == GLFW.GLFW_KEY_UNKNOWN || Minecraft.getInstance().gui.screen() != null) return false; + + boolean ran = false; + for (Macro macro : new ArrayList<>(macros)) { + if (macro.getKey() != key) continue; + macro.run(); + ran = true; + } + return ran; + } + + public void setMacros(List macros) { + this.macros.clear(); + this.macros.addAll(macros); + } + + public void save() { + if (ConfigManager.INSTANCE != null) ConfigManager.INSTANCE.setMacros(macros); + } + + public List getMacros() { + return macros; + } +} diff --git a/src/main/java/me/retucio/sputnik/module/Module.java b/src/main/java/me/retucio/sputnik/module/Module.java index aaec352..e75c2e3 100644 --- a/src/main/java/me/retucio/sputnik/module/Module.java +++ b/src/main/java/me/retucio/sputnik/module/Module.java @@ -158,6 +158,7 @@ public class Module { // onDisable no miraba notify.getValue(), así que avisaba aunque estuviera desactivado private void notifyToggle(boolean on) { if (!notify.getValue()) return; + if (ConfigManager.INSTANCE != null && ConfigManager.INSTANCE.isApplying()) return; String key = on ? "sputnik.module.toggled.on" : "sputnik.module.toggled.off"; Component message = Component.translatable(key, getDisplayName()); diff --git a/src/main/java/me/retucio/sputnik/module/ModuleManager.java b/src/main/java/me/retucio/sputnik/module/ModuleManager.java index d3c6c22..56fd1f2 100644 --- a/src/main/java/me/retucio/sputnik/module/ModuleManager.java +++ b/src/main/java/me/retucio/sputnik/module/ModuleManager.java @@ -142,6 +142,7 @@ public class ModuleManager { private void addNetwork() { add(new BungeecordSpoofer()); add(new LogoutSpots()); + add(new PacketCanceller()); add(new PacketDelay()); add(new PacketMine()); add(new Reconnect()); diff --git a/src/main/java/me/retucio/sputnik/module/modules/camera/Autism.java b/src/main/java/me/retucio/sputnik/module/modules/camera/Autism.java index dbc1ee4..0e26d67 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/camera/Autism.java +++ b/src/main/java/me/retucio/sputnik/module/modules/camera/Autism.java @@ -1,12 +1,10 @@ package me.retucio.sputnik.module.modules.camera; -import com.github.retucio.neutrino.EventListener; -import me.retucio.sputnik.event.network.PacketEvent; import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.setting.settings.EnumSetting; import me.retucio.sputnik.module.setting.settings.NumberSetting; -import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import me.retucio.sputnik.util.RotationUtil; /** @@ -47,18 +45,7 @@ public class Autism extends Module { if (direction.is(RotationDirection.COUNTERCLOCKWISE)) yaw -= interval.getFloatValue(); - mc.getConnection().send(new ServerboundMovePlayerPacket.Rot( - yaw, - mc.player.getXRot(), - mc.player.onGround(), - mc.player.horizontalCollision - )); - } - - @EventListener - private void onPacketSend(PacketEvent.Send event) { - if (event.getPacket() instanceof ServerboundMovePlayerPacket.Rot packet - && packet.getYRot(yaw) != yaw) event.cancel(); + RotationUtil.requestYaw(RotationUtil.HIGHEST, yaw, RotationUtil.Mode.SILENT); } private enum RotationDirection { diff --git a/src/main/java/me/retucio/sputnik/module/modules/camera/Rotations.java b/src/main/java/me/retucio/sputnik/module/modules/camera/Rotations.java index bde6508..6d82bf0 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/camera/Rotations.java +++ b/src/main/java/me/retucio/sputnik/module/modules/camera/Rotations.java @@ -6,6 +6,7 @@ import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.setting.settings.BooleanSetting; import me.retucio.sputnik.module.setting.settings.NumberSetting; +import me.retucio.sputnik.util.RotationUtil; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; /** @@ -30,8 +31,7 @@ public class Rotations extends Module { public void onTick() { if (mc.player == null) return; - mc.player.setYRot(yaw.getFloatValue()); - mc.player.setXRot(pitch.getFloatValue()); + RotationUtil.request(RotationUtil.HIGHEST, yaw.getFloatValue(), pitch.getFloatValue(), RotationUtil.Mode.CLIENT); if (serverSide.getValue()) mc.player.connection.send( diff --git a/src/main/java/me/retucio/sputnik/module/modules/combat/CrystalAura.java b/src/main/java/me/retucio/sputnik/module/modules/combat/CrystalAura.java index cb745c7..ec80d7a 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/combat/CrystalAura.java +++ b/src/main/java/me/retucio/sputnik/module/modules/combat/CrystalAura.java @@ -234,11 +234,22 @@ public class CrystalAura extends Module { private void doBreak() { if (breakTimer > 0) return; + + List crystals = new ArrayList<>(); for (Entity entity : mc.level.getEntities().getAll()) { if (!(entity instanceof EndCrystal) || entity.distanceTo(mc.player) > mc.player.entityInteractionRange()) continue; - if (rotateToBreak.getValue()) EntityUtil.lookAt(entity.getEyePosition()); - mc.gameMode.attack(mc.player, entity); + crystals.add(entity); } + + if (!crystals.isEmpty()) { + Runnable attack = () -> crystals.forEach(crystal -> mc.gameMode.attack(mc.player, crystal)); + + // solo se puede apuntar a uno, así que se rompen todos rotando solo al primero + if (rotateToBreak.getValue()) + RotationUtil.lookAt(RotationUtil.HIGH, crystals.getFirst().getEyePosition(), attack); + else attack.run(); + } + breakTimer = breakDelay.getIntValue(); } @@ -254,8 +265,11 @@ public class CrystalAura extends Module { return; } - if (rotateToPlace.getValue()) EntityUtil.lookAt(Vec3.atCenterOf(result.getBlockPos())); - mc.gameMode.startPrediction(mc.level, (sequence) -> new ServerboundUseItemOnPacket(hand, result, sequence)); + Runnable place = () -> mc.gameMode.startPrediction(mc.level, (sequence) -> new ServerboundUseItemOnPacket(hand, result, sequence)); + + if (rotateToPlace.getValue()) + RotationUtil.lookAt(RotationUtil.HIGH, Vec3.atCenterOf(result.getBlockPos()), place); + else place.run(); } private void placeObby(BlockPos pos) { @@ -273,8 +287,11 @@ public class CrystalAura extends Module { BlockPos supportPos = pos.below(); BlockHitResult result = new BlockHitResult(Vec3.atCenterOf(supportPos), Direction.UP, supportPos, false); - if (rotateToPlace.getValue()) EntityUtil.lookAt(Vec3.atCenterOf(pos)); - mc.gameMode.startPrediction(mc.level, (sequence) -> new ServerboundUseItemOnPacket(hand, result, sequence)); + Runnable place = () -> mc.gameMode.startPrediction(mc.level, (sequence) -> new ServerboundUseItemOnPacket(hand, result, sequence)); + + if (rotateToPlace.getValue()) + RotationUtil.lookAt(RotationUtil.HIGH, Vec3.atCenterOf(pos), place); + else place.run(); } private void doAutoSelect(Item item) { diff --git a/src/main/java/me/retucio/sputnik/module/modules/combat/SpearKill.java b/src/main/java/me/retucio/sputnik/module/modules/combat/SpearKill.java index eac66c0..2b78f15 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/combat/SpearKill.java +++ b/src/main/java/me/retucio/sputnik/module/modules/combat/SpearKill.java @@ -5,6 +5,7 @@ import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.setting.settings.BooleanSetting; import me.retucio.sputnik.module.setting.settings.NumberSetting; +import me.retucio.sputnik.util.RotationUtil; import net.minecraft.tags.ItemTags; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; @@ -73,13 +74,11 @@ public class SpearKill extends Module { float yaw = (float) (Math.toDegrees(Math.atan2(toTarget.z, toTarget.x)) - 90.0); float pitch = (float) -Math.toDegrees(Math.asin(toTarget.y)); - mc.player.setYRot(yaw); - mc.player.setYHeadRot(yaw); - mc.player.setXRot(pitch); + RotationUtil.request(RotationUtil.HIGH, yaw, pitch, RotationUtil.Mode.CLIENT); if (heldTicks >= 10) { double lungeSpeed = speed.getValue(); - Vec3 viewDir = Vec3.directionFromRotation(mc.player.getXRot(), mc.player.getYRot()); + Vec3 viewDir = Vec3.directionFromRotation(pitch, yaw); mc.player.setSprinting(true); mc.player.setDeltaMovement(viewDir.scale(lungeSpeed)); } diff --git a/src/main/java/me/retucio/sputnik/module/modules/movement/InfiniteElytra.java b/src/main/java/me/retucio/sputnik/module/modules/movement/InfiniteElytra.java index f6ea6ea..9240153 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/movement/InfiniteElytra.java +++ b/src/main/java/me/retucio/sputnik/module/modules/movement/InfiniteElytra.java @@ -5,6 +5,7 @@ import me.retucio.sputnik.event.interact.UseItemEvent; import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.setting.settings.NumberSetting; +import me.retucio.sputnik.util.RotationUtil; import net.minecraft.world.item.Items; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -99,7 +100,7 @@ public class InfiniteElytra extends Module { } } - mc.player.setXRot(pitch); + RotationUtil.requestPitch(RotationUtil.NORMAL, pitch, RotationUtil.Mode.CLIENT); } @EventListener @@ -107,7 +108,7 @@ public class InfiniteElytra extends Module { if (mc.player == null) return; if (mc.player.getItemInHand(event.getHand()).getItem() == Items.FIREWORK_ROCKET && !mc.player.isSpectator() && mc.player.isFallFlying()) { pitch = pitchUp.getFloatValue(); - mc.player.setXRot(pitch); + RotationUtil.requestPitch(RotationUtil.NORMAL, pitch, RotationUtil.Mode.CLIENT); state = FlyState.PITCHING_DOWN; lowestY = mc.player.getY(); } diff --git a/src/main/java/me/retucio/sputnik/module/modules/movement/Scaffold.java b/src/main/java/me/retucio/sputnik/module/modules/movement/Scaffold.java index 6120a5b..41f48a7 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/movement/Scaffold.java +++ b/src/main/java/me/retucio/sputnik/module/modules/movement/Scaffold.java @@ -334,8 +334,8 @@ public class Scaffold extends Module { if (NetworkUtil.placeBlock( pos, InteractionHand.MAIN_HAND, slot, - rotate.getValue(), - rotate.getValue(), + rotate.getValue() ? RotationUtil.Mode.CLIENT : null, + RotationUtil.NORMAL, swingHand.getValue(), true, true diff --git a/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java b/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java new file mode 100644 index 0000000..0728328 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java @@ -0,0 +1,44 @@ +package me.retucio.sputnik.module.modules.network; + +import com.github.retucio.neutrino.EventListener; +import me.retucio.sputnik.event.network.PacketEvent; +import me.retucio.sputnik.module.Category; +import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.setting.settings.ListSetting; +import me.retucio.sputnik.util.Lists; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketType; +import org.jspecify.annotations.NonNull; + + +public class PacketCanceller extends Module { + + private final ListSetting>> c2s = sgGeneral.add(new ListSetting( + "cliente > servidor", "paquetes a cancelar, dirección servidor", + Lists.c2sPacketList, Lists.allFalse(Lists.c2sPacketList), Lists.c2sPacketNames + )); + + private final ListSetting>> s2c = sgGeneral.add(new ListSetting( + "servidor > cliente", "paquetes a cancelar, dirección cliente", + Lists.s2cPacketList, Lists.allFalse(Lists.s2cPacketList), Lists.s2cPacketNames + )); + + public PacketCanceller() { + super("cancelador de paquetes", "cancela los paquetes seleccionados", Category.NETWORK); + } + + + @EventListener + private void onPacketSend(PacketEvent.Send event) { + if (c2s.isEnabled(event.getPacket().type())) { + event.cancel(); + } + } + + @EventListener + private void onPacketReceive(PacketEvent.Receive event) { + if (s2c.isEnabled(event.getPacket().type())) { + event.cancel(); + } + } +} diff --git a/src/main/java/me/retucio/sputnik/module/modules/network/PacketDelay.java b/src/main/java/me/retucio/sputnik/module/modules/network/PacketDelay.java index fde9045..c5d7677 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/network/PacketDelay.java +++ b/src/main/java/me/retucio/sputnik/module/modules/network/PacketDelay.java @@ -120,8 +120,6 @@ public class PacketDelay extends Module { @EventListener private void onJoinWorld(JoinWorldEvent event) { - // el aviso iba sin condición, así que el módulo se apagaba solo al entrar - // a cualquier mundo, servidores incluidos if (!MiscUtil.isSingleplayer()) return; ChatUtil.error(Component.translatable("sputnik.module.packetdelay.singleplayer")); diff --git a/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java b/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java index bc47e73..2a5ad05 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java +++ b/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java @@ -11,7 +11,7 @@ import me.retucio.sputnik.module.setting.settings.ColorSetting; import me.retucio.sputnik.module.setting.settings.EnumSetting; import me.retucio.sputnik.module.setting.settings.NumberSetting; import me.retucio.sputnik.util.ChatUtil; -import me.retucio.sputnik.util.EntityUtil; +import me.retucio.sputnik.util.RotationUtil; import me.retucio.sputnik.util.render.RenderUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -278,14 +278,18 @@ public class PacketMine extends Module { return false; } - rotate(); + Runnable mine = () -> { + mc.gameMode.startPrediction(mc.level, sequence -> + new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, dir, sequence)); + mc.gameMode.startPrediction(mc.level, sequence -> + new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, pos, dir, sequence)); + }; + + RotationUtil.Mode rotation = getRotationMode(); + if (rotation == null) mine.run(); + else RotationUtil.lookAt(RotationUtil.NORMAL, Vec3.atLowerCornerOf(pos), 0, rotation, mine); - mc.gameMode.startPrediction(mc.level, sequence -> - new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, dir, sequence)); mining = true; - mc.gameMode.startPrediction(mc.level, sequence -> - new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, pos, dir, sequence)); - return true; } @@ -303,13 +307,12 @@ public class PacketMine extends Module { ); } - private void rotate() { - Vec3 pos = Vec3.atLowerCornerOf(this.pos); - switch (rotate.getValue()) { - case CLIENT -> EntityUtil.lookAtClient(pos); - case SERVER -> EntityUtil.lookAtServer(pos); - case BOTH -> EntityUtil.lookAt(pos); - } + private RotationUtil.Mode getRotationMode() { + return switch (rotate.getValue()) { + case CLIENT, BOTH -> RotationUtil.Mode.CLIENT; + case SERVER -> RotationUtil.Mode.SILENT; + case NONE -> null; + }; } public boolean shouldRetry() { diff --git a/src/main/java/me/retucio/sputnik/module/modules/player/NoFall.java b/src/main/java/me/retucio/sputnik/module/modules/player/NoFall.java index 91320b0..33bd0bc 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/player/NoFall.java +++ b/src/main/java/me/retucio/sputnik/module/modules/player/NoFall.java @@ -11,6 +11,7 @@ import me.retucio.sputnik.module.setting.settings.NumberSetting; import me.retucio.sputnik.util.ChatUtil; import me.retucio.sputnik.util.InventoryUtil; import me.retucio.sputnik.util.Lists; +import me.retucio.sputnik.util.RotationUtil; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; @@ -136,23 +137,23 @@ public class NoFall extends Module { return; } - // mirar abajo - mc.player.setXRot(180); - int slot = mc.player.getInventory().findSlotMatchingItem(mlgItem); mc.player.getInventory().setSelectedSlot(slot); // usar bloque if (mlgItem.getItem() instanceof BlockItem) { + RotationUtil.requestPitch(RotationUtil.HIGH, 90, RotationUtil.Mode.CLIENT); shouldUse = true; placed = true; return; } - // usar ítem - InteractionResult didPlace = mc.gameMode.useItem(mc.player, InteractionHand.MAIN_HAND); - if (didPlace.consumesAction()) - placed = true; + // mirar abajo y usar el ítem cuando el servidor lo vea + RotationUtil.request(RotationUtil.HIGH, Float.NaN, 90, 0, RotationUtil.Mode.CLIENT, () -> { + InteractionResult didPlace = mc.gameMode.useItem(mc.player, InteractionHand.MAIN_HAND); + if (didPlace.consumesAction()) + placed = true; + }); } } } diff --git a/src/main/java/me/retucio/sputnik/module/modules/world/Racist.java b/src/main/java/me/retucio/sputnik/module/modules/world/Racist.java index 601b568..0c27567 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/world/Racist.java +++ b/src/main/java/me/retucio/sputnik/module/modules/world/Racist.java @@ -3,6 +3,7 @@ package me.retucio.sputnik.module.modules.world; import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.util.EntityUtil; +import me.retucio.sputnik.util.RotationUtil; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.monster.EnderMan; @@ -52,10 +53,6 @@ public class Racist extends Module { } private void lookAtEnderman(EnderMan enderman) { - double yaw = EntityUtil.getYaw(enderman); - double pitch = EntityUtil.getPitch(enderman, EntityUtil.Target.HEAD); - - mc.player.setYRot((float) yaw); - mc.player.setXRot((float) pitch); + RotationUtil.lookAt(RotationUtil.LOW, enderman, EntityUtil.Target.HEAD, 0, RotationUtil.Mode.CLIENT); } } diff --git a/src/main/java/me/retucio/sputnik/ui/hud/HudEditor.java b/src/main/java/me/retucio/sputnik/ui/hud/HudEditor.java new file mode 100644 index 0000000..14661f2 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/hud/HudEditor.java @@ -0,0 +1,155 @@ +package me.retucio.sputnik.ui.hud; + +import me.retucio.sputnik.config.ConfigManager; +import me.retucio.sputnik.module.ModuleManager; +import me.retucio.sputnik.module.modules.client.Hud; +import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.KeyUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + + +// editor de elementos del hud, compartido entre su propia pantalla y la pestaña de la interfaz +public class HudEditor { + + public static HudEditor INSTANCE; + + private static final Minecraft mc = Minecraft.getInstance(); + + private final List elements = new ArrayList<>(); + @Nullable private HudElement selected = null; + + private boolean dragging = false; + private int dragX, dragY; + + public void setElements(List elements) { + this.elements.clear(); + this.elements.addAll(elements); + } + + public void render(GuiGraphicsExtractor gui, int mouseX, int mouseY) { + Hud hud = ModuleManager.INSTANCE.getModuleByClass(Hud.class); + + if (hud.axialMovement.getValue() && dragging && selected != null) { + int centerX = mc.getWindow().getGuiScaledWidth() / 2; + if (Math.abs(centerX - mouseX) < hud.axisOffset.getValue()) { + dragX = mouseX - centerX + selected.getW() / 2; + gui.verticalLine(centerX, 0, mc.getWindow().getGuiScaledHeight(), Colors.CELESTE.getRGB()); + } + + int centerY = mc.getWindow().getGuiScaledHeight() / 2; + if (Math.abs(centerY - mouseY) < hud.axisOffset.getValue()) { + dragY = mouseY - centerY + selected.getH() / 2; + gui.horizontalLine(0, mc.getWindow().getGuiScaledWidth(), centerY, Colors.CELESTE.getRGB()); + } + } + + for (HudElement element : elements) element.renderInEditor(gui, hud); + + if (dragging) return; + + for (HudElement element : elements) { + if (!element.isVisible() || !element.isHovered(mouseX, mouseY)) continue; + + List tooltip = element.getTooltip(); + if (!tooltip.isEmpty()) + gui.setComponentTooltipForNextFrame(mc.font, tooltip, mouseX + 7, mouseY + 20); + break; + } + } + + public boolean mouseClicked(int mouseX, int mouseY, int button) { + for (HudElement element : elements) { + if (!element.isHovered(mouseX, mouseY)) continue; + selected = element; + + if (button == 0) { + dragging = true; + dragX = mouseX - element.getX(); + dragY = mouseY - element.getY(); + + elements.remove(element); + elements.add(element); + return true; + } + + if (button == 1) { + if (KeyUtil.isShiftDown()) selected.resetPosition(); + else selected.setVisible(!selected.isVisible()); + + saveElement(selected); + return true; + } + } + + return false; + } + + public boolean mouseDragged(int mouseX, int mouseY, int button) { + if (!dragging || selected == null || button != 0) return false; + + int width = mc.getWindow().getGuiScaledWidth(); + int height = mc.getWindow().getGuiScaledHeight(); + + selected.setPosition( + Math.clamp(mouseX - dragX, 1, width - selected.getW() - 1), + Math.clamp(mouseY - dragY, 1, height - selected.getH() - 1)); + return true; + } + + public boolean mouseReleased(int button) { + if (dragging && button == 0) { + dragging = false; + if (selected != null) saveElement(selected); + return true; + } + + selected = null; + return false; + } + + public void onKey(int key) { + Hud hud = ModuleManager.INSTANCE.getModuleByClass(Hud.class); + if (!hud.arrowMovement.getValue() || selected == null) return; + + int offset = hud.arrowOffset.getIntValue(); + switch (key) { + case GLFW.GLFW_KEY_UP -> selected.setY(Math.max(1, selected.getY() - offset)); + case GLFW.GLFW_KEY_DOWN -> selected.setY(Math.min( + mc.getWindow().getGuiScaledHeight() - selected.getH() + 1, selected.getY() + offset)); + case GLFW.GLFW_KEY_LEFT -> selected.setX(Math.max(1, selected.getX() - offset)); + case GLFW.GLFW_KEY_RIGHT -> selected.setX(Math.min( + mc.getWindow().getGuiScaledWidth() - selected.getW() + 1, selected.getX() + offset)); + } + } + + public void saveAll() { + for (HudElement element : elements) writeElement(element); + ConfigManager.INSTANCE.save(); + } + + private void saveElement(HudElement element) { + writeElement(element); + ConfigManager.INSTANCE.save(); + } + + private static void writeElement(HudElement element) { + ConfigManager.INSTANCE.setHudPosition(element.getId(), element.getX(), element.getY()); + ConfigManager.INSTANCE.setHudVisibility(element.getId(), element.isVisible()); + } + + public boolean isSelected(HudElement element) { + return selected == element; + } + + public void unselect() { + selected = null; + dragging = false; + } +} diff --git a/src/main/java/me/retucio/sputnik/ui/hud/HudEditorScreen.java b/src/main/java/me/retucio/sputnik/ui/hud/HudEditorScreen.java index ac53548..7835456 100644 --- a/src/main/java/me/retucio/sputnik/ui/hud/HudEditorScreen.java +++ b/src/main/java/me/retucio/sputnik/ui/hud/HudEditorScreen.java @@ -1,200 +1,77 @@ package me.retucio.sputnik.ui.hud; +import com.github.retucio.neutrino.EventListener; import me.retucio.sputnik.Sputnik; -import me.retucio.sputnik.config.ConfigManager; import me.retucio.sputnik.event.input.KeyEvent; -import me.retucio.sputnik.module.ModuleManager; -import me.retucio.sputnik.module.modules.client.Hud; import me.retucio.sputnik.ui.widgets.panels.settings.ClientSettingsPanel; import me.retucio.sputnik.util.Colors; -import me.retucio.sputnik.util.KeyUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; import org.lwjgl.glfw.GLFW; -import java.util.ArrayList; -import java.util.List; - public class HudEditorScreen extends Screen { public static HudEditorScreen INSTANCE; private final Minecraft mc = Minecraft.getInstance(); - private final Hud hud = ModuleManager.INSTANCE.getModuleByClass(Hud.class); - - private final List elements = new ArrayList<>(); - @Nullable private HudElement selected = null; - - private boolean dragging = false; - private int dragX, dragY; public HudEditorScreen() { super(Component.translatable("sputnik.hud.editor.title")); Sputnik.EVENT_BUS.subscribe(this); } - public void setElements(List elements) { - this.elements.clear(); - this.elements.addAll(elements); - } - @Override public void extractRenderState(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { gui.fill(0, 0, this.width, this.height, Colors.hudEditorScreenBackgroundColor.getRGB()); - // instrucciones gui.centeredText(mc.font, title, this.width / 2, this.height / 2 - mc.font.lineHeight, -1); gui.centeredText(mc.font, Component.translatable("sputnik.hud.editor.instructions"), this.width / 2, this.height / 2 + mc.font.lineHeight, Colors.instructionsTextColor.getRGB()); - // movimiento axial - if (hud.axialMovement.getValue()) { - int centerX = mc.getWindow().getGuiScaledWidth() / 2; - if (dragging && selected != null && Math.abs(centerX - mouseX) < hud.axisOffset.getValue()) { - dragX = mouseX - centerX + selected.getW() / 2; - gui.verticalLine(centerX, 0, mc.getWindow().getGuiScaledHeight(), Colors.CELESTE.getRGB()); - } - - int centerY = mc.getWindow().getGuiScaledHeight() / 2; - if (dragging && selected != null && Math.abs(centerY - mouseY) < hud.axisOffset.getValue()) { - dragY = mouseY - centerY + selected.getH() / 2; - gui.horizontalLine(0, mc.getWindow().getGuiScaledWidth(), centerY, Colors.CELESTE.getRGB()); - } - } - - // elementos - for (HudElement element : elements) { - element.renderInEditor(gui, hud); - } - - // tooltips - if (!dragging) { - for (HudElement element : elements) { - if (!element.isVisible()) continue; - - if (element.isHovered(mouseX, mouseY)) { - List tooltip = element.getTooltip(); - if (!tooltip.isEmpty()) - gui.setComponentTooltipForNextFrame(mc.font, tooltip, mouseX + 7, mouseY + 20); - break; - } - } - } + HudEditor.INSTANCE.render(gui, mouseX, mouseY); super.extractRenderState(gui, mouseX, mouseY, delta); } @Override public boolean mouseClicked(@NonNull MouseButtonEvent click, boolean doubled) { - int mouseX = (int) click.x(); - int mouseY = (int) click.y(); - - for (HudElement element : elements) { - if (element.isHovered(mouseX, mouseY)) { - selected = element; - - if (click.button() == 0) { - dragging = true; - dragX = mouseX - element.getX(); - dragY = mouseY - element.getY(); - - elements.remove(element); - elements.add(element); - return true; - - } else if (click.button() == 1) { - if (KeyUtil.isShiftDown()) { - selected.resetPosition(); - } else { - selected.setVisible(!selected.isVisible()); - } - saveElementToConfig(selected); - return true; - } - } - } - + if (HudEditor.INSTANCE.mouseClicked((int) click.x(), (int) click.y(), click.button())) return true; return super.mouseClicked(click, doubled); } @Override public boolean mouseDragged(@NonNull MouseButtonEvent click, double deltaX, double deltaY) { - if (!dragging || selected == null || click.button() != 0) - return super.mouseDragged(click, deltaX, deltaY); - - int mouseX = (int) click.x(); - int mouseY = (int) click.y(); - - int newX = mouseX - dragX; - int newY = mouseY - dragY; - - newX = Math.clamp(newX, 1, width - selected.getW() - 1); - newY = Math.clamp(newY, 1, height - selected.getH() - 1); - - selected.setPosition(newX, newY); - return true; + if (HudEditor.INSTANCE.mouseDragged((int) click.x(), (int) click.y(), click.button())) return true; + return super.mouseDragged(click, deltaX, deltaY); } @Override public boolean mouseReleased(@NonNull MouseButtonEvent click) { - if (dragging && click.button() == 0) { - dragging = false; - if (selected != null) - saveElementToConfig(selected); - return true; - } - - selected = null; + if (HudEditor.INSTANCE.mouseReleased(click.button())) return true; return super.mouseReleased(click); } // he sustituído el método del súper por un evento para ver si así se podían // registrar dos teclas a la vez, pero resulta que no :P + @EventListener public void onKey(KeyEvent event) { - if (!(mc.gui.screen() == INSTANCE)) return; + if (mc.gui.screen() != INSTANCE) return; if (event.getAction() == GLFW.GLFW_RELEASE) return; event.cancel(); - // guardar y salir if (event.getKey() == GLFW.GLFW_KEY_ESCAPE) { - saveAllElementsToConfig(); - selected = null; + HudEditor.INSTANCE.saveAll(); + HudEditor.INSTANCE.unselect(); mc.gui.setScreen(null); + return; } - // movimiento de los elementos con las teclas de las flechas - else if (hud.arrowMovement.getValue() && selected != null) { - int offset = hud.arrowOffset.getIntValue(); - if (event.getKey() == GLFW.GLFW_KEY_UP) { - selected.setY(Math.max(1, selected.getY() - offset)); - } - if (event.getKey() == GLFW.GLFW_KEY_DOWN) { - selected.setY(Math.min(mc.getWindow().getGuiScaledHeight() - selected.getH() + 1, selected.getY() + offset)); - } - if (event.getKey() == GLFW.GLFW_KEY_LEFT) { - selected.setX(Math.max(1, selected.getX() - offset)); - } - if (event.getKey() == GLFW.GLFW_KEY_RIGHT) { - selected.setX(Math.min(mc.getWindow().getGuiScaledWidth() - selected.getW() + 1, selected.getX() + offset)); - } - } - } - private void saveElementToConfig(HudElement element) { - ConfigManager.INSTANCE.setHudPosition(element.getId(), element.getX(), element.getY()); - ConfigManager.INSTANCE.setHudVisibility(element.getId(), element.isVisible()); - ConfigManager.INSTANCE.save(); - } - - private void saveAllElementsToConfig() { - for (HudElement element : elements) { - saveElementToConfig(element); - } + HudEditor.INSTANCE.onKey(event.getKey()); } @Override @@ -203,8 +80,4 @@ public class HudEditorScreen extends Screen { super.extractBlurredBackground(gui); } } - - public boolean isSelected(HudElement element) { - return selected == element; - } -} \ No newline at end of file +} diff --git a/src/main/java/me/retucio/sputnik/ui/hud/HudRenderer.java b/src/main/java/me/retucio/sputnik/ui/hud/HudRenderer.java index 7cc4136..522bfb8 100644 --- a/src/main/java/me/retucio/sputnik/ui/hud/HudRenderer.java +++ b/src/main/java/me/retucio/sputnik/ui/hud/HudRenderer.java @@ -4,6 +4,7 @@ import me.retucio.sputnik.config.ConfigManager; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.client.Hud; import me.retucio.sputnik.ui.hud.elements.*; +import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.widgets.misc.NotificationWidget; import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; @@ -49,7 +50,7 @@ public class HudRenderer { addElement(new TotemsElement(), positions, visibilities); addElement(new ArmorElement(), positions, visibilities); - HudEditorScreen.INSTANCE.setElements(elements); + HudEditor.INSTANCE.setElements(elements); } private void addElement(HudElement element, Map positions, Map visibilities) { @@ -130,7 +131,8 @@ public class HudRenderer { || !hud.isEnabled() || (mc.debugEntries.isOverlayVisible() && !hud.showOnF3.getValue()) || (mc.gui.screen() instanceof ChatScreen && !hud.showOnChat.getValue()) - || mc.gui.screen() instanceof HudEditorScreen; + || mc.gui.screen() instanceof HudEditorScreen + || (ClickGui.INSTANCE != null && mc.gui.screen() == ClickGui.INSTANCE && ClickGui.INSTANCE.isOnHudTab()); } public void pushNotification(String title, String desc) { diff --git a/src/main/java/me/retucio/sputnik/ui/hud/ImageHudElement.java b/src/main/java/me/retucio/sputnik/ui/hud/ImageHudElement.java index 4865c7d..b2b7854 100644 --- a/src/main/java/me/retucio/sputnik/ui/hud/ImageHudElement.java +++ b/src/main/java/me/retucio/sputnik/ui/hud/ImageHudElement.java @@ -109,7 +109,7 @@ public abstract class ImageHudElement extends HudElement { protected void drawEditorBackground(GuiGraphicsExtractor gui) { int bgColor = visible ? Colors.visibleHudElementColor.getRGB() : Colors.disabledHudElementColor.getRGB(); - int outlineColor = HudEditorScreen.INSTANCE != null && HudEditorScreen.INSTANCE.isSelected(this) + int outlineColor = HudEditor.INSTANCE != null && HudEditor.INSTANCE.isSelected(this) ? Colors.selectedHudElementOutlineColor.getRGB() : Colors.unselectedHudElementOutlineColor.getRGB(); diff --git a/src/main/java/me/retucio/sputnik/ui/hud/TextHudElement.java b/src/main/java/me/retucio/sputnik/ui/hud/TextHudElement.java index 05b1d8a..1b6146d 100644 --- a/src/main/java/me/retucio/sputnik/ui/hud/TextHudElement.java +++ b/src/main/java/me/retucio/sputnik/ui/hud/TextHudElement.java @@ -37,7 +37,7 @@ public abstract class TextHudElement extends HudElement { protected void drawEditorBackground(GuiGraphicsExtractor gui) { int bgColor = visible ? Colors.visibleHudElementColor.getRGB() : Colors.disabledHudElementColor.getRGB(); - int outlineColor = HudEditorScreen.INSTANCE != null && HudEditorScreen.INSTANCE.isSelected(this) + int outlineColor = HudEditor.INSTANCE != null && HudEditor.INSTANCE.isSelected(this) ? Colors.selectedHudElementOutlineColor.getRGB() : Colors.unselectedHudElementOutlineColor.getRGB(); diff --git a/src/main/java/me/retucio/sputnik/ui/screen/ClickGui.java b/src/main/java/me/retucio/sputnik/ui/screen/ClickGui.java index 28bf648..dd91887 100644 --- a/src/main/java/me/retucio/sputnik/ui/screen/ClickGui.java +++ b/src/main/java/me/retucio/sputnik/ui/screen/ClickGui.java @@ -24,8 +24,13 @@ import me.retucio.sputnik.ui.widgets.panels.SettingsPanel; import me.retucio.sputnik.ui.widgets.Button; import me.retucio.sputnik.ui.widgets.Panel; import me.retucio.sputnik.ui.widgets.Widget; +import me.retucio.sputnik.ui.hud.HudEditor; +import me.retucio.sputnik.ui.screen.tabs.MacrosTab; +import me.retucio.sputnik.ui.screen.tabs.ProfilesTab; import me.retucio.sputnik.ui.widgets.misc.ScrollBarWidget; import me.retucio.sputnik.ui.widgets.misc.SearchBarWidget; +import me.retucio.sputnik.ui.widgets.misc.TabBarWidget; +import me.retucio.sputnik.util.Colors; import me.retucio.sputnik.ui.widgets.panels.settings.FriendSettingsPanel; import me.retucio.sputnik.util.KeyUtil; @@ -70,6 +75,10 @@ public class ClickGui extends Screen { private final SearchBarWidget searchBar = new SearchBarWidget(320, 10, 320, 20); private final List miscWidgets = List.of(scrollBar, searchBar); + private final TabBarWidget tabBar = new TabBarWidget(this::onTabLeft); + private final ProfilesTab profilesTab = new ProfilesTab(); + private final MacrosTab macrosTab = new MacrosTab(); + public ClickGui() { super(Component.translatable("sputnik.ui.clickgui.title")); Sputnik.EVENT_BUS.subscribe(this); @@ -83,6 +92,27 @@ public class ClickGui extends Screen { @Override public void extractRenderState(@NonNull GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + switch (tabBar.getSelected()) { + case MODULES -> renderModules(gui, mouseX, mouseY, delta); + case HUD -> renderHud(gui, mouseX, mouseY); + case PROFILES -> profilesTab.render(gui, mouseX, mouseY, delta); + case MACROS -> macrosTab.render(gui, mouseX, mouseY, delta); + } + + tabBar.render(gui, mouseX, mouseY, delta); + + super.extractRenderState(gui, mouseX, mouseY, delta); + } + + private void renderHud(GuiGraphicsExtractor gui, int mouseX, int mouseY) { + gui.fill(0, 0, this.width, this.height, Colors.hudEditorScreenBackgroundColor.getRGB()); + gui.centeredText(mc.font, Component.translatable("sputnik.hud.editor.instructions"), + this.width / 2, this.height / 2, Colors.instructionsTextColor.getRGB()); + + HudEditor.INSTANCE.render(gui, mouseX, mouseY); + } + + private void renderModules(@NonNull GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { scrollBar.setWindowHeight(mc.getWindow().getGuiScaledHeight()); scrollBar.setContentHeight(calculateContentHeight()); scrollBar.render(gui, mouseX, mouseY, delta); @@ -128,8 +158,6 @@ public class ClickGui extends Screen { filterSearchResults(); renderBottomGradient(gui, scrollOffset); - - super.extractRenderState(gui, mouseX, mouseY, delta); } // renderizar un gradiente negro leve en la parte inferior de la pantalla si el contenido excede el límite inferior de la pantalla, para indicarlo visualmente @@ -179,18 +207,24 @@ public class ClickGui extends Screen { int x = (int) click.x(); int y = (int) click.y(); - miscWidgets.forEach(w -> w.mouseClicked( - x, y, click.button() - )); + if (tabBar.handleClick(x, y, click.button())) return super.mouseClicked(click, doubled); - // detectar clics sobre los paneles - friendsPanel.mouseClicked(x, y, click.button()); - clientSettingsPanel.mouseClicked(x, y, click.button()); - for (ModulePanel mp : modulePanels.reversed()) { - mp.mouseClicked(x, y, click.button()); - } - for (SettingsPanel sp : new ArrayList<>(settingsPanels)) { - sp.mouseClicked(x, y, click.button()); + switch (tabBar.getSelected()) { + case MODULES -> { + miscWidgets.forEach(w -> w.mouseClicked(x, y, click.button())); + + friendsPanel.mouseClicked(x, y, click.button()); + clientSettingsPanel.mouseClicked(x, y, click.button()); + for (ModulePanel mp : modulePanels.reversed()) { + mp.mouseClicked(x, y, click.button()); + } + for (SettingsPanel sp : new ArrayList<>(settingsPanels)) { + sp.mouseClicked(x, y, click.button()); + } + } + case HUD -> HudEditor.INSTANCE.mouseClicked(x, y, click.button()); + case PROFILES -> profilesTab.mouseClicked(x, y, click.button()); + case MACROS -> macrosTab.mouseClicked(x, y, click.button()); } return super.mouseClicked(click, doubled); @@ -201,6 +235,13 @@ public class ClickGui extends Screen { int x = (int) click.x(); int y = (int) click.y(); + if (tabBar.getSelected() == Tab.HUD) { + HudEditor.INSTANCE.mouseReleased(click.button()); + return super.mouseReleased(click); + } + + if (tabBar.getSelected() != Tab.MODULES) return super.mouseReleased(click); + miscWidgets.forEach(w -> w.mouseReleased( x, y, click.button())); @@ -219,6 +260,13 @@ public class ClickGui extends Screen { @Override public boolean mouseDragged(@NonNull MouseButtonEvent click, double deltaX, double deltaY) { + if (tabBar.getSelected() == Tab.HUD) { + HudEditor.INSTANCE.mouseDragged((int) click.x(), (int) click.y(), click.button()); + return super.mouseDragged(click, deltaX, deltaY); + } + + if (tabBar.getSelected() != Tab.MODULES) return super.mouseDragged(click, deltaX, deltaY); + miscWidgets.forEach(w -> w.mouseDragged( (int) click.x(), (int) click.y())); @@ -233,11 +281,21 @@ public class ClickGui extends Screen { @EventListener public void onKey(KeyEvent event) { - searchBar.onKey(event.getKey(), event.getAction()); + if (mc.gui.screen() != this) return; + + switch (tabBar.getSelected()) { + case MODULES -> searchBar.onKey(event.getKey(), event.getAction()); + case HUD -> { + if (event.getAction() != GLFW.GLFW_RELEASE) HudEditor.INSTANCE.onKey(event.getKey()); + } + case PROFILES -> profilesTab.onKey(event.getKey(), event.getAction()); + case MACROS -> macrosTab.onKey(event.getKey(), event.getAction()); + } } @EventListener public void onMouseScroll(MouseScrollEvent event) { + if (tabBar.getSelected() != Tab.MODULES) return; miscWidgets.forEach(w -> w.mouseScrolled( event.getVertical() * clientSettings.scrollSens.getValue())); } @@ -477,6 +535,32 @@ public class ClickGui extends Screen { // métodos del súper + private void onTabLeft(Tab previous) { + if (previous == Tab.HUD) { + HudEditor.INSTANCE.saveAll(); + HudEditor.INSTANCE.unselect(); + } + unselect(selected); + } + + // lo miran Sputnik#onInput y el editor del hud para no tratar las teclas que se están + // escribiendo como atajos de módulos + public boolean isCapturingInput() { + return searchBar.isFocused() || profilesTab.isCapturingInput() || macrosTab.isCapturingInput(); + } + + public boolean isOnHudTab() { + return tabBar.getSelected() == Tab.HUD; + } + + public void closeAllSettingsPanels() { + for (SettingsPanel sp : new ArrayList<>(settingsPanels)) { + Sputnik.EVENT_BUS.post(new SettingsPanelEvent.Close(sp)); + unselect(sp); + } + settingsPanels.clear(); + } + @Override public void onClose() { // evitar que al reabrir la interfaz sin previamente haber soltado el clic, se sigan arrastrando objetos modulePanels.forEach(mp -> mp.mouseReleased(0, 0, 0)); @@ -484,6 +568,11 @@ public class ClickGui extends Screen { scrollBar.mouseReleased(0, 0, 0); searchBar.mouseReleased(0, 0, 0); + if (isOnHudTab()) { + HudEditor.INSTANCE.saveAll(); + HudEditor.INSTANCE.unselect(); + } + unselect(selected); super.onClose(); } @@ -512,6 +601,7 @@ public class ClickGui extends Screen { public ClientSettingsPanel getClientSettingsPanel() { return clientSettingsPanel; } public FriendsPanel getFriendsPanel() { return friendsPanel; } public SearchBarWidget getSearchBar() { return searchBar; } + public TabBarWidget getTabBar() { return tabBar; } public ScrollBarWidget getScrollBar() { return scrollBar; } public void setAnyFocused(boolean anyFocused) { diff --git a/src/main/java/me/retucio/sputnik/ui/screen/Tab.java b/src/main/java/me/retucio/sputnik/ui/screen/Tab.java new file mode 100644 index 0000000..84bd059 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/screen/Tab.java @@ -0,0 +1,27 @@ +package me.retucio.sputnik.ui.screen; + +import me.retucio.sputnik.util.I18nUtil; + + +public enum Tab { + + MODULES("módulos"), + HUD("hud"), + PROFILES("perfiles"), + MACROS("macros"); + + private final String name; + + Tab(String name) { + this.name = name; + } + + public String getDisplayName() { + return I18nUtil.orElse("sputnik.ui.tab." + name().toLowerCase(), name); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/me/retucio/sputnik/ui/screen/tabs/MacrosTab.java b/src/main/java/me/retucio/sputnik/ui/screen/tabs/MacrosTab.java new file mode 100644 index 0000000..d8c3b80 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/screen/tabs/MacrosTab.java @@ -0,0 +1,247 @@ +package me.retucio.sputnik.ui.screen.tabs; + +import me.retucio.sputnik.macro.Macro; +import me.retucio.sputnik.macro.MacroManager; +import me.retucio.sputnik.ui.widgets.misc.TextFieldWidget; +import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.KeyUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + + +public class MacrosTab { + + private static final Minecraft mc = Minecraft.getInstance(); + + private static final int LIST_WIDTH = 180; + private static final int LINES_WIDTH = 280; + private static final int GAP = 12; + private static final int ROW_HEIGHT = 18; + private static final int TOP = 46; + + private final TextFieldWidget nameField = new TextFieldWidget( + () -> "sputnik.ui.macros.nameplaceholder", 0, 0, LIST_WIDTH - 24, ROW_HEIGHT) + .maxLength(32) + .onSubmit(this::create); + + private final TextFieldWidget lineField = new TextFieldWidget( + () -> "sputnik.ui.macros.lineplaceholder", 0, 0, LINES_WIDTH - 24, ROW_HEIGHT) + .maxLength(256) + .onSubmit(this::addLine); + + @Nullable private Macro selected = null; + @Nullable private Macro listening = null; + + public void render(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + List macros = MacroManager.INSTANCE.getMacros(); + if (selected != null && !macros.contains(selected)) selected = null; + + int listX = listX(); + + gui.text(mc.font, Component.translatable("sputnik.ui.macros.title"), + listX, TOP - 14, Colors.mainColor.getRGB(), true); + + nameField.setX(listX); + nameField.setY(TOP); + nameField.render(gui, mouseX, mouseY, delta); + + int addX = listX + LIST_WIDTH - 20; + gui.fill(addX, TOP, addX + 20, TOP + ROW_HEIGHT, + isAddHovered(mouseX, mouseY) ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + gui.text(mc.font, "+", addX + 8, TOP + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + + drawHint(gui, Component.translatable("sputnik.ui.macros.instructions"), listX, LIST_WIDTH); + + int rowY = listTop(); + if (macros.isEmpty()) + gui.text(mc.font, Component.translatable("sputnik.ui.macros.empty"), + listX, rowY + 4, Colors.instructionsTextColor.getRGB(), true); + + for (Macro macro : macros) { + boolean hovered = isHovered(listX, rowY, LIST_WIDTH, mouseX, mouseY); + int color = macro == selected ? Colors.mainColor.getRGB() + : (hovered ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + gui.fill(listX, rowY, listX + LIST_WIDTH, rowY + ROW_HEIGHT, color); + + String key = macro == listening + ? Component.translatable("sputnik.ui.macros.presskey").getString() + : KeyUtil.getKeyName(macro.getKey()); + + gui.text(mc.font, trim(macro.getName(), LIST_WIDTH - mc.font.width(key) - 15), listX + 5, + rowY + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + gui.text(mc.font, key, listX + LIST_WIDTH - mc.font.width(key) - 5, + rowY + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, Colors.instructionsTextColor.getRGB(), true); + + rowY += ROW_HEIGHT + 2; + } + + renderLines(gui, mouseX, mouseY, delta); + } + + private void renderLines(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + if (selected == null) return; + + int x = linesX(); + + gui.text(mc.font, selected.getName(), x, TOP - 14, Colors.mainColor.getRGB(), true); + + lineField.setX(x); + lineField.setY(TOP); + lineField.render(gui, mouseX, mouseY, delta); + + int addX = x + LINES_WIDTH - 20; + gui.fill(addX, TOP, addX + 20, TOP + ROW_HEIGHT, + isAddLineHovered(mouseX, mouseY) ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + gui.text(mc.font, "+", addX + 8, TOP + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + + drawHint(gui, Component.translatable("sputnik.ui.macros.lineinstructions"), x, LINES_WIDTH); + + int rowY = listTop(); + for (String line : selected.getLines()) { + boolean hovered = isHovered(x, rowY, LINES_WIDTH, mouseX, mouseY); + gui.fill(x, rowY, x + LINES_WIDTH, rowY + ROW_HEIGHT, + hovered ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + gui.text(mc.font, trim(line, LINES_WIDTH - 10), x + 5, + rowY + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + rowY += ROW_HEIGHT + 2; + } + } + + public void mouseClicked(int mouseX, int mouseY, int button) { + nameField.mouseClicked(mouseX, mouseY, button); + lineField.mouseClicked(mouseX, mouseY, button); + + if (button == 0 && isAddHovered(mouseX, mouseY)) { + create(nameField.getText().trim()); + nameField.clear(); + return; + } + + if (button == 0 && selected != null && isAddLineHovered(mouseX, mouseY)) { + addLine(lineField.getText().trim()); + lineField.clear(); + return; + } + + int rowY = listTop(); + for (Macro macro : new ArrayList<>(MacroManager.INSTANCE.getMacros())) { + if (isHovered(listX(), rowY, LIST_WIDTH, mouseX, mouseY)) { + onMacroClicked(macro, button); + return; + } + rowY += ROW_HEIGHT + 2; + } + + if (selected == null) return; + + rowY = listTop(); + for (String line : new ArrayList<>(selected.getLines())) { + if (isHovered(linesX(), rowY, LINES_WIDTH, mouseX, mouseY)) { + if (button == 1 && KeyUtil.isShiftDown()) { + selected.getLines().remove(line); + MacroManager.INSTANCE.save(); + } + return; + } + rowY += ROW_HEIGHT + 2; + } + } + + private void onMacroClicked(Macro macro, int button) { + switch (button) { + case 0 -> selected = macro; + case 1 -> { + if (KeyUtil.isShiftDown()) { + MacroManager.INSTANCE.remove(macro); + if (selected == macro) selected = null; + if (listening == macro) listening = null; + } else { + listening = macro; + } + } + } + } + + private void create(String name) { + if (name.isEmpty()) return; + + Macro macro = new Macro(name); + if (MacroManager.INSTANCE.add(macro)) selected = macro; + } + + private void addLine(String line) { + if (selected == null || line.isEmpty()) return; + selected.getLines().add(line); + MacroManager.INSTANCE.save(); + } + + public void onKey(int key, int action) { + if (listening != null && action != GLFW.GLFW_RELEASE) { + listening.setKey(key == GLFW.GLFW_KEY_ESCAPE ? GLFW.GLFW_KEY_UNKNOWN : key); + listening = null; + MacroManager.INSTANCE.save(); + return; + } + + nameField.onKey(key, action); + lineField.onKey(key, action); + } + + public boolean isCapturingInput() { + return listening != null || nameField.isFocused() || lineField.isFocused(); + } + + private static int listX() { + return (mc.getWindow().getGuiScaledWidth() - (LIST_WIDTH + GAP + LINES_WIDTH)) / 2; + } + + private static int linesX() { + return listX() + LIST_WIDTH + GAP; + } + + private static void drawHint(GuiGraphicsExtractor gui, Component hint, int x, int width) { + int y = hintTop(); + for (FormattedCharSequence line : mc.font.split(hint, width)) { + gui.text(mc.font, line, x, y, Colors.instructionsTextColor.getRGB(), true); + y += mc.font.lineHeight; + } + } + + private static String trim(String text, int maxWidth) { + if (mc.font.width(text) <= maxWidth) return text; + return mc.font.plainSubstrByWidth(text, maxWidth - mc.font.width("...")) + "..."; + } + + private static int hintTop() { + return TOP + ROW_HEIGHT + 6; + } + + // las dos columnas arrancan bajo la pista más alta, para que ninguna se le meta encima + private static int listTop() { + int lines = Math.max( + mc.font.split(Component.translatable("sputnik.ui.macros.instructions"), LIST_WIDTH).size(), + mc.font.split(Component.translatable("sputnik.ui.macros.lineinstructions"), LINES_WIDTH).size()); + return hintTop() + lines * mc.font.lineHeight + 6; + } + + private static boolean isHovered(int x, int rowY, int width, int mouseX, int mouseY) { + return mouseX >= x && mouseX < x + width && mouseY >= rowY && mouseY < rowY + ROW_HEIGHT; + } + + private static boolean isAddHovered(int mouseX, int mouseY) { + int addX = listX() + LIST_WIDTH - 20; + return mouseX >= addX && mouseX < addX + 20 && mouseY >= TOP && mouseY < TOP + ROW_HEIGHT; + } + + private static boolean isAddLineHovered(int mouseX, int mouseY) { + int addX = linesX() + LINES_WIDTH - 20; + return mouseX >= addX && mouseX < addX + 20 && mouseY >= TOP && mouseY < TOP + ROW_HEIGHT; + } +} diff --git a/src/main/java/me/retucio/sputnik/ui/screen/tabs/ProfilesTab.java b/src/main/java/me/retucio/sputnik/ui/screen/tabs/ProfilesTab.java new file mode 100644 index 0000000..a2d7e36 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/screen/tabs/ProfilesTab.java @@ -0,0 +1,158 @@ +package me.retucio.sputnik.ui.screen.tabs; + +import me.retucio.sputnik.config.ConfigManager; +import me.retucio.sputnik.config.ProfileManager; +import me.retucio.sputnik.ui.hud.HudRenderer; +import me.retucio.sputnik.ui.widgets.misc.TextFieldWidget; +import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.I18nUtil; +import me.retucio.sputnik.util.KeyUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; + +import java.util.List; + + +public class ProfilesTab { + + private static final Minecraft mc = Minecraft.getInstance(); + + private static final int WIDTH = 280; + private static final int ROW_HEIGHT = 18; + private static final int TOP = 46; + + private final TextFieldWidget nameField = new TextFieldWidget( + () -> "sputnik.ui.profiles.placeholder", 0, 0, WIDTH - 24, ROW_HEIGHT) + .maxLength(ProfileManager.MAX_NAME_LENGTH) + .onSubmit(this::create); + + private List profiles = List.of(); + + public void render(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + profiles = ProfileManager.INSTANCE.getProfileNames(); + + int x = (mc.getWindow().getGuiScaledWidth() - WIDTH) / 2; + + gui.text(mc.font, Component.translatable("sputnik.ui.profiles.title"), + x, TOP - 14, Colors.mainColor.getRGB(), true); + + nameField.setX(x); + nameField.setY(TOP); + nameField.render(gui, mouseX, mouseY, delta); + + int addX = x + WIDTH - 20; + gui.fill(addX, TOP, addX + 20, TOP + ROW_HEIGHT, + isAddHovered(mouseX, mouseY) ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + gui.text(mc.font, "+", addX + 8, TOP + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + + int hintY = TOP + ROW_HEIGHT + 6; + for (FormattedCharSequence line : mc.font.split( + Component.translatable("sputnik.ui.profiles.instructions"), WIDTH)) { + gui.text(mc.font, line, x, hintY, Colors.instructionsTextColor.getRGB(), true); + hintY += mc.font.lineHeight; + } + + int rowY = listTop(); + if (profiles.isEmpty()) { + gui.text(mc.font, Component.translatable("sputnik.ui.profiles.empty"), + x, rowY + 4, Colors.instructionsTextColor.getRGB(), true); + return; + } + + String current = ProfileManager.INSTANCE.getCurrent(); + for (String profile : profiles) { + boolean hovered = isRowHovered(x, rowY, mouseX, mouseY); + gui.fill(x, rowY, x + WIDTH, rowY + ROW_HEIGHT, + hovered ? Colors.buttonColor.brighter().getRGB() : Colors.buttonColor.getRGB()); + + gui.text(mc.font, profile, x + 5, rowY + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, -1, true); + + if (profile.equals(current)) + gui.text(mc.font, "●", x + WIDTH - 12, rowY + ROW_HEIGHT / 2 - mc.font.lineHeight / 2, + Colors.mainColor.getRGB(), true); + + rowY += ROW_HEIGHT + 2; + } + } + + public void mouseClicked(int mouseX, int mouseY, int button) { + nameField.mouseClicked(mouseX, mouseY, button); + + if (button == 0 && isAddHovered(mouseX, mouseY)) { + create(nameField.getText().trim()); + nameField.clear(); + return; + } + + int x = (mc.getWindow().getGuiScaledWidth() - WIDTH) / 2; + int rowY = listTop(); + + for (String profile : profiles) { + if (isRowHovered(x, rowY, mouseX, mouseY)) { + onRowClicked(profile, button); + return; + } + rowY += ROW_HEIGHT + 2; + } + } + + private void onRowClicked(String profile, int button) { + if (button == 0) { + if (ProfileManager.INSTANCE.load(profile)) + notify("sputnik.ui.profiles.loaded", profile); + return; + } + + if (button != 1) return; + + if (KeyUtil.isShiftDown()) { + if (ProfileManager.INSTANCE.delete(profile)) + notify("sputnik.ui.profiles.deleted", profile); + } else if (ProfileManager.INSTANCE.save(profile)) { + ConfigManager.INSTANCE.save(); + notify("sputnik.ui.profiles.saved", profile); + } + } + + private void create(String name) { + if (!ProfileManager.isValidName(name)) { + notify("sputnik.ui.profiles.invalidname", name); + return; + } + + if (ProfileManager.INSTANCE.save(name)) { + ConfigManager.INSTANCE.save(); + notify("sputnik.ui.profiles.saved", name); + } + } + + private static void notify(String key, String profile) { + HudRenderer.INSTANCE.pushNotification( + I18nUtil.orElse("sputnik.ui.profiles.title", "perfiles"), + I18nUtil.orElse(key, key, profile)); + } + + public void onKey(int key, int action) { + nameField.onKey(key, action); + } + + public boolean isCapturingInput() { + return nameField.isFocused(); + } + + private static int listTop() { + int lines = mc.font.split(Component.translatable("sputnik.ui.profiles.instructions"), WIDTH).size(); + return TOP + ROW_HEIGHT + 6 + lines * mc.font.lineHeight + 6; + } + + private static boolean isRowHovered(int x, int rowY, int mouseX, int mouseY) { + return mouseX >= x && mouseX < x + WIDTH && mouseY >= rowY && mouseY < rowY + ROW_HEIGHT; + } + + private static boolean isAddHovered(int mouseX, int mouseY) { + int addX = (mc.getWindow().getGuiScaledWidth() - WIDTH) / 2 + WIDTH - 20; + return mouseX >= addX && mouseX < addX + 20 && mouseY >= TOP && mouseY < TOP + ROW_HEIGHT; + } +} diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/misc/TabBarWidget.java b/src/main/java/me/retucio/sputnik/ui/widgets/misc/TabBarWidget.java new file mode 100644 index 0000000..f8cc52c --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/widgets/misc/TabBarWidget.java @@ -0,0 +1,90 @@ +package me.retucio.sputnik.ui.widgets.misc; + +import me.retucio.sputnik.ui.screen.Tab; +import me.retucio.sputnik.ui.widgets.Widget; +import me.retucio.sputnik.util.Colors; +import net.minecraft.client.gui.GuiGraphicsExtractor; + +import java.util.function.Consumer; + + +public class TabBarWidget extends Widget { + + private static final int PADDING = 14; + + private Tab selected = Tab.MODULES; + private final Consumer onLeave; + + public TabBarWidget(Consumer onLeave) { + super(0, 0, 0, 20); + this.onLeave = onLeave; + } + + @Override + public void render(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + w = totalWidth(); + x = (mc.getWindow().getGuiScaledWidth() - w) / 2; + y = 0; + + gui.fill(x, 0, x + w, h, Colors.panelBgColor.getRGB()); + + int tabX = x; + for (Tab tab : Tab.values()) { + int tabW = widthOf(tab); + boolean hovered = isTabHovered(tabX, tabW, mouseX, mouseY); + + if (tab == selected) gui.fill(tabX, 0, tabX + tabW, h, Colors.mainColor.getRGB()); + else if (hovered) gui.fill(tabX, 0, tabX + tabW, h, Colors.buttonColor.brighter().getRGB()); + + gui.text(mc.font, tab.getDisplayName(), + tabX + (tabW - mc.font.width(tab.getDisplayName())) / 2, + h / 2 - mc.font.lineHeight / 2, + -1, true); + + tabX += tabW; + } + } + + // devuelve si el clic era para la barra, para que no llegue también al contenido + public boolean handleClick(int mouseX, int mouseY, int button) { + if (mouseY > h || mouseX < x || mouseX > x + totalWidth()) return false; + if (button != 0) return true; + + int tabX = x; + for (Tab tab : Tab.values()) { + int tabW = widthOf(tab); + if (isTabHovered(tabX, tabW, mouseX, mouseY)) { + select(tab); + return true; + } + tabX += tabW; + } + return true; + } + + public void select(Tab tab) { + if (selected == tab) return; + + Tab previous = selected; + selected = tab; + onLeave.accept(previous); + } + + private boolean isTabHovered(int tabX, int tabW, int mouseX, int mouseY) { + return mouseX >= tabX && mouseX < tabX + tabW && mouseY >= 0 && mouseY < h; + } + + private int widthOf(Tab tab) { + return mc.font.width(tab.getDisplayName()) + PADDING * 2; + } + + private int totalWidth() { + int total = 0; + for (Tab tab : Tab.values()) total += widthOf(tab); + return total; + } + + public Tab getSelected() { + return selected; + } +} diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/misc/TextFieldWidget.java b/src/main/java/me/retucio/sputnik/ui/widgets/misc/TextFieldWidget.java new file mode 100644 index 0000000..3b891ef --- /dev/null +++ b/src/main/java/me/retucio/sputnik/ui/widgets/misc/TextFieldWidget.java @@ -0,0 +1,118 @@ +package me.retucio.sputnik.ui.widgets.misc; + +import me.retucio.sputnik.ui.widgets.Widget; +import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.KeyUtil; +import me.retucio.sputnik.util.MiscUtil; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +import java.awt.Color; +import java.util.function.Consumer; +import java.util.function.Supplier; + + +public class TextFieldWidget extends Widget { + + private final StringBuilder buffer = new StringBuilder(); + private final Supplier placeholder; + + private boolean focused; + private int maxLength = 64; + private Consumer onSubmit; + + public TextFieldWidget(Supplier placeholder, int x, int y, int w, int h) { + super(x, y, w, h); + this.placeholder = placeholder; + } + + public TextFieldWidget maxLength(int maxLength) { + this.maxLength = maxLength; + return this; + } + + public TextFieldWidget onSubmit(Consumer onSubmit) { + this.onSubmit = onSubmit; + return this; + } + + @Override + public void render(GuiGraphicsExtractor gui, int mouseX, int mouseY, float delta) { + Color bg = focused + ? Colors.buttonColor.darker() + : (isHovered(mouseX, mouseY) ? Colors.buttonColor.brighter() : Colors.buttonColor); + gui.fill(x, y, x + w, y + h, bg.getRGB()); + + boolean empty = buffer.isEmpty(); + Component label = (!focused && empty) + ? Component.literal(ChatFormatting.ITALIC.toString()).append(Component.translatable(placeholderKey())) + : Component.literal(focused ? buffer + "_" : buffer.toString()); + + gui.text(mc.font, label, + x + 5, y + h / 2 - mc.font.lineHeight / 2, + (empty && !focused) ? Color.LIGHT_GRAY.getRGB() : -1, true); + } + + private String placeholderKey() { + return placeholder.get(); + } + + @Override + public void mouseClicked(int mouseX, int mouseY, int button) { + focused = button == 0 && isHovered(mouseX, mouseY); + } + + @Override + public void mouseReleased(int mouseX, int mouseY, int button) {} + + @Override + public void onKey(int key, int action) { + if (!focused || action == GLFW.GLFW_RELEASE) return; + + if (key == GLFW.GLFW_KEY_V && KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_CONTROL)) { + buffer.append(MiscUtil.getPasteContent(maxLength - buffer.length())); + return; + } + + switch (key) { + case GLFW.GLFW_KEY_ENTER -> submit(); + case GLFW.GLFW_KEY_ESCAPE -> { + buffer.setLength(0); + focused = false; + } + case GLFW.GLFW_KEY_BACKSPACE -> MiscUtil.backspace(buffer); + case GLFW.GLFW_KEY_SPACE -> charTyped(' '); + default -> { + String c = KeyUtil.getKeyName(key); + if (c.length() == 1) + charTyped(KeyUtil.isShiftDown() ? KeyUtil.shiftKey(c).charAt(0) : c.toLowerCase().charAt(0)); + } + } + } + + private void submit() { + String text = buffer.toString().trim(); + if (!text.isEmpty() && onSubmit != null) onSubmit.accept(text); + buffer.setLength(0); + focused = false; + } + + private void charTyped(char c) { + if (buffer.length() >= maxLength) return; + buffer.append(c); + if (mc.font.width(buffer + "_") > w - 10) MiscUtil.backspace(buffer); + } + + @Override + public boolean isHovered(int mouseX, int mouseY) { + return mouseX > x && mouseX < x + w && mouseY > y && mouseY < y + h; + } + + public String getText() { return buffer.toString(); } + public void clear() { buffer.setLength(0); } + + public boolean isFocused() { return focused; } + public void setFocused(boolean focused) { this.focused = focused; } +} diff --git a/src/main/java/me/retucio/sputnik/util/ChatUtil.java b/src/main/java/me/retucio/sputnik/util/ChatUtil.java index 959b413..10f0b16 100644 --- a/src/main/java/me/retucio/sputnik/util/ChatUtil.java +++ b/src/main/java/me/retucio/sputnik/util/ChatUtil.java @@ -1,5 +1,6 @@ package me.retucio.sputnik.util; +import com.github.retucio.neutrino.EventListener; import com.mojang.brigadier.exceptions.CommandSyntaxException; import me.retucio.sputnik.Sputnik; import me.retucio.sputnik.command.CommandManager; @@ -15,6 +16,7 @@ public class ChatUtil { private static final Minecraft mc = Minecraft.getInstance(); private static String prefix = getDefaultPrefix(); + @EventListener public static void onLoadModuleManager(LoadModuleManagerEvent event) { prefix = Colors.getFormatting(Colors.mainColor) + "[" + ClientSettingsPanel.clientSettings.chatName.getValue() + "] "; } diff --git a/src/main/java/me/retucio/sputnik/util/EntityUtil.java b/src/main/java/me/retucio/sputnik/util/EntityUtil.java index 543a60e..0c849cc 100644 --- a/src/main/java/me/retucio/sputnik/util/EntityUtil.java +++ b/src/main/java/me/retucio/sputnik/util/EntityUtil.java @@ -3,7 +3,6 @@ package me.retucio.sputnik.util; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.projectile.ProjectileUtil; -import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.world.level.ClipContext; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.EntityHitResult; @@ -89,25 +88,6 @@ public class EntityUtil { return mc.player.getXRot() + Mth.wrapDegrees((float) -Math.toDegrees(Math.atan2(diffY, diffXZ)) - mc.player.getXRot()); } - public static void lookAt(Vec3 pos) { - lookAtClient(pos); - lookAtServer(pos); - } - - public static void lookAtClient(Vec3 pos) { - mc.player.setYRot((float) getYaw(pos)); - mc.player.setXRot((float) getPitch(pos)); - } - - public static void lookAtServer(Vec3 pos) { - mc.getConnection().send(new ServerboundMovePlayerPacket.Rot( - (float) getYaw(pos), - (float) getPitch(pos), - mc.player.onGround(), - mc.player.horizontalCollision - )); - } - public static Vec3 getHorizontalVelocity(double bps) { float yaw = mc.player.getYRot(); diff --git a/src/main/java/me/retucio/sputnik/util/Lists.java b/src/main/java/me/retucio/sputnik/util/Lists.java index ea150c4..94f368e 100644 --- a/src/main/java/me/retucio/sputnik/util/Lists.java +++ b/src/main/java/me/retucio/sputnik/util/Lists.java @@ -3,16 +3,26 @@ package me.retucio.sputnik.util; import net.minecraft.core.particles.ParticleType; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.ProtocolInfo; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketType; +import net.minecraft.network.protocol.configuration.ConfigurationProtocols; +import net.minecraft.network.protocol.game.GameProtocols; +import net.minecraft.network.protocol.handshake.HandshakeProtocols; +import net.minecraft.network.protocol.login.LoginProtocols; +import net.minecraft.network.protocol.status.StatusProtocols; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.EntityType; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; +import org.jspecify.annotations.NonNull; import java.awt.*; import java.util.*; import java.util.List; +import java.util.stream.Stream; import static me.retucio.sputnik.util.Colors.*; @@ -36,14 +46,14 @@ public class Lists { public static final List> screenList = BuiltInRegistries.MENU.stream().toList(); public static Map, String> screenNames; + public static List>> c2sPacketList = new ArrayList<>(); + public static Map>, String> c2sPacketNames = new HashMap<>(); + public static List>> s2cPacketList = new ArrayList<>(); + public static Map>, String> s2cPacketNames = new HashMap<>(); + public static final List colorList = new ArrayList<>(); public static final List fontList = new ArrayList<>(); - // estos mapas guardan la CLAVE de traducción, no el texto ya traducido: son los nombres - // internos de las opciones y por tanto claves de la config. (ver ListSetting#getConfigName). - // antes se resolvía aquí con Component#translatable, pero esto corre en la inicialización - // del mod, cuando Language todavía es el en_us de vanilla: salían los nombres en inglés - // pasara lo que pasara. ahora se traducen al mostrarlos (ListSetting#getDisplayName) public static void init() { entityNames = getMapOfLists(entityList, entityList.stream().map(EntityType::getDescriptionId).toList()); @@ -81,6 +91,31 @@ public class Lists { "deja-vu-sans-mono", "new-times-roman", "roboto", "segoe-ui", "ubuntu" )); + + Stream.of( + StatusProtocols.CLIENTBOUND_TEMPLATE, + LoginProtocols.CLIENTBOUND_TEMPLATE, + ConfigurationProtocols.CLIENTBOUND_TEMPLATE, + GameProtocols.CLIENTBOUND_TEMPLATE + ) + .map(ProtocolInfo.DetailsProvider::details) + .forEach(details -> details.listPackets((type, _) -> { + s2cPacketList.add(type); + s2cPacketNames.put(type, type.id().toShortString()); + })); + + Stream.of( + HandshakeProtocols.SERVERBOUND_TEMPLATE, + StatusProtocols.SERVERBOUND_TEMPLATE, + LoginProtocols.SERVERBOUND_TEMPLATE, + ConfigurationProtocols.SERVERBOUND_TEMPLATE, + GameProtocols.SERVERBOUND_TEMPLATE + ) + .map(ProtocolInfo.DetailsProvider::details) + .forEach(details -> details.listPackets((type, _) -> { + c2sPacketList.add(type); + c2sPacketNames.put(type, type.id().toShortString()); + })); } public static Map allTrue(List options) { diff --git a/src/main/java/me/retucio/sputnik/util/NetworkUtil.java b/src/main/java/me/retucio/sputnik/util/NetworkUtil.java index 5cbc6d2..58f019f 100644 --- a/src/main/java/me/retucio/sputnik/util/NetworkUtil.java +++ b/src/main/java/me/retucio/sputnik/util/NetworkUtil.java @@ -107,9 +107,9 @@ public class NetworkUtil { mc.player.setShiftKeyDown(wasSneaking); } - // colocar un bloque + // colocar un bloque, con rotate a null para no rotar public static boolean placeBlock(BlockPos blockPos, InteractionHand hand, int slot, - boolean rotateClient, boolean rotateServer, boolean swingHand, + RotationUtil.Mode rotate, int rotationPriority, boolean swingHand, boolean checkEntities, boolean swapBack) { if (slot < 0 || slot > 8 || mc.player == null) return false; @@ -135,17 +135,19 @@ public class NetworkUtil { BlockHitResult bhr = new BlockHitResult(hitPos, side.getOpposite(), neighbour, false); int prevSlot = mc.player.getInventory().getSelectedSlot(); - if (rotateClient) EntityUtil.lookAtClient(hitPos); - if (rotateServer) EntityUtil.lookAtServer(hitPos); + Runnable place = () -> { + mc.getConnection().send(new ServerboundSetCarriedItemPacket(slot)); - mc.getConnection().send(new ServerboundSetCarriedItemPacket(slot)); + interactBlock(bhr, hand, swingHand); - interactBlock(bhr, hand, swingHand); + if (swapBack) { + mc.player.getInventory().setSelectedSlot(prevSlot); + mc.getConnection().send(new ServerboundSetCarriedItemPacket(prevSlot)); + } + }; - if (swapBack) { - mc.player.getInventory().setSelectedSlot(prevSlot); - mc.getConnection().send(new ServerboundSetCarriedItemPacket(prevSlot)); - } + if (rotate == null) place.run(); + else RotationUtil.lookAt(rotationPriority, hitPos, 0, rotate, place); return true; } diff --git a/src/main/java/me/retucio/sputnik/util/RotationUtil.java b/src/main/java/me/retucio/sputnik/util/RotationUtil.java new file mode 100644 index 0000000..7de6836 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/util/RotationUtil.java @@ -0,0 +1,490 @@ +package me.retucio.sputnik.util; + +import com.github.retucio.neutrino.Event; +import com.github.retucio.neutrino.EventListener; +import com.github.retucio.neutrino.EventPriority; +import me.retucio.sputnik.event.TickEvent; +import me.retucio.sputnik.event.network.DisconnectEvent; +import me.retucio.sputnik.event.network.JoinWorldEvent; +import me.retucio.sputnik.event.network.PacketEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.GameProtocols; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.network.protocol.status.StatusProtocols; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +import static me.retucio.sputnik.Sputnik.mc; + + +/** + * arbitra las rotaciones entre módulos. + *

+ * en vez de que cada módulo escriba directamente en {@code player.setYRot()} y se peleen entre ellos, + * cada uno pide una rotación con una prioridad y aquí se decide cuál gana el tick. + * + * @author retucio + */ + +public class RotationUtil { + + // prioridades sugeridas: gana la más alta del tick + public static final int LOWEST = EventPriority.LOWEST; + public static final int LOW = EventPriority.LOW; + public static final int NORMAL = EventPriority.DEFAULT; + public static final int HIGH = EventPriority.HIGH; + public static final int HIGHEST = EventPriority.HIGHEST; + + // tolerancia por defecto de hasReached(), en grados + private static final float DEFAULT_TOLERANCE = 1f; + + + /** + * CLIENT: mueve la cámara de verdad, el servidor recibe la rotación como cualquier otra + *

+ * SILENT: la cámara no se mueve, solo se reescribe la rotación de los paquetes de movimiento + */ + public enum Mode { + CLIENT, + SILENT + } + + + // ajustes globales, pensados para engancharlos a un módulo / a los ajustes del cliente + + // grados máximos por tick, >= 180 es instantáneo + public static float speed = 180f; + + // redondear los pasos a la precisión real del ratón + public static boolean gcdFix = true; + + // ticks máximos para devolver la rotación del servidor a la real al soltar una rotación silenciosa + public static int restoreTicks = 3; + + + // estado + + // rotación que ve el servidor + private static float yaw, pitch; + private static float prevYaw, prevPitch; + + // mejor petición recibida desde el último tick + private static Request pending; + // petición que se está aplicando + private static Request active; + + // ticks que le quedan a la petición activa sin que nadie vuelva a pedirla + private static int hold; + + // devolviendo la rotación del servidor a la del jugador + private static boolean restoring; + private static int restoreLeft; + + // este tick mandamos nosotros la rotación del servidor + private static boolean controlled; + // ya ha salido un paquete de movimiento este tick + private static boolean sent; + + // lo que ejecutar cuando el servidor ya haya recibido la rotación de la petición activa + private static Runnable callback; + + + private record Request(int priority, float yaw, float pitch, int holdTicks, Mode mode, Runnable callback) {} + + + // peticiones + + // pide una rotación para este tick + public static void request(int priority, float yaw, float pitch) { + request(priority, yaw, pitch, 0, Mode.SILENT, null); + } + + public static void request(int priority, float yaw, float pitch, Mode mode) { + request(priority, yaw, pitch, 0, mode, null); + } + + public static void request(int priority, float yaw, float pitch, Runnable callback) { + request(priority, yaw, pitch, 0, Mode.SILENT, callback); + } + + public static void request(int priority, float yaw, float pitch, int holdTicks, Mode mode) { + request(priority, yaw, pitch, holdTicks, mode, null); + } + + /** + * @param priority gana la petición más alta del tick, en empate gana la primera + * @param holdTicks ticks que se mantiene la rotación tras la última petición + * @param callback se ejecuta al final del tick en el que el servidor ya ha recibido la rotación, + * que es donde hay que interactuar para que el servidor nos vea mirando bien + */ + public static void request(int priority, float yaw, float pitch, int holdTicks, Mode mode, Runnable callback) { + if (mc.player == null) return; + if (pending != null && pending.priority() >= priority) return; + + pending = new Request(priority, yaw, Float.isNaN(pitch) ? pitch : Mth.clamp(pitch, -90f, 90f), holdTicks, mode, callback); + } + + // pide solo un eje, dejando el otro como esté + public static void requestYaw(int priority, float yaw, Mode mode) { + request(priority, yaw, Float.NaN, 0, mode, null); + } + + public static void requestPitch(int priority, float pitch, Mode mode) { + request(priority, Float.NaN, pitch, 0, mode, null); + } + + public static void lookAt(int priority, Vec3 pos) { + lookAt(priority, pos, 0, Mode.SILENT, null); + } + + public static void lookAt(int priority, Vec3 pos, Mode mode) { + lookAt(priority, pos, 0, mode, null); + } + + public static void lookAt(int priority, Vec3 pos, Runnable callback) { + lookAt(priority, pos, 0, Mode.SILENT, callback); + } + + public static void lookAt(int priority, Vec3 pos, int holdTicks, Mode mode) { + lookAt(priority, pos, holdTicks, mode, null); + } + + public static void lookAt(int priority, Vec3 pos, int holdTicks, Mode mode, Runnable callback) { + if (mc.player == null) return; + + float[] rotations = getRotations(pos); + request(priority, rotations[0], rotations[1], holdTicks, mode, callback); + } + + public static void lookAt(int priority, BlockPos pos) { + lookAt(priority, Vec3.atCenterOf(pos), 0, Mode.SILENT, null); + } + + public static void lookAt(int priority, BlockPos pos, Runnable callback) { + lookAt(priority, Vec3.atCenterOf(pos), 0, Mode.SILENT, callback); + } + + public static void lookAt(int priority, BlockPos pos, int holdTicks, Mode mode) { + lookAt(priority, Vec3.atCenterOf(pos), holdTicks, mode, null); + } + + public static void lookAt(int priority, Entity entity, EntityUtil.Target target) { + lookAt(priority, entity, target, 0, Mode.SILENT, null); + } + + public static void lookAt(int priority, Entity entity, EntityUtil.Target target, Runnable callback) { + lookAt(priority, entity, target, 0, Mode.SILENT, callback); + } + + public static void lookAt(int priority, Entity entity, EntityUtil.Target target, int holdTicks, Mode mode) { + lookAt(priority, getTargetPos(entity, target), holdTicks, mode, null); + } + + public static void lookAt(int priority, Entity entity, EntityUtil.Target target, int holdTicks, Mode mode, Runnable callback) { + lookAt(priority, getTargetPos(entity, target), holdTicks, mode, callback); + } + + // suelta la rotación activa sin esperar a que caduque + public static void release() { + pending = null; + if (active != null && active.mode() == Mode.SILENT) startRestoring(); + active = null; + callback = null; + hold = 0; + } + + + // estado + + // rotación que ve el servidor: la falsa si hay una activa, si no la del jugador + public static float getYaw() { + return isOverriding() ? yaw : (mc.player == null ? 0f : mc.player.getYRot()); + } + + public static float getPitch() { + return isOverriding() ? pitch : (mc.player == null ? 0f : mc.player.getXRot()); + } + + // interpoladas, para renderizar + public static float getYaw(float tickDelta) { + return isOverriding() ? Mth.rotLerp(tickDelta, prevYaw, yaw) : getYaw(); + } + + public static float getPitch(float tickDelta) { + return isOverriding() ? Mth.lerp(tickDelta, prevPitch, pitch) : getPitch(); + } + + // hay una petición aplicándose + public static boolean isRotating() { + return active != null; + } + + // la rotación del servidor no es la del jugador + public static boolean isOverriding() { + return controlled; + } + + public static Mode getMode() { + return active == null ? null : active.mode(); + } + + // prioridad de la petición activa, para que un módulo sepa si le han ganado + public static int getPriority() { + return active == null ? Integer.MIN_VALUE : active.priority(); + } + + // verdadero si nadie con más prioridad está rotando, es decir, si la petición sería aceptada + public static boolean canRotate(int priority) { + return active == null || active.priority() <= priority; + } + + // la rotación del servidor ya ha llegado a la que se pidió + public static boolean hasReached() { + return hasReached(DEFAULT_TOLERANCE); + } + + public static boolean hasReached(float tolerance) { + if (active == null) return false; + return (Float.isNaN(active.yaw()) || Math.abs(Mth.degreesDifference(yaw, active.yaw())) <= tolerance) + && (Float.isNaN(active.pitch()) || Math.abs(active.pitch() - pitch) <= tolerance); + } + + // el servidor nos ve mirando a pos + public static boolean isAimingAt(Vec3 pos, float tolerance) { + float[] rotations = getRotations(pos); + return Math.abs(Mth.degreesDifference(getYaw(), rotations[0])) <= tolerance + && Math.abs(rotations[1] - getPitch()) <= tolerance; + } + + + // helpers + + // rotación necesaria para mirar de from a to, en {yaw, pitch} + public static float[] getRotations(Vec3 from, Vec3 to) { + double diffX = to.x() - from.x(); + double diffY = to.y() - from.y(); + double diffZ = to.z() - from.z(); + + double diffXZ = Math.sqrt(diffX * diffX + diffZ * diffZ); + + return new float[] { + (float) (Math.toDegrees(Math.atan2(diffZ, diffX)) - 90d), + Mth.clamp((float) -Math.toDegrees(Math.atan2(diffY, diffXZ)), -90f, 90f) + }; + } + + // rotación necesaria para que el jugador mire a pos + public static float[] getRotations(Vec3 pos) { + return getRotations(mc.player.getEyePosition(), pos); + } + + public static float[] getRotations(BlockPos pos) { + return getRotations(Vec3.atCenterOf(pos)); + } + + public static float[] getRotations(Entity entity, EntityUtil.Target target) { + return getRotations(getTargetPos(entity, target)); + } + + // vector de mirada según la rotación que ve el servidor + public static Vec3 getViewVector() { + return Vec3.directionFromRotation(getPitch(), getYaw()); + } + + // raycast desde los ojos del jugador con la rotación que ve el servidor + public static HitResult raycast(double range) { + Vec3 eyes = mc.player.getEyePosition(); + Vec3 end = eyes.add(getViewVector().scale(range)); + + return mc.level.clip(new ClipContext( + eyes, + end, + ClipContext.Block.OUTLINE, + ClipContext.Fluid.NONE, + mc.player + )); + } + + private static Vec3 getTargetPos(Entity entity, EntityUtil.Target target) { + return switch (target) { + case HEAD -> new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); + case BODY -> entity.getBoundingBox().getCenter(); + case FEET -> entity.position(); + }; + } + + + // lógica + + // en TickEvent.Pre, que va justo después de que los módulos tickeen y antes de que LocalPlayer + // mande su paquete de movimiento + @EventListener + public static void onTick(TickEvent.Pre event) { + if (mc.player == null || mc.level == null) { + reset(); + return; + } + + prevYaw = yaw; + prevPitch = pitch; + + // caducar la petición activa si nadie con al menos su prioridad la ha renovado + if (active != null && (pending == null || pending.priority() < active.priority())) { + if (hold > 0) hold--; + else { + if (active.mode() == Mode.SILENT) startRestoring(); + active = null; + callback = null; + } + } + + // la petición nueva gana si no hay activa o si empata / supera su prioridad + if (pending != null && (active == null || pending.priority() >= active.priority())) { + active = pending; + hold = pending.holdTicks(); + callback = pending.callback(); + restoring = false; + } + + pending = null; + + controlled = false; + + if (active != null) { + boolean freeYaw = Float.isNaN(active.yaw()); + boolean freePitch = Float.isNaN(active.pitch()); + + yaw = step(yaw, freeYaw ? mc.player.getYRot() : active.yaw()); + pitch = Mth.clamp(step(pitch, freePitch ? mc.player.getXRot() : active.pitch()), -90f, 90f); + + if (active.mode() == Mode.CLIENT) { + if (!freeYaw) { + mc.player.setYRot(yaw); + mc.player.setYHeadRot(yaw); + } + if (!freePitch) mc.player.setXRot(pitch); + } else controlled = true; + } else if (restoring) { + float realYaw = mc.player.getYRot(); + float realPitch = mc.player.getXRot(); + + if (yaw == realYaw && pitch == realPitch) { + // la rotación real ya salió el tick pasado, soltamos + restoring = false; + } else { + if (--restoreLeft <= 0) { + yaw = realYaw; + pitch = realPitch; + } else { + yaw = step(yaw, realYaw); + pitch = step(pitch, realPitch); + } + controlled = true; + } + } + + if (!controlled) { + yaw = mc.player.getYRot(); + pitch = mc.player.getXRot(); + } + } + + // prioridad mínima para ir después de los módulos que cancelan y reenvían estos paquetes + // (nofall, antihunger...), cuyo reemplazo vuelve a pasar por aquí + @EventListener(priority = EventPriority.LOWEST) + public static void onSendPacket(PacketEvent.Send event) { + if (!controlled || event.getStage() != Event.Stage.PRE || event.isCancelled()) return; + if (mc.player == null || mc.getConnection() == null) return; + if (!(event.getPacket() instanceof ServerboundMovePlayerPacket packet)) return; + + sent = true; + + // ya lleva la nuestra + if (packet.hasRotation() && packet.getYRot(0f) == yaw && packet.getXRot(0f) == pitch) return; + + event.cancel(); + NetworkUtil.sendPacketNoEvent(packet.hasPosition() + ? new ServerboundMovePlayerPacket.PosRot( + packet.getX(mc.player.getX()), + packet.getY(mc.player.getY()), + packet.getZ(mc.player.getZ()), + yaw, pitch, + packet.isOnGround(), + packet.horizontalCollision()) + : new ServerboundMovePlayerPacket.Rot( + yaw, pitch, + packet.isOnGround(), + packet.horizontalCollision())); + } + + // si el jugador está quieto y sin girar no sale ningún paquete que reescribir, así que lo mandamos nosotros + @EventListener + public static void onTickPost(TickEvent.Post event) { + if (mc.player == null || mc.getConnection() == null) return; + + if (controlled && !sent) + NetworkUtil.sendPacketNoEvent(new ServerboundMovePlayerPacket.Rot( + yaw, + pitch, + mc.player.onGround(), + mc.player.horizontalCollision + )); + + sent = false; + + // aquí el paquete de movimiento del tick ya ha salido, así que el servidor nos ve mirando + // donde se pidió y es seguro interactuar + if (callback != null && hasReached()) { + Runnable toRun = callback; + callback = null; + toRun.run(); + } + } + + @EventListener + public static void onJoinWorld(JoinWorldEvent event) { + reset(); + } + + @EventListener + public static void onDisconnect(DisconnectEvent event) { + reset(); + } + + + private static void startRestoring() { + restoring = true; + restoreLeft = Math.max(restoreTicks, 1); + } + + private static void reset() { + pending = null; + active = null; + callback = null; + hold = 0; + restoring = false; + restoreLeft = 0; + controlled = false; + sent = false; + } + + // acerca current a target sin pasarse de speed grados + private static float step(float current, float target) { + float delta = Mth.degreesDifference(current, target); + if (Math.abs(delta) <= speed) return target; + + float stepped = current + Math.signum(delta) * speed; + return gcdFix ? applyGcd(current, stepped) : stepped; + } + + // el ratón solo puede girar en múltiplos de esta cantidad, así que los pasos que no lo sean cantan + private static float applyGcd(float current, float target) { + double sensitivity = mc.options.sensitivity().get() * 0.6d + 0.2d; + double gcd = sensitivity * sensitivity * sensitivity * 8d * 0.15d; + + return (float) (current + Math.round((target - current) / gcd) * gcd); + } +} diff --git a/src/main/resources/assets/sputnik/lang/en_us.json b/src/main/resources/assets/sputnik/lang/en_us.json index 1b6895d..1ccd833 100644 --- a/src/main/resources/assets/sputnik/lang/en_us.json +++ b/src/main/resources/assets/sputnik/lang/en_us.json @@ -29,6 +29,16 @@ "sputnik.commands.friend.removed": "%s is not your friend anymore :(", "sputnik.commands.gc.collected": "garbage collected", "sputnik.commands.gc.desc": "frees up ram using the garbage collector (gc)", + "sputnik.commands.help.desc": "lists every command with its description", + "sputnik.commands.macro.added": "macro %s created", + "sputnik.commands.macro.cleared": "lines of %s cleared", + "sputnik.commands.macro.desc": "manages macros", + "sputnik.commands.macro.exists": "a macro named \"%s\" already exists", + "sputnik.commands.macro.lineadded": "line added to %s", + "sputnik.commands.macro.list": "%s macro(s):", + "sputnik.commands.macro.none": "there are no macros", + "sputnik.commands.macro.notfound": "macro \"%s\" not found", + "sputnik.commands.macro.removed": "macro %s removed", "sputnik.commands.panorama.desc": "creates a panorama resource pack based on your current position", "sputnik.commands.panorama.errorassemble": "failed to assemble custom resource pack! does a pack of that name already exist?", "sputnik.commands.panorama.packdesc": "generated by Sputnik", @@ -44,6 +54,15 @@ "sputnik.commands.plugins.singleplayer": "you are in singleplayer", "sputnik.commands.prefix.changed": "prefix changed to %s", "sputnik.commands.prefix.desc": "changes the command prefix", + "sputnik.commands.profile.deleted": "profile %s deleted", + "sputnik.commands.profile.desc": "manages config profiles", + "sputnik.commands.profile.invalidname": "invalid name: \"%s\"", + "sputnik.commands.profile.list": "%s profile(s):", + "sputnik.commands.profile.loaded": "profile %s loaded", + "sputnik.commands.profile.none": "there are no profiles", + "sputnik.commands.profile.notfound": "profile \"%s\" not found", + "sputnik.commands.profile.saved": "current settings saved to %s", + "sputnik.commands.profile.savefailed": "could not save profile \"%s\"", "sputnik.commands.purge.desc": "removes messages from the chat", "sputnik.commands.repeat.desc": "repeats a command x times", "sputnik.commands.repeatwait.desc": "combination of $repeat and $wait", @@ -351,7 +370,7 @@ "sputnik.module.bookbot.created": "created book no. %s", "sputnik.module.bookbot.nobook": "no book found", "sputnik.module.brillibrilli.desc": "disables darkness (and applies colors to shaders)", - "sputnik.module.brillibrilli.name": "bling bling", + "sputnik.module.brillibrilli.name": "fullbright", "sputnik.module.bungeespoofer.invalidip": "invalid IP address", "sputnik.module.burglar.dump": "dump", "sputnik.module.burglar.steal": "steal", @@ -473,6 +492,8 @@ "sputnik.module.notificaciones.name": "notifications", "sputnik.module.packetdelay.singleplayer": "packet delay doesn't work in singleplayer", "sputnik.module.packetmine.pickbreaking": "the pickaxe is about to break", + "sputnik.module.canceladordepaquetes.name": "packet cancel", + "sputnik.module.canceladordepaquetes.desc": "cancels packets.", "sputnik.module.parkour.desc": "automatically jumps on the last possible tick, at the block's edge", "sputnik.module.parkour.name": "parkour", "sputnik.module.particulas.desc": "emits artificial particles", @@ -484,7 +505,7 @@ "sputnik.module.perspectivaplus.desc": "adds cool little things to the perspective switch", "sputnik.module.perspectivaplus.name": "perspective plus", "sputnik.module.precoz.desc": "does things like using items or placing blocks faster", - "sputnik.module.precoz.name": "early", + "sputnik.module.precoz.name": "premature ejaculation", "sputnik.module.prevdeshulkers.desc": "lets you preview shulker contents from the inventory", "sputnik.module.prevdeshulkers.name": "shulker preview", "sputnik.module.progresodeminado.desc": "shows you a block's mining progress more visibly", @@ -501,9 +522,9 @@ "sputnik.module.reponer.desc": "automatically refills items as they get used up", "sputnik.module.reponer.name": "refill", "sputnik.module.resaltadodebloques.desc": "shows you where a certain kind of block is by rendering a box with its shape", - "sputnik.module.resaltadodebloques.name": "block highlight", + "sputnik.module.resaltadodebloques.name": "block ESP", "sputnik.module.resaltadodeentidades.desc": "highlights the outline of the selected entities through blocks", - "sputnik.module.resaltadodeentidades.name": "entity highlight", + "sputnik.module.resaltadodeentidades.name": "entity ESP", "sputnik.module.retroceso.desc": "makes you fat", "sputnik.module.retroceso.name": "knockback", "sputnik.module.rotaciones.desc": "lets you force a specific rotation", @@ -555,6 +576,9 @@ "sputnik.module.yunquesconpluma.name": "anvils with a quill", "sputnik.module.zoom.desc": "magnifying glass", "sputnik.module.zoom.name": "zoom", + "sputnik.profile.error.dir": "could not create the profiles directory", + "sputnik.profile.error.loading": "error loading profile \"%s\"", + "sputnik.profile.error.saving": "error saving profile \"%s\"", "sputnik.setting.amigos.general.prevenirataques.desc": "don't let you attack them manually", "sputnik.setting.amigos.general.prevenirataques.name": "prevent attacks", "sputnik.setting.amigos.general.rueditaparaanadiramigo.desc": "press the middle mouse button to (un)mark a player as a friend", @@ -1452,10 +1476,29 @@ "sputnik.ui.friend.remove": "remove friend", "sputnik.ui.friends.title": "friends", "sputnik.ui.list.tooltip": "%s (%s of %s)", + "sputnik.ui.macros.empty": "no macros yet", + "sputnik.ui.macros.instructions": "left click: select / right click: bind / shift + right click: delete", + "sputnik.ui.macros.lineinstructions": "shift + right click on a line to remove it", + "sputnik.ui.macros.lineplaceholder": "command or message...", + "sputnik.ui.macros.nameplaceholder": "macro name...", + "sputnik.ui.macros.presskey": "press a key...", + "sputnik.ui.macros.title": "macros", "sputnik.ui.preview.echest": "echest", "sputnik.ui.preview.shulker": "shulker preview", + "sputnik.ui.profiles.deleted": "deleted \"%s\"", + "sputnik.ui.profiles.empty": "no profiles yet", + "sputnik.ui.profiles.instructions": "left click: load / right click: overwrite / shift + right click: delete", + "sputnik.ui.profiles.invalidname": "invalid name: \"%s\"", + "sputnik.ui.profiles.loaded": "loaded \"%s\"", + "sputnik.ui.profiles.placeholder": "profile name...", + "sputnik.ui.profiles.saved": "saved \"%s\"", + "sputnik.ui.profiles.title": "profiles", "sputnik.ui.searchbar.placeholder": "search...", "sputnik.ui.settingsof": "%s settings", + "sputnik.ui.tab.hud": "hud", + "sputnik.ui.tab.macros": "macros", + "sputnik.ui.tab.modules": "modules", + "sputnik.ui.tab.profiles": "profiles", "sputnik.update.clickhere": "CLICK HERE", "sputnik.update.download": "download the latest version (%s) from here:", "sputnik.update.idgaf": "i don't care", diff --git a/src/main/resources/assets/sputnik/lang/es_es.json b/src/main/resources/assets/sputnik/lang/es_es.json index 2edd4b7..e71ec62 100644 --- a/src/main/resources/assets/sputnik/lang/es_es.json +++ b/src/main/resources/assets/sputnik/lang/es_es.json @@ -29,6 +29,16 @@ "sputnik.commands.friend.removed": "%s ya no es tu amigo :(", "sputnik.commands.gc.collected": "basura recolectada", "sputnik.commands.gc.desc": "limpia la ram mediante el recolector de basura (gc)", + "sputnik.commands.help.desc": "lista todos los comandos con su descripción", + "sputnik.commands.macro.added": "macro %s creada", + "sputnik.commands.macro.cleared": "líneas de %s borradas", + "sputnik.commands.macro.desc": "gestiona las macros", + "sputnik.commands.macro.exists": "ya hay una macro que se llama \"%s\"", + "sputnik.commands.macro.lineadded": "línea añadida a %s", + "sputnik.commands.macro.list": "%s macro(s):", + "sputnik.commands.macro.none": "no hay macros", + "sputnik.commands.macro.notfound": "macro \"%s\" no encontrada", + "sputnik.commands.macro.removed": "macro %s borrada", "sputnik.commands.panorama.desc": "crea un pack de textures con la panorámica basada en tu posición actual", "sputnik.commands.panorama.errorassemble": "no se pudo crear el pack; existe uno ya con ese nombre?", "sputnik.commands.panorama.packdesc": "generado por Sputnik", @@ -44,6 +54,15 @@ "sputnik.commands.plugins.singleplayer": "estás en un solo jugador", "sputnik.commands.prefix.changed": "prefijo cambiado a %s", "sputnik.commands.prefix.desc": "cambia el prefijo de los comandos", + "sputnik.commands.profile.deleted": "perfil %s borrado", + "sputnik.commands.profile.desc": "gestiona los perfiles de configuración", + "sputnik.commands.profile.invalidname": "nombre inválido: \"%s\"", + "sputnik.commands.profile.list": "%s perfil(es):", + "sputnik.commands.profile.loaded": "perfil %s cargado", + "sputnik.commands.profile.none": "no hay perfiles", + "sputnik.commands.profile.notfound": "perfil \"%s\" no encontrado", + "sputnik.commands.profile.saved": "ajustes actuales guardados en %s", + "sputnik.commands.profile.savefailed": "no se pudo guardar el perfil \"%s\"", "sputnik.commands.purge.desc": "elimina mensajes del chat", "sputnik.commands.repeat.desc": "repite un comando x veces", "sputnik.commands.repeatwait.desc": "combinación de $repetir y $esperar", @@ -555,6 +574,9 @@ "sputnik.module.yunquesconpluma.name": "yunques con pluma", "sputnik.module.zoom.desc": "lupa", "sputnik.module.zoom.name": "zoom", + "sputnik.profile.error.dir": "no se pudo crear el directorio de perfiles", + "sputnik.profile.error.loading": "error al cargar el perfil \"%s\"", + "sputnik.profile.error.saving": "error al guardar el perfil \"%s\"", "sputnik.setting.amigos.general.prevenirataques.desc": "no dejar que les ataques manualmente", "sputnik.setting.amigos.general.prevenirataques.name": "prevenir ataques", "sputnik.setting.amigos.general.rueditaparaanadiramigo.desc": "pulsara la ruedita del ratón para (des)marcar a un jugador como amigo", @@ -1452,10 +1474,29 @@ "sputnik.ui.friend.remove": "eliminar amigo", "sputnik.ui.friends.title": "amigos", "sputnik.ui.list.tooltip": "%s (%s de %s)", + "sputnik.ui.macros.empty": "todavía no hay macros", + "sputnik.ui.macros.instructions": "clic izq.: seleccionar / clic der.: asignar tecla / shift + clic der.: borrar", + "sputnik.ui.macros.lineinstructions": "shift + clic der. sobre una línea para quitarla", + "sputnik.ui.macros.lineplaceholder": "comando o mensaje...", + "sputnik.ui.macros.nameplaceholder": "nombre de la macro...", + "sputnik.ui.macros.presskey": "pulsa una tecla...", + "sputnik.ui.macros.title": "macros", "sputnik.ui.preview.echest": "echest", "sputnik.ui.preview.shulker": "previsualización del shulker", + "sputnik.ui.profiles.deleted": "borrado \"%s\"", + "sputnik.ui.profiles.empty": "todavía no hay perfiles", + "sputnik.ui.profiles.instructions": "clic izq.: cargar / clic der.: sobrescribir / shift + clic der.: borrar", + "sputnik.ui.profiles.invalidname": "nombre inválido: \"%s\"", + "sputnik.ui.profiles.loaded": "cargado \"%s\"", + "sputnik.ui.profiles.placeholder": "nombre del perfil...", + "sputnik.ui.profiles.saved": "guardado \"%s\"", + "sputnik.ui.profiles.title": "perfiles", "sputnik.ui.searchbar.placeholder": "buscar...", "sputnik.ui.settingsof": "ajustes de %s", + "sputnik.ui.tab.hud": "hud", + "sputnik.ui.tab.macros": "macros", + "sputnik.ui.tab.modules": "módulos", + "sputnik.ui.tab.profiles": "perfiles", "sputnik.update.clickhere": "CLIC AQUÍ", "sputnik.update.download": "descarga la última versión (%s) desde aquí:", "sputnik.update.idgaf": "me la pela",