This commit is contained in:
retucio committed 2026-08-18 13:35:24 +02:00
1 parent 81fc25c33d
commit e0f8523809
29 files changed
+502 -181

No files matched your search

+14 -3
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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;
@@ -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<String, Boolean> moduleStates = readStringBooleanMap(in);
Map<String, Object> settings = readSettingsMap(in);
Map<String, int[]> settingsPanels = readStringIntArrayMap(in);
Map<String, ClientConfig.PanelData> extendablePanels = readExtendablePanels(in);
Map<String, int[]> hudPositions = readStringIntArrayMap(in);
Map<String, Boolean> hudVisibilities = readStringBooleanMap(in);
Map<String, String> hudImagePaths = readStringStringMap(in);
Map<String, String> 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;
}
@@ -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;
}
@@ -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<byte[]> 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<T, Boolean> 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;
}
}
@@ -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();
@@ -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);
@@ -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());
@@ -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;
@@ -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<AttackMode> 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; }
}
}
@@ -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<SimpleParticleType, String> 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<SimpleParticleType> particles = sgGeneral.add(new ListSetting<>(
@@ -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<Integer> {
// 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) {
@@ -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<T> extends Setting<Map<T, Boolean>> {
public Map<String, Boolean> getConfigValues() {
Map<String, Boolean> 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<T> extends Setting<Map<T, Boolean>> {
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);
}
}
@@ -57,6 +57,10 @@ public class NumberSetting extends Setting<Double> {
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);
@@ -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<T> extends Setting<T> {
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<T> extends Setting<T> {
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() {
@@ -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<B extends Button> 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<String> title;
protected final List<B> buttons = new ArrayList<>();
protected List<B> visibleButtons = new ArrayList<>();
@@ -20,10 +25,22 @@ public abstract class Panel<B extends Button> 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<String> 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<String> title) {
this.title = title;
}
public void drawTooltips(GuiGraphicsExtractor gui, int mouseX, int mouseY) {
for (Button button : visibleButtons) button.drawTooltip(gui, mouseX, mouseY);
}
@@ -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<B extends Button> extends Panel<B> {
protected static final int PADDING = 4;
public CloseablePanel(String title, int x, int y, int w, int h) {
public CloseablePanel(Supplier<String> title, int x, int y, int w, int h) {
super(title, x, y, w, h);
}
@@ -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<B extends Button> extends Panel<B> {
protected boolean extended;
public ExtendablePanel(String title, int x, int y, int w, int h) {
public ExtendablePanel(Supplier<String> title, int x, int y, int w, int h) {
super(title, x, y, w, h);
this.extended = true;
}
@@ -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<FriendButton> {
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<FriendButton> {
@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<FriendButton> {
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);
@@ -21,7 +21,7 @@ public class ModulePanel extends ExtendablePanel<ModuleButton> {
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<ModuleButton> {
@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<ModuleButton> {
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);
@@ -27,7 +27,8 @@ public class SettingsPanel extends CloseablePanel<SettingButton<? extends Settin
public boolean drawHeader = true;
public SettingsPanel(Module module, int x, int y, int w, int h) {
super(I18nUtil.orElse("sputnik.ui.settingsof", "ajustes de " + module.getDisplayName(), module.getDisplayName()), x, y, w, h);
super(() -> 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<SettingButton<? extends Settin
: -1;
gui.fill(x, renderY, x + w, renderY + h, headerColor);
gui.text(mc.font, title, titleX, titleY, -1, true);
gui.text(mc.font, getTitle(), titleX, titleY, -1, true);
drawCloseButton(gui, mouseX, mouseY, closeButtonColor);
}
@@ -69,7 +70,7 @@ public class SettingsPanel extends CloseablePanel<SettingButton<? extends Settin
.toList();
// descripción
List<String> descLines = wrapDescription(module.getDescription(), w - 2 * PADDING);
List<String> 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<SettingButton<? extends Settin
@Override
public void updateWidth() {
// asegurarse de que todos los botones caben en el marco, haciendo que la anchura se ajuste al texto más largo
int maxWidth = mc.font.width(title);
int maxWidth = mc.font.width(getTitle());
for (SettingButton<?> button : buttons) {
String text = button.getSetting().getName();
@@ -246,7 +247,7 @@ public class SettingsPanel extends CloseablePanel<SettingButton<? extends Settin
public int getGroupHeaderY(int groupIndex) {
int currentY = renderY + h + PADDING;
List<String> descLines = wrapDescription(module.getDescription(), w - 2 * PADDING);
List<String> descLines = wrapDescription(module.getDisplayDescription(), w - 2 * PADDING);
int descHeight = descLines.size() * mc.font.lineHeight + (descLines.size() - 1) * 2;
currentY += descHeight + 3 * PADDING; // descBoxBottom + padding
@@ -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);
@@ -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
@@ -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<Color> colorList = new ArrayList<>();
public static final List<String> 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,
@@ -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<RenderPipeline> 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());
@@ -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ó
* <p>
* 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.
* <p>
* 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<QueuedDraw> 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();
}
}
@@ -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",
@@ -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",