From e0f85238098dd87c7d335ab0a70cfd05be9994ce Mon Sep 17 00:00:00 2001 From: retucio Date: Tue, 18 Aug 2026 13:35:24 +0200 Subject: [PATCH] v5.6 --- cambios.md | 17 +- gradle.properties | 2 +- src/main/java/me/retucio/sputnik/Sputnik.java | 13 +- .../sputnik/config/BinarySerializer.java | 53 +++-- .../retucio/sputnik/config/ClientConfig.java | 2 +- .../retucio/sputnik/config/ConfigManager.java | 216 ++++++++++++------ .../mixins/render/LevelRendererMixin.java | 2 + .../me/retucio/sputnik/module/Module.java | 4 - .../retucio/sputnik/module/ModuleManager.java | 1 + .../module/modules/camera/Freecam.java | 5 +- .../module/modules/combat/HoldAttack.java | 95 ++++++++ .../module/modules/render/Particles.java | 6 +- .../module/setting/settings/KeySetting.java | 12 +- .../module/setting/settings/ListSetting.java | 15 +- .../setting/settings/NumberSetting.java | 4 + .../setting/settings/OptionSetting.java | 35 ++- .../me/retucio/sputnik/ui/widgets/Panel.java | 19 +- .../ui/widgets/panels/CloseablePanel.java | 3 +- .../ui/widgets/panels/ExtendablePanel.java | 3 +- .../ui/widgets/panels/FriendsPanel.java | 7 +- .../ui/widgets/panels/ModulePanel.java | 6 +- .../ui/widgets/panels/SettingsPanel.java | 11 +- .../panels/settings/ClientSettingsPanel.java | 5 +- .../me/retucio/sputnik/util/I18nUtil.java | 15 ++ .../java/me/retucio/sputnik/util/Lists.java | 31 ++- .../sputnik/util/render/Pipelines.java | 24 +- .../sputnik/util/render/SputnikBuffers.java | 47 ++-- .../resources/assets/sputnik/lang/en_us.json | 15 ++ .../resources/assets/sputnik/lang/es_es.json | 15 ++ 29 files changed, 502 insertions(+), 181 deletions(-) create mode 100644 src/main/java/me/retucio/sputnik/module/modules/combat/HoldAttack.java diff --git a/cambios.md b/cambios.md index 04dc1b4..4769871 100644 --- a/cambios.md +++ b/cambios.md @@ -6,13 +6,24 @@ #### módulos nuevos: -#### comandos nuevos: +- autoataque: mantén el botón de ataque pulsado para atacar automáticamente -- `$panorama`: construye un pack de texturas con una panorámica basada en tu posición actual. +#### comandos nuevos: ### arreglos / mejoras -- arreglado ScreenshotMixin, es decir, que ya no crasheará al tomar capturas +- 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 ## problemas reconocidos diff --git a/gradle.properties b/gradle.properties index 3514ceb..f4b5ab9 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.5 +mod_version=5.6 maven_group=me.retucio archives_base_name=sputnik \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/Sputnik.java b/src/main/java/me/retucio/sputnik/Sputnik.java index e4144aa..b23b7ef 100644 --- a/src/main/java/me/retucio/sputnik/Sputnik.java +++ b/src/main/java/me/retucio/sputnik/Sputnik.java @@ -135,11 +135,8 @@ public class Sputnik implements ClientModInitializer { // se ejecuta cada tick, es decir, 20 veces por segundo public void onTick() { - if (!settingsApplied - && ConfigManager.INSTANCE.getConfig() != null - && !ConfigManager.INSTANCE.hasLoaded()) { - ConfigManager.INSTANCE.applyConfig(); - settingsApplied = true; + if (!settingsApplied && !ConfigManager.INSTANCE.hasLoaded()) { + settingsApplied = ConfigManager.INSTANCE.applyConfig(); } ModuleManager.INSTANCE.getEnabledModules().forEach(Module::onTick); @@ -161,7 +158,7 @@ public class Sputnik implements ClientModInitializer { if (BindCommand.onKeyPress(key)) return; if (action == GLFW.GLFW_PRESS) { handleModuleToggle(key, anyFocused); - handleClickGUIKey(key, anyFocused); + handleClickGuiKey(key, anyFocused); handleHudEditorKey(key, anyFocused); } // solo reenviar teclas del teclado @@ -208,7 +205,7 @@ public class Sputnik implements ClientModInitializer { } // maneja la lógica de apertura de la interfaz - private void handleClickGUIKey(int key, boolean anyFocused) { + private void handleClickGuiKey(int key, boolean anyFocused) { if (key != ClientSettingsPanel.clientSettings.getKey() || anyFocused || isOnTypingScreen()) return; @@ -221,7 +218,7 @@ public class Sputnik implements ClientModInitializer { } } - // manejar la lógica de apertura del editor de elementos del hud, con la misma lógica que handleClickGUIKey() + // manejar la lógica de apertura del editor de elementos del hud, con la misma lógica que handleClickGuiKey() private void handleHudEditorKey(int key, boolean anyFocused) { Hud hud = ModuleManager.INSTANCE.getModuleByClass(Hud.class); if (key != hud.editorKey.getValue() || anyFocused || isOnTypingScreen()) return; diff --git a/src/main/java/me/retucio/sputnik/config/BinarySerializer.java b/src/main/java/me/retucio/sputnik/config/BinarySerializer.java index d3ace7c..0c5a597 100644 --- a/src/main/java/me/retucio/sputnik/config/BinarySerializer.java +++ b/src/main/java/me/retucio/sputnik/config/BinarySerializer.java @@ -1,6 +1,6 @@ package me.retucio.sputnik.config; -import net.minecraft.network.chat.Component; +import me.retucio.sputnik.util.I18nUtil; import java.io.*; import java.nio.charset.StandardCharsets; @@ -85,44 +85,59 @@ public class BinarySerializer { public static ClientConfig readConfig(File file) throws IOException { if (!file.exists() || file.length() == 0) { - throw new IOException(Component.translatable("sputnik.config.error.empty").getString()); + throw new IOException(I18nUtil.fmt("sputnik.config.error.empty", + "archivo de config. vacío o inexistente")); } try (DataInputStream in = new DataInputStream( new BufferedInputStream(new FileInputStream(file)))) { if (file.length() < 8) { - throw new IOException(Component.translatable("sputnik.config.error.toosmall").getString()); + throw new IOException(I18nUtil.fmt("sputnik.config.error.toosmall", + "archivo demasiado pequeño para ser válido")); } // verificar cabezal int magic = in.readInt(); if (magic != MAGIC_NUMBER) { - throw new IOException(Component.translatable("sputnik.config.error.invalidformat", magic).getString()); + throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidformat", + "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) { - throw new IOException(Component.translatable("sputnik.config.error.invalidversion", version).getString()); + throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidversion", + "versión inválida: %s", version)); } - ClientConfig config = new ClientConfig(); - - // leer mapas - config.moduleStates = readStringBooleanMap(in); - config.settings = readSettingsMap(in); - config.settingsPanels = readStringIntArrayMap(in); - config.extendablePanels = readExtendablePanels(in); - config.hudPositions = readStringIntArrayMap(in); - config.hudVisibilities = readStringBooleanMap(in); - config.hudImagePaths = readStringStringMap(in); - config.friends = readStringStringMap(in); + // leer mapas. se leen a locales y la ClientConfig se construye al final: su + // constructor la suscribe al bus de eventos, así que crearla antes de terminar + // de leer dejaba una config. huérfana escuchando si el archivo estaba corrupto + Map moduleStates = readStringBooleanMap(in); + Map settings = readSettingsMap(in); + Map settingsPanels = readStringIntArrayMap(in); + Map extendablePanels = readExtendablePanels(in); + Map hudPositions = readStringIntArrayMap(in); + Map hudVisibilities = readStringBooleanMap(in); + Map hudImagePaths = readStringStringMap(in); + Map friends = readStringStringMap(in); // leer pos. de la barra de búsqueda - if (in.readBoolean()) { - config.searchBarPosition = new int[]{in.readInt(), in.readInt()}; - } + int[] searchBarPosition = in.readBoolean() + ? new int[]{in.readInt(), in.readInt()} + : null; + + ClientConfig config = new ClientConfig(); + config.moduleStates = moduleStates; + config.settings = settings; + config.settingsPanels = settingsPanels; + config.extendablePanels = extendablePanels; + config.hudPositions = hudPositions; + config.hudVisibilities = hudVisibilities; + config.hudImagePaths = hudImagePaths; + config.friends = friends; + if (searchBarPosition != null) config.searchBarPosition = searchBarPosition; return config; } diff --git a/src/main/java/me/retucio/sputnik/config/ClientConfig.java b/src/main/java/me/retucio/sputnik/config/ClientConfig.java index 8aafca1..0a34a77 100644 --- a/src/main/java/me/retucio/sputnik/config/ClientConfig.java +++ b/src/main/java/me/retucio/sputnik/config/ClientConfig.java @@ -71,7 +71,7 @@ public class ClientConfig { case StringSetting s: value = s.getValue(); break; case ListSetting l: value = l.getConfigValues(); break; case ColorSetting c: value = c.getConfigValue(); break; - case OptionSetting o: value = o.getDisplayName(); break; + case OptionSetting o: value = o.getConfigName(); break; default: break; } diff --git a/src/main/java/me/retucio/sputnik/config/ConfigManager.java b/src/main/java/me/retucio/sputnik/config/ConfigManager.java index 0a817c3..ee891a1 100644 --- a/src/main/java/me/retucio/sputnik/config/ConfigManager.java +++ b/src/main/java/me/retucio/sputnik/config/ConfigManager.java @@ -19,6 +19,7 @@ import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.widgets.panels.settings.ClientSettingsPanel; import me.retucio.sputnik.ui.widgets.panels.SettingsPanel; import me.retucio.sputnik.util.ChatUtil; +import me.retucio.sputnik.util.I18nUtil; import net.minecraft.network.chat.Component; import java.io.File; @@ -30,7 +31,7 @@ import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; // se ocupa de guardar, cargar y aplicar ajustes @@ -42,8 +43,9 @@ public class ConfigManager { public static ConfigManager INSTANCE; private boolean loaded = false; + private boolean applying = false; private boolean dirty = false; - private long lastDirtyTime = 0L; + private long lastSaveTime = System.currentTimeMillis(); private ClientConfig config = null; private final ExecutorService saveExecutor = Executors.newSingleThreadExecutor(r -> { @@ -52,7 +54,10 @@ public class ConfigManager { return t; }); - private final AtomicBoolean saveQueued = new AtomicBoolean(false); + // instantánea pendiente de escribir. si llega otra antes de que el hilo de fondo escriba, + // sustituye a la anterior en vez de descartarse (que era lo que pasaba con un simple + // "ya hay uno en cola": se tiraba justo la más reciente y se perdían esos cambios) + private final AtomicReference pending = new AtomicReference<>(); // ------------------ MÉTODOS PRINCIPALES ------------------ @@ -60,58 +65,81 @@ public class ConfigManager { // guardar configuraciones public void save() { if (!loaded) return; - ensureConfig(); - - // serializar a bytes en el hilo principal (snapshot inmediato, evita race conditions) - final byte[] snapshot; - try { - snapshot = BinarySerializer.toBytes(config); - } catch (IOException e) { - Sputnik.LOGGER.error(Component.translatable("sputnik.config.error.serialization").getString(), e); - return; - } - - // si ya hay un guardado en cola, no añadir otro - if (!saveQueued.compareAndSet(false, true)) return; - - saveExecutor.execute(() -> { - try { - BinarySerializer.writeBytes(snapshot, CONFIG_FILE); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.saved").getString()); - } catch (IOException e) { - Sputnik.LOGGER.error(Component.translatable("sputnik.config.error.saving").getString(), e); - } finally { - saveQueued.set(false); - } - }); + writeSnapshot(false); } public void saveOnShutdown() { - // esperar a que termine cualquier guardado en curso, luego guardar síncronamente + // no aceptar más tareas y esperar a que termine el guardado en curso saveExecutor.shutdown(); try { // dar hasta 5 segundos para que termine el guardado en curso if (!saveExecutor.awaitTermination(5, TimeUnit.SECONDS)) { - Sputnik.LOGGER.warn(Component.translatable("sputnik.config.warn.thread").getString()); + Sputnik.LOGGER.warn(I18nUtil.fmt("sputnik.config.warn.thread", + "el hilo de guardado no terminó a tiempo")); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - // guardado final sincrónico por si había cambios pendientes no guardados - if (dirty) save(); + // el guardado final va en este mismo hilo: el executor ya está cerrado, así que + // encolar la escritura lanzaba RejectedExecutionException y no se guardaba NADA al + // cerrar el juego. o sea que se perdía todo lo hecho desde el último autoguardado + if (!loaded) return; + writeSnapshot(true); + } + + // serializa en el hilo actual (instantánea inmediata, evita race conditions) y escribe + // en el de fondo, salvo que se pida síncrono (al cerrar) + private void writeSnapshot(boolean sync) { + ensureConfig(); + + final byte[] snapshot; + try { + snapshot = BinarySerializer.toBytes(config); + } catch (IOException e) { + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.serialization", + "error al serializar ajustes"), e); + return; + } + + dirty = false; + lastSaveTime = System.currentTimeMillis(); + + if (sync) { + writeToDisk(snapshot); + return; + } + + // si ya había una instantánea esperando, la tarea encolada recogerá ésta + if (pending.getAndSet(snapshot) != null) return; + + saveExecutor.execute(() -> { + byte[] bytes = pending.getAndSet(null); + if (bytes != null) writeToDisk(bytes); + }); + } + + private static void writeToDisk(byte[] bytes) { + try { + BinarySerializer.writeBytes(bytes, CONFIG_FILE); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.saved", "ajustes guardados")); + } catch (IOException e) { + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.saving", + "error al guardar ajustes"), e); + } } public void load() { - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.loading").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.loading", "cargando ajustes...")); if (CONFIG_FILE.exists()) { try { - config = BinarySerializer.readConfig(CONFIG_FILE); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.loaded").getString()); + setConfig(BinarySerializer.readConfig(CONFIG_FILE)); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.loaded", "ajustes cargados")); return; } catch (IOException e) { - Sputnik.LOGGER.error(Component.translatable("sputnik.config.error.loading").getString(), e); + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.loading", + "error cargando ajustes, intentando json"), e); } } @@ -119,35 +147,61 @@ public class ConfigManager { if (LEGACY_JSON_FILE.exists()) { try (FileReader reader = new FileReader(LEGACY_JSON_FILE)) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); - config = gson.fromJson(reader, ClientConfig.class); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.json").getString()); + setConfig(gson.fromJson(reader, ClientConfig.class)); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.json", + "ajustes json cargados (migración)")); - save(); + // aquí `loaded` todavía es false (se pone al aplicar), así que save() no + // haría nada: la migración se escribe directamente + writeSnapshot(true); return; } catch (IOException e) { - Sputnik.LOGGER.error(Component.translatable("sputnik.config.error.json").getString(), e); + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.json", + "error cargando json, usando defaults"), e); } } // crear nuevo config ensureConfig(); - save(); + writeSnapshot(true); } - // aplicar configuraciones cargadas - public void applyConfig() { - if (!shouldApply()) return; + // aplicar configuraciones cargadas. devuelve false si todavía no se puede (falta algo por + // construir), para que se reintente en el siguiente tick en vez de quedarse sin aplicar + public boolean applyConfig() { + if (!shouldApply()) return false; - applyModuleStates(); - applyModuleSettings(); - applySettingsPanels(); - applySearchBarPosition(); - applyClientSettings(); - applyExtendablePanels(); - applyFriends(); + applying = true; + try { + // los del mod primero: entre ellos está "teclas multimódulo", que decide si al + // asignar una tecla se le quita a los demás módulos. aplicándolos los últimos, + // las teclas de los módulos se cargaban con el valor por defecto de ese ajuste + applyClientSettings(); + applyModuleStates(); + applyModuleSettings(); + applySettingsPanels(); + applySearchBarPosition(); + applyExtendablePanels(); + applyFriends(); - loaded = true; - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.applied").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.applied", "ajustes aplicados")); + } catch (Exception e) { + // si algo peta a medio aplicar no se puede dejar `loaded` en false: save() no + // haría nada en toda la sesión y se perdería la config. entera al cerrar + Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.applying", + "error aplicando ajustes"), e); + } finally { + applying = false; + loaded = true; + } + + return true; + } + + // 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 + public boolean isApplying() { + return applying; } // obtener configuración actual @@ -213,7 +267,8 @@ public class ConfigManager { friend.setName(name); FriendManager.INSTANCE.add(friend); }); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.friends").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.friends", + "amigos aplicados")); } @SuppressWarnings({"rawtypes", "unchecked"}) @@ -233,8 +288,8 @@ public class ConfigManager { case ListSetting listSetting -> applyListSetting(listSetting, value); case ColorSetting colorSetting -> applyColorSetting(colorSetting, value); case OptionSetting optionSetting -> applyOptionSetting(optionSetting, value); - default -> Sputnik.LOGGER.warn(Component.translatable("sputnik.config.warn.unknownsetting", - setting.getClass().getSimpleName()).getString()); + default -> Sputnik.LOGGER.warn(I18nUtil.fmt("sputnik.config.warn.unknownsetting", + "watafac queseso: %s", setting.getClass().getSimpleName())); } } @@ -244,14 +299,16 @@ public class ConfigManager { module.setEnabled(config.moduleStates.get(module.getName())); } }); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.modulestates").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.modulestates", + "estados de módulos aplicados")); } private void applyModuleSettings() { ModuleManager.INSTANCE.getModules().forEach(module -> { module.getSettings().forEach(this::applySetting); }); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.modulesettings").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.modulesettings", + "ajustes de módulos aplicados")); } private void applySettingsPanels() { @@ -259,14 +316,16 @@ public class ConfigManager { Module module = ModuleManager.INSTANCE.getModuleByName(moduleName); if (module == null) { - Sputnik.LOGGER.warn(Component.translatable("sputnik.config.warn.nullmodule", moduleName).getString()); + Sputnik.LOGGER.warn(I18nUtil.fmt("sputnik.config.warn.nullmodule", + "\"null\" como módulo (\"%s\")", moduleName)); return; } ClickGui.INSTANCE.openSettingsPanel(module, position[0], position[1]); }); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.settingspanels").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.settingspanels", + "estados de marcos de ajustes aplicados")); } private void applySearchBarPosition() { @@ -274,13 +333,15 @@ public class ConfigManager { ClickGui.INSTANCE.getSearchBar().setX(config.searchBarPosition[0]); ClickGui.INSTANCE.getSearchBar().setY(config.searchBarPosition[1]); } - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.searchbar").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.searchbar", + "posición de la barra de búsqueda aplicada")); } private void applyClientSettings() { ClientSettingsPanel.clientSettings.setEnabled(true); ClientSettingsPanel.clientSettings.getSettings().forEach(this::applySetting); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.clientsettings").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.clientsettings", + "ajustes del cliente aplicados")); } private void applyExtendablePanels() { @@ -290,7 +351,8 @@ public class ConfigManager { applyExtendablePanel(ClickGui.INSTANCE.getClientSettingsPanel()); applyExtendablePanel(ClickGui.INSTANCE.getFriendsPanel()); - Sputnik.LOGGER.info(Component.translatable("sputnik.config.info.extendablepanels").getString()); + Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.extendablepanels", + "posiciones de marcos extendibles aplicadas")); ClickGui.INSTANCE.refreshListButtons(); } @@ -359,8 +421,14 @@ public class ConfigManager { Map convertedValues = new HashMap<>(); for (T option : setting.getOptions()) { - String key = setting.getDisplayName(option); + String key = setting.getConfigName(option); Object booleanValue = map.get(key); + + // compatibilidad con configs. de antes, cuando la clave era el nombre ya + // traducido (el de vanilla en inglés). sin esto la lista entera se quedaba + // en false, o sea que al usuario se le borraba lo que tuviera marcado + if (booleanValue == null) booleanValue = map.get(I18nUtil.vanillaName(key)); + convertedValues.put(option, booleanValue instanceof Boolean && (Boolean) booleanValue); } @@ -411,12 +479,24 @@ public class ConfigManager { return setting.getSg().getModule().getName() + ":" + setting.getTrueName(); } + // ClickGui incluida: los marcos y la barra de búsqueda se aplican sobre ella, y se + // construye en una tarea aparte. si todavía no está, mejor esperar al siguiente tick private boolean shouldApply() { - return Sputnik.mc != null && ModuleManager.INSTANCE != null && config != null; + return Sputnik.mc != null && ModuleManager.INSTANCE != null + && ClickGui.INSTANCE != null && config != null; } private void ensureConfig() { - if (config == null) config = new ClientConfig(); + if (config == null) setConfig(new ClientConfig()); + } + + // ClientConfig se suscribe al bus en su constructor, así que al reemplazarla hay que + // dar de baja a la anterior: si no, la vieja seguía escuchando y escribiendo en un + // mapa que ya no era el que se guarda + private void setConfig(ClientConfig newConfig) { + if (config == newConfig) return; + if (config != null) Sputnik.EVENT_BUS.unsubscribe(config); + config = newConfig; } public boolean hasLoaded() { @@ -425,7 +505,6 @@ public class ConfigManager { public void markDirty() { this.dirty = true; - this.lastDirtyTime = System.currentTimeMillis(); } public boolean isDirty() { @@ -438,13 +517,14 @@ public class ConfigManager { ClientSettingsModule settings = ClientSettingsPanel.clientSettings; if (!settings.autosave.getValue()) return; + // desde el último guardado, no desde el último cambio: contando desde el cambio, + // tocando ajustes cada menos del intervalo no se autoguardaba nunca long intervalMs = (long) (settings.autosaveInterval.getValue() * 1000); - if (System.currentTimeMillis() - lastDirtyTime < intervalMs) return; + if (System.currentTimeMillis() - lastSaveTime < intervalMs) return; HudRenderer.INSTANCE.pushNotification( Component.translatable("sputnik.config.autosaving.title").getString(), Component.translatable("sputnik.config.autosaving.desc").getString()); save(); - dirty = false; } } \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/render/LevelRendererMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/render/LevelRendererMixin.java index baa526b..b9325ee 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/render/LevelRendererMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/render/LevelRendererMixin.java @@ -10,6 +10,7 @@ import me.retucio.sputnik.event.render.Render3DEvent; import me.retucio.sputnik.event.render.RenderBlockOutlineEvent; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.camera.Freecam; +import me.retucio.sputnik.util.render.SputnikBuffers; import net.minecraft.client.DeltaTracker; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.SubmitNodeCollector; @@ -42,6 +43,7 @@ public abstract class LevelRendererMixin { profiler.push(Sputnik.MOD_ID + "-3d"); EVENT_BUS.post(new Render3DEvent(matrices, dt, cameraState)); + SputnikBuffers.flush(); // antes de que empiece el hud profiler.pop(); matrices.popPose(); diff --git a/src/main/java/me/retucio/sputnik/module/Module.java b/src/main/java/me/retucio/sputnik/module/Module.java index 5ae817e..aaec352 100644 --- a/src/main/java/me/retucio/sputnik/module/Module.java +++ b/src/main/java/me/retucio/sputnik/module/Module.java @@ -83,10 +83,6 @@ public class Module { this.bind.setDefaultValue(key); // puto marcos - // la clave tiene que salir igual que en ConfigManager#getSettingKey, o sea con los - // nombres internos: Module#getName ya lo es, pero en los ajustes el traducido es - // getName() y el interno getTrueName(). si no cuadran no se encuentra la tecla - // guardada y se le pisa al usuario con la de por defecto if (ConfigManager.INSTANCE.getConfig().settings .get(this.getName() + ":" + bind.getTrueName()) == null) this.bind.setValue(key); diff --git a/src/main/java/me/retucio/sputnik/module/ModuleManager.java b/src/main/java/me/retucio/sputnik/module/ModuleManager.java index 866e7a5..d3c6c22 100644 --- a/src/main/java/me/retucio/sputnik/module/ModuleManager.java +++ b/src/main/java/me/retucio/sputnik/module/ModuleManager.java @@ -89,6 +89,7 @@ public class ModuleManager { add(new AttributeSwapper()); add(new ChestplateSwitch()); add(new CrystalAura()); + add(new HoldAttack()); add(new MaceKill()); add(new ProjectileTrajectories()); add(new SpearKill()); diff --git a/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java b/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java index ea3c33c..f931140 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java +++ b/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java @@ -177,15 +177,14 @@ public class Freecam extends Module { super.onEnable(); } - // allChanged() ya no existe; ahora hay que pasarle el estado a mano private static void invalidateChunks() { if (mc.level == null) return; - mc.levelRenderer.invalidateCompiledGeometry(mc.level, mc.options, mc.gameRenderer.mainCamera(), mc.getBlockColors()); + mc.levelExtractor.allChanged(); } @Override public void onDisable() { - if (reloadChunks.getValue()) mc.execute(Freecam::invalidateChunks); + if (reloadChunks.getValue()) invalidateChunks(); mc.options.setCameraType(perspective); crouching = false; diff --git a/src/main/java/me/retucio/sputnik/module/modules/combat/HoldAttack.java b/src/main/java/me/retucio/sputnik/module/modules/combat/HoldAttack.java new file mode 100644 index 0000000..562b50b --- /dev/null +++ b/src/main/java/me/retucio/sputnik/module/modules/combat/HoldAttack.java @@ -0,0 +1,95 @@ +package me.retucio.sputnik.module.modules.combat; + +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.EnumSetting; +import me.retucio.sputnik.module.setting.settings.NumberSetting; +import net.minecraft.world.phys.EntityHitResult; + + +/** + * @author retucio + */ + +public class HoldAttack extends Module { + + private final EnumSetting mode = sgGeneral.add(new EnumSetting<>( + "modo de ataque", + "cómo atacar", + AttackMode.class, + AttackMode.TIME + )); + + private final NumberSetting timeCooldown = sgGeneral.add(new NumberSetting( + "cooldown", + "cómo de llena tiene que estar la barra de ataque antes de atacar", + 84.8f, 0.0f, 100.0f, 0.1f + )).visibility(() -> mode.is(AttackMode.TIME)); + + private final NumberSetting spamCooldown = sgGeneral.add(new NumberSetting( + "cooldown ", + "cada cuánto atacar, en ticks", + 0, 0, 40, 1 + )).visibility(() -> mode.is(AttackMode.SPAM)); + + private final BooleanSetting attackInScreens = sgGeneral.add(new BooleanSetting( + "atacar en pantallas", + "atacar aun estando en una pantalla", + false + )); + + private int counter = 0; + + public HoldAttack() { + super("autoataque", "mantén pulsado el clic para atacar automáticamente", Category.COMBAT); + mode.onUpdate(v -> counter = 0); + } + + @Override + public void onEnable() { + counter = 0; + super.onEnable(); + } + + @Override + public void onTick() { + if (mc.player == null || mc.gameMode == null || mc.getCameraEntity() == null) return; + + counter++; + + if (mc.gui.screen() != null && !attackInScreens.getValue()) { + return; + } + + if (!mc.options.keyAttack.isDown()) { + return; + } + + if (!(mc.hitResult instanceof EntityHitResult ehr)) { + return; + } + + if (mode.is(AttackMode.TIME)) { + if (mc.player.getAttackStrengthScale(0) < timeCooldown.getNormalizedValue()) { + return; + } + } else if (mode.is(AttackMode.SPAM)) { + if (counter < spamCooldown.getIntValue()) { + return; + } + } + + mc.gameMode.attack(mc.player, ehr.getEntity()); + counter = 0; + } + + private enum AttackMode { + SPAM("spamear"), + TIME("timear"); + + private final String name; + AttackMode(String name) { this.name = name; } + @Override public String toString() { return name; } + } +} diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/Particles.java b/src/main/java/me/retucio/sputnik/module/modules/render/Particles.java index e98887c..d32c7bd 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/Particles.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/Particles.java @@ -8,7 +8,6 @@ import me.retucio.sputnik.module.setting.settings.NumberSetting; import me.retucio.sputnik.util.Lists; import net.minecraft.core.particles.SimpleParticleType; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.chat.Component; import net.minecraft.world.phys.Vec3; import java.util.List; @@ -26,9 +25,10 @@ public class Particles extends Module { .map(particleType -> (SimpleParticleType) particleType) .toList(); + // claves de traducción, no texto traducido (ver Lists#init) Map particleNames = Lists.getMapOfLists(particleList, - particleList.stream().map(particle -> Component.translatable( - BuiltInRegistries.PARTICLE_TYPE.getKey(particle).toShortLanguageKey()).getString()) + particleList.stream().map(particle -> + BuiltInRegistries.PARTICLE_TYPE.getKey(particle).toShortLanguageKey()) .toList()); private final ListSetting particles = sgGeneral.add(new ListSetting<>( diff --git a/src/main/java/me/retucio/sputnik/module/setting/settings/KeySetting.java b/src/main/java/me/retucio/sputnik/module/setting/settings/KeySetting.java index f7d6574..bd101ae 100644 --- a/src/main/java/me/retucio/sputnik/module/setting/settings/KeySetting.java +++ b/src/main/java/me/retucio/sputnik/module/setting/settings/KeySetting.java @@ -1,5 +1,6 @@ package me.retucio.sputnik.module.setting.settings; +import me.retucio.sputnik.config.ConfigManager; import me.retucio.sputnik.module.Module; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.setting.Setting; @@ -32,8 +33,17 @@ public class KeySetting extends Setting { // con "teclas multimódulo" puesto se permite repetir tecla, que es justo lo que // dice el ajuste. antes se le quitaba la tecla al otro módulo igualmente, así que - // el ajuste sólo servía para las teclas de vanilla + // el ajuste sólo servía para las teclas de vanilla. + // + // mientras se carga la config. no se toca nada: son las teclas que el usuario ya + // tenía, y quitárselas aquí se las machacaba (y encima guardaba el -1). + // + // clientSettings puede ser null: el módulo de ajustes se construye en el + // inicializador estático de ClientSettingsPanel, y su propia tecla por defecto pasa + // por aquí antes de que el campo esté asignado if (ModuleManager.INSTANCE != null + && ClientSettingsPanel.clientSettings != null + && !ConfigManager.INSTANCE.isApplying() && !ClientSettingsPanel.clientSettings.multipleKeybinds.getValue()) { for (Module module : ModuleManager.INSTANCE.getModules()) if (module.getKey() == value && value != -1) { diff --git a/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java b/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java index aa1bd24..152b002 100644 --- a/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java +++ b/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java @@ -1,6 +1,7 @@ package me.retucio.sputnik.module.setting.settings; import me.retucio.sputnik.module.setting.Setting; +import me.retucio.sputnik.util.I18nUtil; import java.util.*; @@ -62,7 +63,7 @@ public class ListSetting extends Setting> { public Map getConfigValues() { Map configValues = new HashMap<>(); for (T option : options) { - configValues.put(getDisplayName(option), value.getOrDefault(option, false)); + configValues.put(getConfigName(option), value.getOrDefault(option, false)); } return configValues; } @@ -103,10 +104,20 @@ public class ListSetting extends Setting> { return displayNames != null ? new HashMap<>(displayNames) : null; } - public String getDisplayName(T key) { + // OJO: nombre interno de la opción. es la clave de la config. (ver + // ConfigManager#applyListSetting) y para las listas del juego es la clave de + // traducción ("entity.minecraft.zombie"), que no depende del idioma. no traducir + public String getConfigName(T key) { if (displayNames != null && displayNames.containsKey(key)) { return displayNames.get(key); } return key != null ? key.toString() : "?"; } + + // nombre visible: se traduce aquí. antes se mostraba el nombre interno tal cual, o sea + // los nombres de vanilla en inglés aunque el juego estuviera en otro idioma + public String getDisplayName(T key) { + String name = getConfigName(key); + return I18nUtil.orElse(name, name); + } } \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/setting/settings/NumberSetting.java b/src/main/java/me/retucio/sputnik/module/setting/settings/NumberSetting.java index a39685d..f6b3513 100644 --- a/src/main/java/me/retucio/sputnik/module/setting/settings/NumberSetting.java +++ b/src/main/java/me/retucio/sputnik/module/setting/settings/NumberSetting.java @@ -57,6 +57,10 @@ public class NumberSetting extends Setting { return value.byteValue(); } + public double getNormalizedValue() { + return (value - min ) / (max - min); + } + public void setValue(double value) { if (this.value == value || this.locked) return; double clamped = clamp(value, min, max); diff --git a/src/main/java/me/retucio/sputnik/module/setting/settings/OptionSetting.java b/src/main/java/me/retucio/sputnik/module/setting/settings/OptionSetting.java index 3445ed0..aafaeae 100644 --- a/src/main/java/me/retucio/sputnik/module/setting/settings/OptionSetting.java +++ b/src/main/java/me/retucio/sputnik/module/setting/settings/OptionSetting.java @@ -1,6 +1,7 @@ package me.retucio.sputnik.module.setting.settings; import me.retucio.sputnik.module.setting.Setting; +import me.retucio.sputnik.util.I18nUtil; import java.util.List; import java.util.Map; @@ -26,14 +27,19 @@ public class OptionSetting extends Setting { if (options.contains(option)) super.setValue(option); } + // por nombre interno, no por el traducido: es lo que se guarda en la config. public void setValueByName(String name) { for (T opt : options) { - if (displayNames != null && !displayNames.isEmpty()) { - if (displayNames.get(opt).equals(name)) { - setValue(opt); - return; - } - } else if (opt.toString().equals(name)) { + if (getConfigName(opt).equals(name)) { + setValue(opt); + return; + } + } + + // compatibilidad con configs. de antes, cuando se guardaba el nombre ya traducido + // (el de vanilla en inglés). si no, la opción guardada se perdía y volvía a la de serie + for (T opt : options) { + if (I18nUtil.vanillaName(getConfigName(opt)).equals(name)) { setValue(opt); return; } @@ -82,9 +88,22 @@ public class OptionSetting extends Setting { return displayNames; } + // OJO: nombre interno de la opción, o sea la clave de la config. (ver ClientConfig + // #onUpdateSetting). igual que en ListSetting, para las listas del juego es la clave + // de traducción. no traducir + public String getConfigName(T key) { + if (displayNames != null && displayNames.containsKey(key)) return displayNames.get(key); + return key != null ? key.toString() : "?"; + } + + public String getConfigName() { + return getConfigName(value); + } + + // nombre visible, traducido public String getDisplayName(T key) { - if (displayNames != null) return displayNames.get(key); - return key.toString(); + String name = getConfigName(key); + return I18nUtil.orElse(name, name); } public String getDisplayName() { diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/Panel.java b/src/main/java/me/retucio/sputnik/ui/widgets/Panel.java index 2f69f7c..5ebe1a8 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/Panel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/Panel.java @@ -4,11 +4,16 @@ import net.minecraft.client.gui.GuiGraphicsExtractor; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; public abstract class Panel extends Widget { - protected String title; + // el título se resuelve al pintarlo, no al construir el marco: los marcos se crean en + // la inicialización del mod, cuando el idioma todavía no ha cargado, así que guardar el + // texto ya traducido dejaba los títulos en español para siempre (y no cambiaban aunque + // se cambiara el idioma del juego en marcha) + private Supplier title; protected final List buttons = new ArrayList<>(); protected List visibleButtons = new ArrayList<>(); @@ -20,10 +25,22 @@ public abstract class Panel extends Widget { protected boolean dragging; public Panel(String title, int x, int y, int w, int h) { + this(() -> title, x, y, w, h); + } + + public Panel(Supplier title, int x, int y, int w, int h) { super(x, y, w, h); this.title = title; } + public String getTitle() { + return title.get(); + } + + protected void setTitle(Supplier title) { + this.title = title; + } + public void drawTooltips(GuiGraphicsExtractor gui, int mouseX, int mouseY) { for (Button button : visibleButtons) button.drawTooltip(gui, mouseX, mouseY); } diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/CloseablePanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/CloseablePanel.java index 5387756..71e2a08 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/CloseablePanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/CloseablePanel.java @@ -4,12 +4,13 @@ import me.retucio.sputnik.ui.widgets.Button; import me.retucio.sputnik.ui.widgets.Panel; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.network.chat.Component; +import java.util.function.Supplier; public abstract class CloseablePanel extends Panel { protected static final int PADDING = 4; - public CloseablePanel(String title, int x, int y, int w, int h) { + public CloseablePanel(Supplier title, int x, int y, int w, int h) { super(title, x, y, w, h); } diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/ExtendablePanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/ExtendablePanel.java index ad26c4b..80ccadf 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/ExtendablePanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/ExtendablePanel.java @@ -4,11 +4,12 @@ import me.retucio.sputnik.ui.widgets.Button; import me.retucio.sputnik.ui.widgets.Panel; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.network.chat.Component; +import java.util.function.Supplier; public abstract class ExtendablePanel extends Panel { protected boolean extended; - public ExtendablePanel(String title, int x, int y, int w, int h) { + public ExtendablePanel(Supplier title, int x, int y, int w, int h) { super(title, x, y, w, h); this.extended = true; } diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/FriendsPanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/FriendsPanel.java index 21d0785..f323f1c 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/FriendsPanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/FriendsPanel.java @@ -9,6 +9,7 @@ import me.retucio.sputnik.friend.FriendManager; import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.widgets.buttons.FriendButton; import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.I18nUtil; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.network.chat.Component; @@ -20,7 +21,7 @@ public class FriendsPanel extends ExtendablePanel { private int offset; public FriendsPanel(int x, int y, int w, int h) { - super("amigos", x, y, w, h); + super(() -> I18nUtil.orElse("sputnik.ui.friends.title", "amigos"), x, y, w, h); dragging = false; Sputnik.EVENT_BUS.subscribe(this); @@ -34,7 +35,7 @@ public class FriendsPanel extends ExtendablePanel { @Override protected void updateWidth() { - int maxWidth = mc.font.width(title) + mc.font.width(" +"); + int maxWidth = mc.font.width(getTitle()) + mc.font.width(" +"); for (FriendButton button : buttons) { int textWidth = mc.font.width(button.getFriend().getName()); maxWidth = Math.max(maxWidth, textWidth); @@ -47,7 +48,7 @@ public class FriendsPanel extends ExtendablePanel { updateWidth(); gui.fill(x, renderY, x + w, renderY + h, Colors.mainColor.getRGB()); - gui.text(mc.font, Component.literal(title), + gui.text(mc.font, Component.literal(getTitle()), x + 8, renderY + (h / 2) - (mc.font.lineHeight / 2), -1, true); diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/ModulePanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/ModulePanel.java index b3cfee2..0973057 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/ModulePanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/ModulePanel.java @@ -21,7 +21,7 @@ public class ModulePanel extends ExtendablePanel { public final Category category; public ModulePanel(Category category, int x, int y, int w, int h) { - super(category.getDisplayName(), x, y, w, h); + super(category::getDisplayName, x, y, w, h); this.category = category; dragging = false; @@ -35,7 +35,7 @@ public class ModulePanel extends ExtendablePanel { @Override protected void updateWidth() { - int maxWidth = mc.font.width(title); + int maxWidth = mc.font.width(getTitle()); for (ModuleButton button : buttons) { int textWidth = mc.font.width(button.getModule().getDisplayName()); maxWidth = Math.max(maxWidth, textWidth); @@ -48,7 +48,7 @@ public class ModulePanel extends ExtendablePanel { updateWidth(); gui.fill(x, renderY, x + w, renderY + h, Colors.mainColor.getRGB()); - gui.text(mc.font, Component.literal(title), + gui.text(mc.font, Component.literal(getTitle()), x + 8, renderY + (h / 2) - (mc.font.lineHeight / 2), -1, true); diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/SettingsPanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/SettingsPanel.java index ed62c4b..aedb200 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/SettingsPanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/SettingsPanel.java @@ -27,7 +27,8 @@ public class SettingsPanel extends CloseablePanel I18nUtil.orElse("sputnik.ui.settingsof", + "ajustes de " + module.getDisplayName(), module.getDisplayName()), x, y, w, h); this.module = module; this.settingGroups = module.getSgs(); @@ -59,7 +60,7 @@ public class SettingsPanel extends CloseablePanel descLines = wrapDescription(module.getDescription(), w - 2 * PADDING); + List descLines = wrapDescription(module.getDisplayDescription(), w - 2 * PADDING); int lineSpacing = 2; int descHeight = descLines.size() * mc.font.lineHeight + (descLines.size() - 1) * lineSpacing; @@ -212,7 +213,7 @@ public class SettingsPanel extends CloseablePanel button : buttons) { String text = button.getSetting().getName(); @@ -246,7 +247,7 @@ public class SettingsPanel extends CloseablePanel descLines = wrapDescription(module.getDescription(), w - 2 * PADDING); + List descLines = wrapDescription(module.getDisplayDescription(), w - 2 * PADDING); int descHeight = descLines.size() * mc.font.lineHeight + (descLines.size() - 1) * 2; currentY += descHeight + 3 * PADDING; // descBoxBottom + padding diff --git a/src/main/java/me/retucio/sputnik/ui/widgets/panels/settings/ClientSettingsPanel.java b/src/main/java/me/retucio/sputnik/ui/widgets/panels/settings/ClientSettingsPanel.java index 70f1b22..4143d39 100644 --- a/src/main/java/me/retucio/sputnik/ui/widgets/panels/settings/ClientSettingsPanel.java +++ b/src/main/java/me/retucio/sputnik/ui/widgets/panels/settings/ClientSettingsPanel.java @@ -6,6 +6,7 @@ import me.retucio.sputnik.module.modules.client.ClientSettingsModule; import me.retucio.sputnik.ui.screen.ClickGui; import me.retucio.sputnik.ui.widgets.panels.SettingsPanel; import me.retucio.sputnik.util.Colors; +import me.retucio.sputnik.util.I18nUtil; import net.minecraft.client.gui.GuiGraphicsExtractor; public class ClientSettingsPanel extends SettingsPanel { @@ -15,7 +16,7 @@ public class ClientSettingsPanel extends SettingsPanel { public ClientSettingsPanel(int x, int y, int w, int h) { super(clientSettings, x, y, w, h); - this.title = "ajustes del mod"; + setTitle(() -> I18nUtil.orElse("sputnik.ui.clientsettings.title", "ajustes del mod")); this.drawHeader = false; // disable the original header } @@ -25,7 +26,7 @@ public class ClientSettingsPanel extends SettingsPanel { // Draw our own custom header (no close button, with +/- gui.fill(x, renderY, x + w, renderY + h, Colors.mainColor.getRGB()); - gui.text(mc.font, title, + gui.text(mc.font, getTitle(), x + 8, renderY + (h / 2) - (mc.font.lineHeight / 2), -1, true); diff --git a/src/main/java/me/retucio/sputnik/util/I18nUtil.java b/src/main/java/me/retucio/sputnik/util/I18nUtil.java index 90f653d..77a0138 100644 --- a/src/main/java/me/retucio/sputnik/util/I18nUtil.java +++ b/src/main/java/me/retucio/sputnik/util/I18nUtil.java @@ -47,6 +47,21 @@ public class I18nUtil { return Language.getInstance().has(key) ? I18n.get(key, args) : fallback; } + // igual que orElse pero rellenando también los %s del texto de reserva. + // para lo que se registra durante el arranque (cargar la config., comprobar la versión...): + // ahí el idioma todavía no está cargado y Component#translatable escupía la clave pelada + public static String fmt(String key, String fallback, Object... args) { + if (Language.getInstance().has(key)) return I18n.get(key, args); + return args.length == 0 ? fallback : String.format(fallback, args); + } + + // texto que daba el en_us de vanilla para una clave. sólo para leer configs. viejas: + // las listas guardaban el nombre ya traducido y Language, durante el arranque, es + // siempre el en_us de vanilla, así que lo guardado eran los nombres en inglés + public static String vanillaName(String key) { + return Language.DEFAULT_INSTANCE.getOrDefault(key, key); + } + // nombre visible de una constante de enum. los EnumSetting se guardan por índice // (ver ConfigManager#applyEnumSetting), así que su toString() es sólo texto y se traduce. // la clave se deriva de la clase que la declara, así que dos enums con el mismo nombre diff --git a/src/main/java/me/retucio/sputnik/util/Lists.java b/src/main/java/me/retucio/sputnik/util/Lists.java index fc96809..ea150c4 100644 --- a/src/main/java/me/retucio/sputnik/util/Lists.java +++ b/src/main/java/me/retucio/sputnik/util/Lists.java @@ -3,7 +3,6 @@ package me.retucio.sputnik.util; import net.minecraft.core.particles.ParticleType; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.EntityType; @@ -40,38 +39,36 @@ public class Lists { public static final List colorList = new ArrayList<>(); public static final List fontList = new ArrayList<>(); - // intentar traducir nombres y fallar miserablemente + // 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(entity -> Component.translatable( - entity.getDescriptionId() - ).getString()).toList()); + entityList.stream().map(EntityType::getDescriptionId).toList()); particleNames = getMapOfLists(particleList, - particleList.stream().map(particle -> Component.translatable( + particleList.stream().map(particle -> BuiltInRegistries.PARTICLE_TYPE.getKey(particle).toLanguageKey() - ).getString()).toList()); + ).toList()); itemNames = getMapOfLists(itemList, - itemList.stream().map(item -> Component.translatable( - item.getDescriptionId() - ).getString()).toList()); + itemList.stream().map(Item::getDescriptionId).toList()); blockNames = getMapOfLists(blockList, - blockList.stream().map(block -> Component.translatable( - block.getDescriptionId() - ).getString()).toList()); + blockList.stream().map(Block::getDescriptionId).toList()); soundNames = getMapOfLists(soundList, - soundList.stream().map(sound -> Component.translatable( + soundList.stream().map(sound -> sound.location().toShortLanguageKey() - ).getString()).toList()); + ).toList()); soundNames.replace(SoundEvents.EMPTY, "NINGUNO"); screenNames = getMapOfLists(screenList, - screenList.stream().map(screen -> Component.translatable( + screenList.stream().map(screen -> BuiltInRegistries.MENU.getKey(screen).toLanguageKey() - ).getString()).toList()); + ).toList()); colorList.addAll(Arrays.asList( RED, ORANGE, YELLOW, LIME, GREEN, CYAN, diff --git a/src/main/java/me/retucio/sputnik/util/render/Pipelines.java b/src/main/java/me/retucio/sputnik/util/render/Pipelines.java index 25d82b0..2d6acf5 100644 --- a/src/main/java/me/retucio/sputnik/util/render/Pipelines.java +++ b/src/main/java/me/retucio/sputnik/util/render/Pipelines.java @@ -1,7 +1,9 @@ package me.retucio.sputnik.util.render; import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.pipeline.DepthStencilState; import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.platform.CompareOp; import com.mojang.blaze3d.systems.GpuDevice; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.DefaultVertexFormat; @@ -28,12 +30,15 @@ public class Pipelines { static List PIPELINES = new ArrayList<>(); + // 26.2 usa depth invertido (near = 1), así que el LEQUAL de siempre es GREATER_THAN_OR_EQUAL + private static final DepthStencilState NO_DEPTH = new DepthStencilState(CompareOp.ALWAYS_PASS, false); + private static final DepthStencilState DEPTH = new DepthStencilState(CompareOp.GREATER_THAN_OR_EQUAL, false); + static final RenderPipeline LINES_PIPELINE = add(RenderPipeline.builder(RenderPipelines.LINES_SNIPPET) .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/lines")) .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH) .withPrimitiveTopology(PrimitiveTopology.LINES) -// .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) -// .withDepthWrite(false) + .withDepthStencilState(NO_DEPTH) .withCull(false) .build()); @@ -41,8 +46,7 @@ public class Pipelines { .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/lines_cull")) .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH) .withPrimitiveTopology(PrimitiveTopology.LINES) -// .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST) -// .withDepthWrite(false) + .withDepthStencilState(DEPTH) .withCull(true) .build()); @@ -50,9 +54,7 @@ public class Pipelines { .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/quads")) .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) .withPrimitiveTopology(PrimitiveTopology.QUADS) -// .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) -// .withBlend(BlendFunction.TRANSLUCENT) -// .withDepthWrite(false) + .withDepthStencilState(NO_DEPTH) .withCull(false) .build()); @@ -60,9 +62,7 @@ public class Pipelines { .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/quads_cull")) .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) .withPrimitiveTopology(PrimitiveTopology.QUADS) -// .withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST) -// .withBlend(BlendFunction.TRANSLUCENT) -// .withDepthWrite(false) + .withDepthStencilState(DEPTH) .withCull(false) .build()); @@ -76,9 +76,7 @@ public class Pipelines { // siguen usando uniforms con nombre (u_Texture, u_ProjectionMatrix) y seguramente // haya que reescribirlos para que casen con las convenciones nuevas .withBindGroupLayout(BindGroupLayouts.SAMPLER0) -// .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) -// .withDepthWrite(false) -// .withBlend(BlendFunction.TRANSLUCENT) + .withDepthStencilState(NO_DEPTH) .withCull(false) .build()); diff --git a/src/main/java/me/retucio/sputnik/util/render/SputnikBuffers.java b/src/main/java/me/retucio/sputnik/util/render/SputnikBuffers.java index ce9c2ce..ad4ce79 100644 --- a/src/main/java/me/retucio/sputnik/util/render/SputnikBuffers.java +++ b/src/main/java/me/retucio/sputnik/util/render/SputnikBuffers.java @@ -5,24 +5,32 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexFormat; import me.retucio.sputnik.Sputnik; import net.minecraft.client.renderer.StagedVertexBuffer; +import net.minecraft.client.renderer.rendertype.PreparedRenderType; import net.minecraft.client.renderer.rendertype.RenderType; +import java.util.ArrayList; +import java.util.List; + /** emula el antiguo Tesselator + RenderType.draw(MeshData), que 26.2 se cargó *

- * ahora los vértices se escriben en un StagedVertexBuffer: se pide un draw, se - * escriben los vértices, se sube todo a la gpu y se dibuja. el ciclo es - * appendDraw -> upload -> drawFromBuffer -> endDraw; upload() peta si se llama - * dos veces sin endDraw() de por medio. + * el ciclo del StagedVertexBuffer es appendDraw -> upload -> drawFromBuffer -> endDraw, + * y hay que respetarlo entero: los vértices no llegan al Draw hasta que upload() cierra + * el BufferBuilder, y appendDraw() peta después de un upload(). de ahí que draw() sólo + * encole y sea flush() quien suba y dibuje todo de una. *

- * subimos y dibujamos en cada llamada (en vez de agrupar draws) para mantener el - * mismo orden de dibujado que antes; es menos eficiente pero se comporta igual. + * un upload() por caja tampoco vale: cada uno se pilla un buffer de 256 KB del pool que + * no vuelve hasta el final del frame. */ public class SputnikBuffers { private static StagedVertexBuffer buffer; private static StagedVertexBuffer.Draw current; + private static final List queue = new ArrayList<>(); + + private record QueuedDraw(PreparedRenderType type, StagedVertexBuffer.Draw draw) {} + // lazy: al cargar la clase todavía puede no haber device private static StagedVertexBuffer buffer() { if (buffer == null) { @@ -32,8 +40,6 @@ public class SputnikBuffers { } public static VertexConsumer begin(VertexFormat format, PrimitiveTopology topology) { - if (current != null) buffer().endDraw(); // por si quedó un draw a medias - current = buffer().appendDraw(format, topology); return buffer().getVertexBuilder(current); } @@ -41,20 +47,33 @@ public class SputnikBuffers { public static void draw(RenderType type) { if (current == null) return; - if (!current.isEmpty()) { - buffer().upload(); - type.prepare().drawFromBuffer(buffer().getExecuteInfo(current)); + queue.add(new QueuedDraw(type.prepare(), current)); + current = null; + } + + /** sube y dibuja lo encolado; hay que llamarlo antes de que el juego pinte el hud */ + public static void flush() { + if (buffer == null) return; + + current = null; + if (queue.isEmpty()) return; + + buffer.upload(); + + for (QueuedDraw queued : queue) { + StagedVertexBuffer.ExecuteInfo info = buffer.getExecuteInfo(queued.draw()); + if (info != null) queued.type().drawFromBuffer(info); } - buffer().endDraw(); - current = null; + queue.clear(); + buffer.endDraw(); } // devuelve los buffers al pool; una vez por frame public static void endFrame() { if (buffer == null) return; - current = null; + flush(); buffer.endFrame(); } } diff --git a/src/main/resources/assets/sputnik/lang/en_us.json b/src/main/resources/assets/sputnik/lang/en_us.json index f74c38f..1b6895d 100644 --- a/src/main/resources/assets/sputnik/lang/en_us.json +++ b/src/main/resources/assets/sputnik/lang/en_us.json @@ -55,6 +55,7 @@ "sputnik.commands.wait.desc": "waits a given amount of time before running another command", "sputnik.config.autosaving.desc": "saving settings...", "sputnik.config.autosaving.title": "autosaving...", + "sputnik.config.error.applying": "error applying settings", "sputnik.config.error.empty": "config file empty or nonexistent", "sputnik.config.error.invalidformat": "invalid file format: %s", "sputnik.config.error.invalidversion": "invalid version: %s", @@ -105,6 +106,8 @@ "sputnik.enum.module.modules.client.hud.dynosaurs.velocirraptor": "velociraptor", "sputnik.enum.module.modules.client.hud.timeformat.twelve_hour": "12h", "sputnik.enum.module.modules.client.hud.timeformat.twenty_four_hour": "24h", + "sputnik.enum.module.modules.combat.holdattack.attackmode.spam": "spam", + "sputnik.enum.module.modules.combat.holdattack.attackmode.time": "time", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.dashed": "dashed", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.dotted": "points", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.solid": "solid", @@ -327,6 +330,8 @@ "sputnik.module.auradecristales.name": "crystal aura", "sputnik.module.autismo.desc": "hehe", "sputnik.module.autismo.name": "autism", + "sputnik.module.autoataque.desc": "hold the attack button down to attack automatically", + "sputnik.module.autoataque.name": "autoattack", "sputnik.module.autocartel.desc": "lets you place signs with the same text without typing it out one by one", "sputnik.module.autocartel.name": "auto sign", "sputnik.module.autofish.rodbreaking": "the rod is about to break", @@ -646,6 +651,14 @@ "sputnik.setting.autismo.general.direccion.name": "direction", "sputnik.setting.autismo.general.intervalo.desc": "rotation interval", "sputnik.setting.autismo.general.intervalo.name": "interval", + "sputnik.setting.autoataque.general.atacarenpantallas.desc": "attack even while in a screen", + "sputnik.setting.autoataque.general.atacarenpantallas.name": "attack in screens", + "sputnik.setting.autoataque.general.cooldown.desc": "how full the attack bar has to be before attacking", + "sputnik.setting.autoataque.general.cooldown.name": "cooldown", + "sputnik.setting.autoataque.general.cooldown1.desc": "how often to attack, in ticks", + "sputnik.setting.autoataque.general.cooldown1.name": "cooldown", + "sputnik.setting.autoataque.general.mododeataque.desc": "how to attack", + "sputnik.setting.autoataque.general.mododeataque.name": "attack mode", "sputnik.setting.autoherramienta.general.cambiardevuelta.desc": "switch back to the original tool", "sputnik.setting.autoherramienta.general.cambiardevuelta.name": "switch back", "sputnik.setting.autoherramienta.general.delay.desc": "delay when switching tools", @@ -1434,8 +1447,10 @@ "sputnik.setting.zoom.visual.mostrarmanos.name": "show hands", "sputnik.ui.availablemodes": "available modes:", "sputnik.ui.clickgui.title": "gui", + "sputnik.ui.clientsettings.title": "mod settings", "sputnik.ui.friend.manage": "manage %s", "sputnik.ui.friend.remove": "remove friend", + "sputnik.ui.friends.title": "friends", "sputnik.ui.list.tooltip": "%s (%s of %s)", "sputnik.ui.preview.echest": "echest", "sputnik.ui.preview.shulker": "shulker preview", diff --git a/src/main/resources/assets/sputnik/lang/es_es.json b/src/main/resources/assets/sputnik/lang/es_es.json index 3871f41..2edd4b7 100644 --- a/src/main/resources/assets/sputnik/lang/es_es.json +++ b/src/main/resources/assets/sputnik/lang/es_es.json @@ -55,6 +55,7 @@ "sputnik.commands.wait.desc": "espera un tiempo determinado antes de ejecutar otro comando", "sputnik.config.autosaving.desc": "guardando configuración...", "sputnik.config.autosaving.title": "autoguardando...", + "sputnik.config.error.applying": "error aplicando ajustes", "sputnik.config.error.empty": "archivo de config. vacío o inexistente", "sputnik.config.error.invalidformat": "formato de archivo inválido: %s", "sputnik.config.error.invalidversion": "versión inválida: %s", @@ -105,6 +106,8 @@ "sputnik.enum.module.modules.client.hud.dynosaurs.velocirraptor": "velocirráptor", "sputnik.enum.module.modules.client.hud.timeformat.twelve_hour": "12h", "sputnik.enum.module.modules.client.hud.timeformat.twenty_four_hour": "24h", + "sputnik.enum.module.modules.combat.holdattack.attackmode.spam": "spamear", + "sputnik.enum.module.modules.combat.holdattack.attackmode.time": "timear", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.dashed": "discontinua", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.dotted": "puntos", "sputnik.enum.module.modules.combat.projectiletrajectories.trajectorystyle.solid": "continua", @@ -327,6 +330,8 @@ "sputnik.module.auradecristales.name": "aura de cristales", "sputnik.module.autismo.desc": "jeje", "sputnik.module.autismo.name": "autismo", + "sputnik.module.autoataque.desc": "mantén pulsado el clic para atacar automáticamente", + "sputnik.module.autoataque.name": "autoataque", "sputnik.module.autocartel.desc": "te permite colocar carteles con el mismo texto sin tener que escribirlo uno por uno", "sputnik.module.autocartel.name": "autocartel", "sputnik.module.autofish.rodbreaking": "caña a punto de romperse", @@ -646,6 +651,14 @@ "sputnik.setting.autismo.general.direccion.name": "dirección", "sputnik.setting.autismo.general.intervalo.desc": "intervalo de rotación", "sputnik.setting.autismo.general.intervalo.name": "intervalo", + "sputnik.setting.autoataque.general.atacarenpantallas.desc": "atacar aun estando en una pantalla", + "sputnik.setting.autoataque.general.atacarenpantallas.name": "atacar en pantallas", + "sputnik.setting.autoataque.general.cooldown.desc": "cómo de llena tiene que estar la barra de ataque antes de atacar", + "sputnik.setting.autoataque.general.cooldown.name": "cooldown", + "sputnik.setting.autoataque.general.cooldown1.desc": "cada cuánto atacar, en ticks", + "sputnik.setting.autoataque.general.cooldown1.name": "cooldown", + "sputnik.setting.autoataque.general.mododeataque.desc": "cómo atacar", + "sputnik.setting.autoataque.general.mododeataque.name": "modo de ataque", "sputnik.setting.autoherramienta.general.cambiardevuelta.desc": "cambiar de vuelta a la herramienta inicial", "sputnik.setting.autoherramienta.general.cambiardevuelta.name": "cambiar de vuelta", "sputnik.setting.autoherramienta.general.delay.desc": "delay al cambiar de herramienta", @@ -1434,8 +1447,10 @@ "sputnik.setting.zoom.visual.mostrarmanos.name": "mostrar manos", "sputnik.ui.availablemodes": "modos disponibles:", "sputnik.ui.clickgui.title": "interfaz", + "sputnik.ui.clientsettings.title": "ajustes del mod", "sputnik.ui.friend.manage": "gestiona a %s", "sputnik.ui.friend.remove": "eliminar amigo", + "sputnik.ui.friends.title": "amigos", "sputnik.ui.list.tooltip": "%s (%s de %s)", "sputnik.ui.preview.echest": "echest", "sputnik.ui.preview.shulker": "previsualización del shulker",