Compare commits

...
15 Commits
Author SHA1 Message Date
retucio ab191a9016 v6.6 2026-10-11 11:56:22 +02:00
retucio 629d03ff92 v6.5 2026-09-14 19:17:07 +02:00
retucio c1a03f3ddb v6.4 2026-09-06 11:55:58 +02:00
retucio 44494830be v6.3 2026-09-04 12:58:41 +02:00
retucio 73682cb85d v6.2 2026-08-28 12:27:07 +02:00
retucio ac7de7a07a v6.1 2026-08-27 01:00:19 +02:00
retucio 0bd31971a0 v6.0 2026-08-23 16:50:27 +02:00
retucio 3010b164ab v5.9 2026-08-23 16:03:32 +02:00
retucio 6cb4656365 v5.8 2026-08-22 18:20:52 +02:00
retucio 272c1059ef v5.7 2026-08-19 22:57:41 +02:00
retucio e0f8523809 v5.6 2026-08-18 13:35:24 +02:00
retucio 81fc25c33d v5.5 2026-07-20 22:17:44 +02:00
retucio 81b043270b v5.4.1 2026-07-19 23:20:48 +02:00
retucio 3913b31374 v5.4 2026-07-19 23:14:33 +02:00
retucio aeadfb28b1 v5.3 2026-07-15 21:50:51 +02:00
269 changed files with 11906 additions and 2088 deletions

No files matched your search

+2 -1
View File
@@ -43,4 +43,5 @@ replay_*.log
todo.txt
cambios.md
remappedSrc/
remappedSrc/
.claude/
+82
View File
@@ -0,0 +1,82 @@
<p align="center">
<a href="README.md"><img src="https://img.shields.io/badge/lang-Español-yellow.svg" alt="español"></a>
<a href="README.en.md"><img src="https://img.shields.io/badge/lang-English-blue.svg" alt="english"></a>
</p>
# jenny's mod 2 (100% real)
> Minecraft Fabric 26.3 mod that adds some "QOL" features, mostly related to the camera.
## installation
### for normal people:
1. go to [releases](https://codeberg.org/retucio/sputnik/releases)
2. choose the most recent version
3. download the .jar file
4. move it to your mods dir (prolly `%appdata%/.minecraft/mods`)
make sure to have a Minecraft and Fabric version compatible with the mod (MC `26.3`; Fabric `0.19.5`)
issues could arise when using it with other mods, especially other clients, since they could conflict
### for nerds
1. clone the repo (`git clone https://codeberg.org/retucio/sputnik`)
2. enter the repo dir (`cd sputnik`)
3. `gradlew build` for Windows (ew) or `chmod +x gradlew` + `./gradlew build` for Unix-likes (Linux & Mac)
4. the built .jar will be found in `/build/libs/sputnik-X.X.jar`
## usage
`rshift` by default to open the GUI
`insert` by default to open the HUD editor
`$` as prefix for commands (autocompleted)
some modules have keys assigned by default, assign them `ESC` to unbind them.
## ui
- left click to toggle modules
- right click to open its settings
- left click + drag to move a panel
- right click on a panel's header to expand/contract it, or to close it
- left click over settings to change them
- shift + right click on a setting to reset it to its default value
- place the mouse cursor over a module/setting to read its description
## screenshots
<img src="screenshots/automine.png" alt="auto mine">
<img src="screenshots/blockesp.png" alt="block ESP + light overlay">
<img src="screenshots/camtweaks.png" alt="perspective plus">
<img src="screenshots/chatplus.png" alt="chatplus + nametags + screenshots plus">
<img src="screenshots/clickgui.png" alt="click GUI">
<img src="screenshots/freecam.png" alt="freecam + tracers">
<img src="screenshots/hudeditor.png" alt="HUD editor">
<img src="screenshots/itemphysics.png" alt="item physics">
<img src="screenshots/lsd.png" alt="lsd module">
<img src="screenshots/macros.png" alt="macros tab">
<img src="screenshots/trajectories.png" alt="projectile trajectories">
## contribution
yes please (PRs are welcome)
## license
[CC0 license](LICENSE)
## disclaimer
i don't give a single fuck what happens to you when using this mod on servers where its features are bannable and such.
## deps
- [fabric api](https://modrinth.com/mod/fabric-api/)
## notes
this mod IS BUGGY. VERY BUGGY, i know. i started this project when i was 14 and the codebase is such a mess i refuse to fix it. i might rewrite it in a future, though.
+30 -38
View File
@@ -1,23 +1,28 @@
<p align="center">
<a href="README.md"><img src="https://img.shields.io/badge/lang-Español-yellow.svg" alt="español"></a>
<a href="README.en.md"><img src="https://img.shields.io/badge/lang-English-blue.svg" alt="english"></a>
</p>
# jenny's mod 2 100% real
mod para Minecraft Fabric 26.1.2 que añade algunas cosas QOL, mayormente relacionadas a la cámara.
> mod para Minecraft Fabric 26.3 que añade algunas cosas "QOL", mayormente relacionadas a la cámara.
## instalación
### para gente normal:
1. ir a [lanzamientos](https://github.com/retuci0/sputnik/releases)
2. selecionar la versión más reciente
1. ir a [lanzamientos](https://codeberg.org/retucio/sputnik/releases)
2. seleccionar la versión más reciente
3. descargar el .jar
4. moverlo a tu directorio de mods (probablemente `%appdata%/.minecraft/mods`)
asegúrate de tener una version de Minecraft y Fabric compatibles con el mod (MC `26.1.2`; Fabric `0.19.2`)
asegúrate de tener una version de Minecraft y Fabric compatibles con el mod (MC `26.3`; Fabric `0.19.5`)
es posible que haya problemas al usarlo junto a otros mods, especialmente junto a otros clientes, pues pueden conflictar entre ellos.
### para frikis de 200 de iq
1. clona el respositorio (`git clone https://github.com/retuci0/sputnik`)
1. clona el respositorio (`git clone https://codeberg.org/retucio/sputnik`)
2. entra al directorio del repositorio (`cd sputnik`)
3. `gradlew build` para Windows o `chmod +x gradlew` + `./gradlew build` para Linux (Mac es Linux)
4. el resultado estará en `/build/libs/sputnik-X.X.jar` (el que no lleva "sources" en el nombre)
3. `gradlew build` para Windows (ew) o `chmod +x gradlew` + `./gradlew build` para Unix-likes (Linux & Mac)
4. el resultado estará en `/build/libs/sputnik-X.X.jar`
## uso
@@ -33,55 +38,42 @@ algunos módulos tienen teclas asignadas por defecto, asígnales `ESC` para desa
- clic izquierdo para activar / desactivar módulos
- clic derecho para abrir sus ajustes
- clic izquierdo + arrastrar para mover marcos
- clic izquierdo + arrastrar para mover paneles
- clic derecho en la cabeza del marco para expandirlo / contraerlo o para cerrarlo
- clic izquierdo sobre ajustes para modificarlos
- shift + clic derecho sobre ajustes para restablecerlos
- posiciona el puntero del ratón sobre un módulo o ajuste para leer su descripción
### protips
- shift mientras pasas el puntero por encima de un ajuste de modos para ver los modos disponibles
- clic derecho para ciclar hacia atrás en ajustes de modo
- usa la rueda del ratón para mover la interfaz verticalmente
- shift + rueda del ratón para mover todos los marcos a un rango visible en la pantalla
## capturas
<img src="screenshots/ui.png" alt="interfaz">
<img src="screenshots/nametags.png" alt="nametags mostrando daño de un tridente">
<img src="screenshots/chatplus.png" alt="chatplus y capturas en uso">
<img src="screenshots/crits.png" alt="críticos">
<img src="screenshots/fullbright.png" alt="fullbright en modo gamma cambiando el color de los shaders">
<img src="screenshots/hud.png" alt="HUD">
<img src="screenshots/automine.png" alt="auto minado">
<img src="screenshots/blockesp.png" alt="resaltado de bloques + superposición de luz">
<img src="screenshots/camtweaks.png" alt="perspectiva plus">
<img src="screenshots/chatplus.png" alt="chatplus + nametags + capturas">
<img src="screenshots/clickgui.png" alt="interfaz">
<img src="screenshots/freecam.png" alt="cámara libre + trazos">
<img src="screenshots/hudeditor.png" alt="editor de HUD">
<img src="screenshots/colorpicker.png" alt="selector de colores">
<img src="screenshots/itemphysics.png" alt="físicas de items">
<img src="screenshots/lsd.png" alt="LSD">
<img src="screenshots/macros.png" alt="macros">
<img src="screenshots/trajectories.png" alt="trayectorias">
## contibuciones
## contribuciones
sí por favor
## licencia
no sé, haz lo que quieras con esto, es puta basura xd
aunque estaría guay que acreditases
pero bueno
[licencia CC0](LICENSE)
## descargo de responsabilidad
me importa una puta mierda lo que pase al usar este mod en servers donde es baneable y así.
## mods recomendados
## dependencias
> mods que recomiendo usar junto a este:
- [fabric api](https://modrinth.com/mod/fabric-api/)
- [fabric api (necesario)](https://modrinth.com/mod/fabric-api/): necesario para el mod
- [sodium](https://modrinth.com/mod/sodium): mejora el rendimiento general del juego
- [viafabricplus](https://modrinth.com/mod/viafabricplus): te permite conectarte a servidores que tengan una versión distinta a la de tu cliente
- [xaero's world map](https://modrinth.com/mod/xaeros-world-map/) y [xaero's minimap](https://modrinth.com/mod/xaeros-minimap/): mapa chulo
- [mod menu](https://modrinth.com/mod/modmenu): lista de los mods instalados, y sus ajustes
- [iris](https://modrinth.com/mod/iris): shaders
- [lambdynamiclights](https://modrinth.com/mod/lambdynamiclights): iluminación dinámica
- [appleskin](https://modrinth.com/mod/appleskin/): información relacionada con la comida
- [baritone (desactualizado)](https://github.com/cabaletta/baritone): google maps para juego de cubos
## notas
este mod tiene MUCHOS bugs. empecé este proyecto cuando tenía 14 años y no sabía una mierda de programar. el código es un puto desastre y me niego a arreglarlo. puede que en un futuro reescriba el mod o algo. no sé.
+9 -7
View File
@@ -1,18 +1,20 @@
# v5.2
# v6.6
## novedades
- actualizado a 26.3, un verdadero grano en el culo
### adiciones
#### módulos nuevos:
- pestaña de ajustes del mod
- macros de tecla
### arreglos / mejoras
- `andamios` ya funciona correctamente
- autoguardado cada cierto intervalo de tiempo en vez de guardar inmediatamente tras cada cambio en los ajustes, por optimización
- el guardado se realiza desde otro hilo ahora, para evitar que el I/O bloque el hilo principal
- algún que otro bugfix
## problemas reconocidos
- lo de siempre.
- el ítem del tótem no se renderiza en el editor del HUD si es abierto en la pantalla de inicio
- `reponer` no funciona
- `copiar` tras sacar una captura con `capturas` no funciona correctamente
- \+ lo de siempre.
+3 -1
View File
@@ -10,6 +10,8 @@ elytra-recast: https://github.com/InLieuOfLuna/elytra-recast/
SpearHax: https://github.com/etianl/spearHax/
ProjectileTrajectoryPreview: https://github.com/maDU59/ProjectilesTrajectoryPreview
DupersUnited public addon: https://discord.gg/dupes | https://dupers.wtf
Stardust: https://github.com/0xTas/stardust/
probablemente más pero como tengo demencia se me olvida
si quieres que te acredite o quite tu código de mi mod, mándame un mensaje en discord (username: retucio) y sin problemas :)
si quieres que te acredite o quite tu código de mi mod, mándame un mensaje en discord (username: retucio) y sin problemas :)
también es probable que haya créditos sueltos en forma de comentarios donde haya usado código ajeno
+6 -6
View File
@@ -1,15 +1,15 @@
org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true
minecraft_version=26.1.2
loader_version=0.19.2
loom_version=1.16-SNAPSHOT
minecraft_version=26.3
loader_version=0.19.5
loom_version=1.18-SNAPSHOT
fabric_api_version=0.146.0+26.1.2
fabric_api_version=0.160.7+26.3
discord_rpc_version=1.0.3
neutrino_version=1.1.0
jdk_version=25
mod_version=5.2
mod_version=6.6
maven_group=me.retucio
archives_base_name=sputnik
archives_base_name=sputnik
+2 -3
View File
@@ -1,7 +1,6 @@
#Sat Aug 29 22:10:52 CEST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 700 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 KiB

+97 -41
View File
@@ -2,11 +2,14 @@ package me.retucio.sputnik;
import com.github.retucio.neutrino.EventBus;
import com.github.retucio.neutrino.EventListener;
import com.mojang.blaze3d.platform.InputConstants;
import me.retucio.sputnik.cape.CapeManager;
import me.retucio.sputnik.command.CommandManager;
import me.retucio.sputnik.command.commands.BindCommand;
import me.retucio.sputnik.config.ConfigManager;
import me.retucio.sputnik.config.ProfileManager;
import me.retucio.sputnik.macro.MacroManager;
import me.retucio.sputnik.event.ShutdownEvent;
import me.retucio.sputnik.event.interact.OpenScreenEvent;
@@ -17,12 +20,14 @@ import me.retucio.sputnik.module.Module;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.client.Hud;
import me.retucio.sputnik.ui.hud.HudEditor;
import me.retucio.sputnik.ui.hud.HudRenderer;
import me.retucio.sputnik.ui.screen.ClickGui;
import me.retucio.sputnik.ui.hud.HudEditorScreen;
import me.retucio.sputnik.ui.screen.UpdateScreen;
import me.retucio.sputnik.ui.widgets.buttons.settings.BindButton;
import me.retucio.sputnik.ui.widgets.buttons.SettingButton;
import me.retucio.sputnik.ui.widgets.buttons.settings.FileButton;
import me.retucio.sputnik.ui.widgets.buttons.settings.TextButton;
import me.retucio.sputnik.ui.widgets.panels.settings.ClientSettingsPanel;
import me.retucio.sputnik.ui.widgets.panels.SettingsPanel;
@@ -47,11 +52,10 @@ import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.inventory.AbstractCommandBlockEditScreen;
import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen;
import net.minecraft.client.gui.screens.inventory.AnvilScreen;
import net.minecraft.network.chat.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
public class Sputnik implements ClientModInitializer {
@@ -98,17 +102,22 @@ public class Sputnik implements ClientModInitializer {
EVENT_BUS.subscribe(MiscUtil.class);
EVENT_BUS.subscribe(NetworkUtil.class);
EVENT_BUS.subscribe(RenderUtil.class);
EVENT_BUS.subscribe(RotationUtil.class);
EVENT_BUS.subscribe(BlockIterator.class);
Lists.init();
HudRenderer.INSTANCE = new HudRenderer();
HudEditor.INSTANCE = new HudEditor();
MacroManager.INSTANCE = new MacroManager();
ProfileManager.INSTANCE = new ProfileManager();
CapeManager.INSTANCE = new CapeManager();
EVENT_BUS.post(new LoadCapeManagerEvent());
ModuleManager.INSTANCE = new ModuleManager();
ModuleManager.INSTANCE = new ModuleManager();
EVENT_BUS.post(new LoadModuleManagerEvent());
CommandManager.INSTANCE = new CommandManager();
@@ -134,11 +143,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);
@@ -150,44 +156,82 @@ public class Sputnik implements ClientModInitializer {
// se ejecuta cada vez que se presiona una tecla
public void onInput(int key, int action) {
// En SDL el valor 0 es UNKNOWN (y los botones del ratón empiezan en 1).
if (key == InputConstants.UNKNOWN.getValue() || key == -1) return;
// prevenir interrumpir combos de F3
if (key == GLFW.GLFW_KEY_F3) return;
if (key == InputConstants.KEY_F3) return;
boolean anyFocused = isAnySettingButtonFocused() || ClickGui.INSTANCE.getSearchBar().isFocused();
boolean anyFocused = isAnySettingButtonFocused() || ClickGui.INSTANCE.isCapturingInput();
ClickGui.INSTANCE.setAnyFocused(anyFocused);
if (action != GLFW.GLFW_RELEASE) {
// Las KeySetting y los campos de texto reciben el mismo código SDL scancode que
// guardan sus valores; procesarlos antes evita que el bind capturado dispare un módulo.
if (anyFocused) handleSettingButtonsKey(key, action);
if (action != InputConstants.RELEASE) {
if (BindCommand.onKeyPress(key)) return;
if (action == GLFW.GLFW_PRESS) {
if (action == InputConstants.PRESS && MacroManager.INSTANCE.onKeyPress(key)) return;
if (action == InputConstants.PRESS) {
handleModuleToggle(key, anyFocused);
handleClickGUIKey(key, anyFocused);
handleClickGuiKey(key, anyFocused);
handleHudEditorKey(key, anyFocused);
}
// solo reenviar teclas del teclado
if (key >= 32) {
handleSettingButtonsKey(key, action);
}
} else if (!anyFocused) {
handleModuleRelease(key);
}
}
public boolean onMouseInput(int button, int action) {
KeyUtil.setMouseButtonDown(button, action == InputConstants.PRESS);
boolean captured = false;
for (Button settingButton : ClickGui.INSTANCE.getClientSettingsPanel().getButtons()) {
if (settingButton instanceof BindButton bindButton && bindButton.isFocused()) {
bindButton.onMouse(button, action);
captured = true;
}
}
for (SettingsPanel panel : ClickGui.INSTANCE.getSettingsPanels()) {
for (Button settingButton : panel.getButtons()) {
if (settingButton instanceof BindButton bindButton && bindButton.isFocused()) {
bindButton.onMouse(button, action);
captured = true;
}
}
}
if (captured) return true;
onInput(KeyUtil.encodeMouseButton(button), action);
return false;
}
// verifica si algún botón de ajustes está escuchando
private boolean isAnySettingButtonFocused() {
if (ClickGui.INSTANCE == null) return false;
for (Button button : ClickGui.INSTANCE.getClientSettingsPanel().getButtons())
if ((button instanceof BindButton bindButton && bindButton.isFocused())
|| (button instanceof TextButton textButton && textButton.isFocused())
|| (button instanceof FileButton fileButton && fileButton.isFocused())) {
return true;
}
for (SettingsPanel sf : ClickGui.INSTANCE.getSettingsPanels())
for (Button sb : sf.getButtons())
if ((sb instanceof BindButton b && b.isFocused()) || (sb instanceof TextButton t && t.isFocused()))
if ((sb instanceof BindButton b && b.isFocused())
|| (sb instanceof TextButton t && t.isFocused())
|| (sb instanceof FileButton f && f.isFocused())
) {
return true;
}
return false;
}
// se ocupa de la lógica de encendido y apagado de los módulos
private void handleModuleToggle(int key, boolean anyFocused) {
if (mc.screen != null && mc.screen != ClickGui.INSTANCE) return;
if (mc.gui.screen() != null && mc.gui.screen() != ClickGui.INSTANCE) return;
for (Module module : ModuleManager.INSTANCE.getModules()) {
// evitar interrumpir combinaciones de teclas del F3
if (key != module.getKey() || anyFocused || KeyUtil.isKeyDown(GLFW.GLFW_KEY_F3)) continue;
if (key != module.getKey() || anyFocused || KeyUtil.isKeyDown(InputConstants.KEY_F3)) continue;
if (module.shouldToggleOnBindRelease() && !module.isEnabled())
module.setEnabled(true);
@@ -198,49 +242,51 @@ public class Sputnik implements ClientModInitializer {
// se ocupa de hacer los botones de ajustes que lo necesiten escuchar teclas
private void handleSettingButtonsKey(int key, int action) {
ClickGui.INSTANCE.getClientSettingsPanel().onKey(key, action);
for (SettingsPanel sf : ClickGui.INSTANCE.getSettingsPanels()) {
for (SettingButton<?> sb : sf.getButtons()) {
if (sb instanceof BindButton bb) bb.onKey(key, action);
if (sb instanceof FileButton fb) fb.onKey(key, action);
if (sb instanceof TextButton tb) tb.onKey(key, action);
}
}
}
// 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;
if (mc.screen != ClickGui.INSTANCE) {
prevScreen = mc.screen;
mc.setScreen(ClickGui.INSTANCE);
if (mc.gui.screen() != ClickGui.INSTANCE) {
prevScreen = mc.gui.screen();
mc.gui.setScreen(ClickGui.INSTANCE);
} else {
ClickGui.INSTANCE.onClose();
mc.setScreen(prevScreen);
mc.gui.setScreen(prevScreen);
}
}
// 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;
if (mc.screen != HudEditorScreen.INSTANCE) {
if (mc.gui.screen() != HudEditorScreen.INSTANCE) {
if (!hud.isEnabled()) {
ChatUtil.warn("HUD está desactivado, lumbreras");
ChatUtil.warn(Component.translatable("sputnik.hud.disabled"));
return;
}
prevScreen = mc.screen;
mc.setScreen(HudEditorScreen.INSTANCE);
prevScreen = mc.gui.screen();
mc.gui.setScreen(HudEditorScreen.INSTANCE);
} else {
HudEditorScreen.INSTANCE.onClose();
mc.setScreen(prevScreen);
mc.gui.setScreen(prevScreen);
}
}
// se ocupa de apagar los módulos que tengan configurado hacerlo tras soltar su tecla
private void handleModuleRelease(int key) {
if (mc.screen != null && mc.screen != ClickGui.INSTANCE) return;
if (mc.gui.screen() != null && mc.gui.screen() != ClickGui.INSTANCE) return;
for (Module module : ModuleManager.INSTANCE.getEnabledModules())
if (module.shouldToggleOnBindRelease() && key == module.getKey())
@@ -248,19 +294,29 @@ public class Sputnik implements ClientModInitializer {
}
private boolean isOnTypingScreen() {
return mc.screen instanceof ChatScreen
|| mc.screen instanceof AnvilScreen
|| mc.screen instanceof AbstractSignEditScreen
|| mc.screen instanceof AbstractCommandBlockEditScreen;
return mc.gui.screen() instanceof ChatScreen
|| mc.gui.screen() instanceof AnvilScreen
|| mc.gui.screen() instanceof AbstractSignEditScreen
|| mc.gui.screen() instanceof AbstractCommandBlockEditScreen;
}
private boolean keysVerified = false;
@EventListener
// notificar al usuario de que hay una actualización disponible, si la hay
public void onSetScreen(OpenScreenEvent event) {
if (VersionChecker.INSTANCE.shouldShowScreen && event.getScreen() instanceof TitleScreen) {
event.cancel();
VersionChecker.INSTANCE.shouldShowScreen = false;
mc.execute(() -> mc.screen = new UpdateScreen());
if (event.getScreen() instanceof TitleScreen) {
// aquí el idioma ya está cargado, que en onInitializeClient todavía no
if (!keysVerified) {
keysVerified = true;
I18nUtil.verifyKeys();
}
if (VersionChecker.INSTANCE.shouldShowScreen) {
event.cancel();
VersionChecker.INSTANCE.shouldShowScreen = false;
mc.execute(() -> mc.gui.setScreen(new UpdateScreen()));
}
}
}
@@ -272,4 +328,4 @@ public class Sputnik implements ClientModInitializer {
public static String getVersionName() {
return MOD_ID + "_v" + MOD_VERSION + "_" + SharedConstants.getCurrentVersion().name();
}
}
}
@@ -9,23 +9,26 @@ import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.data.registries.VanillaRegistries;
import net.minecraft.network.chat.Component;
import java.util.List;
public abstract class Command {
// utilizar los "registries" ya existentes de Brigadier para el autocompletado
protected static final CommandBuildContext REGISTRY_ACCESS = Commands.createValidationContext(VanillaRegistries.createLookup());
protected static final CommandBuildContext REGISTRY_ACCESS = Commands.createValidationContext(VanillaRegistries.createWorldLookup());
protected static final int SUCCESS = com.mojang.brigadier.Command.SINGLE_SUCCESS;
protected static final Minecraft mc = Minecraft.getInstance();
private final String name, description;
private final String name, descriptionKey;
private final List<String> aliases;
public Command(String name, String description, String... aliases) {
// la descripción se guarda como clave de traducción, no como texto ya resuelto: los comandos
// se construyen antes de que se cargue el idioma, así que hay que traducirla al mostrarla
public Command(String name, String descriptionKey, String... aliases) {
this.name = name;
this.description = description;
this.descriptionKey = descriptionKey;
this.aliases = List.of(aliases);
}
@@ -56,8 +59,12 @@ public abstract class Command {
return name;
}
public String getDescription() {
return description;
public String getDescriptionKey() {
return descriptionKey;
}
public Component getDescription() {
return Component.translatable(descriptionKey);
}
public List<String> getAliases() {
@@ -38,9 +38,13 @@ public class CommandManager {
addCommand(new EnderChestCommand());
addCommand(new FriendCommand());
addCommand(new GarbageCleanerCommand());
addCommand(new HelpCommand());
addCommand(new MacroCommand());
addCommand(new PanoramaCommand());
addCommand(new PeekCommand());
addCommand(new PluginsCommand());
addCommand(new PrefixCommand());
addCommand(new ProfileCommand());
addCommand(new PurgeCommand());
addCommand(new RepeatCommand());
addCommand(new RepeatWaitCommand());
@@ -25,7 +25,7 @@ public class EntityArgumentType implements ArgumentType<Entity> {
public static final EntityArgumentType INSTANCE = new EntityArgumentType();
private static final DynamicCommandExceptionType NO_SUCH_ENTITY = new DynamicCommandExceptionType(
name -> Component.literal("Entidad \"" + name + "\" no encontrada"));
name -> Component.translatable("sputnik.args.entity.notfound", name));
private static final Collection<String> EXAMPLES = List.of(
"AdlerHitdolf",
@@ -15,7 +15,8 @@ import java.util.concurrent.CompletableFuture;
public class EnumArgumentType<T extends Enum<T>> implements ArgumentType<T> {
private static final DynamicCommandExceptionType NO_SUCH_TYPE = new DynamicCommandExceptionType((value) -> Component.nullToEmpty(" argumento inválido: " + value));
private static final DynamicCommandExceptionType NO_SUCH_TYPE = new DynamicCommandExceptionType(
(value) -> Component.translatable("sputnik.args.enum.invalid", value));
private final T[] values;
@@ -33,8 +34,10 @@ public class EnumArgumentType<T extends Enum<T>> implements ArgumentType<T> {
public T parse(StringReader reader) throws CommandSyntaxException {
String argument = reader.readString();
// ignorando mayúsculas: listSuggestions sugiere en minúsculas, así que comparar
// con equals() hacía que las propias sugerencias no se pudieran parsear
return Arrays.stream(this.values)
.filter((value) -> value.toString().equals(argument))
.filter((value) -> value.toString().equalsIgnoreCase(argument))
.findFirst()
.orElseThrow(() -> NO_SUCH_TYPE.create(argument)
);
@@ -22,15 +22,15 @@ public class ModuleArgumentType implements ArgumentType<Module> {
public static final ModuleArgumentType INSTANCE = new ModuleArgumentType();
private static final DynamicCommandExceptionType UNKNOWN_MODULE = new DynamicCommandExceptionType(
name -> Component.literal("módulo \"" + name + "\" no encontrado"));
name -> Component.translatable("sputnik.args.module.notfound", name));
private static final Collection<String> examples = ModuleManager.INSTANCE.getModules()
.stream().limit(3).map(module -> module.getName().replace(" ", "_")).toList();
.stream().limit(3).map(module -> module.getDisplayName().replace(" ", "_")).toList();
@Override
public Module parse(StringReader reader) throws CommandSyntaxException {
String argument = reader.readString().replace("_", " ");
Module module = ModuleManager.INSTANCE.getModuleByName(argument);
Module module = ModuleManager.INSTANCE.getModuleByDisplayName(argument);
if (module == null) throw UNKNOWN_MODULE.create(argument);
return module;
}
@@ -38,7 +38,7 @@ public class ModuleArgumentType implements ArgumentType<Module> {
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(ModuleManager.INSTANCE.getModules()
.stream().map(module -> MiscUtil.removeAccentMarks(module.getName().replace(" ", "_"))), builder);
.stream().map(module -> MiscUtil.removeAccentMarks(module.getDisplayName().replace(" ", "_"))), builder);
}
@Override
@@ -21,7 +21,8 @@ import static me.retucio.sputnik.Sputnik.mc;
public class PlayerArgumentType implements ArgumentType<Player> {
public static final PlayerArgumentType INSTANCE = new PlayerArgumentType();
private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(name -> Component.literal("Player with name " + name + " doesn't exist."));
private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(
name -> Component.translatable("sputnik.args.player.notfound", name));
private static final Collection<String> EXAMPLES = List.of("retucio", "prepucio", "postpucio", "pucio", "adler hitdolf");
@@ -24,7 +24,7 @@ public class BindCommand extends Command {
private static Module listeningModule = null;
public BindCommand() {
super("bind", "asigna una tecla a un módulo", "keybind");
super("bind", "sputnik.commands.bind.desc", "keybind", "kb", "tecla");
}
@Override
@@ -34,14 +34,16 @@ public class BindCommand extends Command {
.executes(ctx -> {
Module module = ctx.getArgument("módulo", Module.class);
listeningModule = module;
ChatUtil.info("presiona una tecla para asignarla al módulo " + ChatFormatting.GREEN + module.getName());
ChatUtil.info(Component.translatable("sputnik.commands.bind.presskey", ChatFormatting.GREEN + module.getDisplayName()));
return SUCCESS;
})
.then(literal("reset").executes(ctx -> {
Module module = ctx.getArgument("módulo", Module.class);
module.getBind().reset();
ChatUtil.info("tecla para el módulo " + ChatFormatting.GREEN + module.getName()
+ " restablecida a " + ChatFormatting.AQUA + KeyUtil.getKeyName(module.getKey()));
ChatUtil.info(Component.translatable("sputnik.commands.bind.keyreset",
ChatFormatting.GREEN + module.getDisplayName(),
ChatFormatting.AQUA + KeyUtil.getKeyName(module.getKey())
));
return SUCCESS;
}))
);
@@ -51,17 +53,18 @@ public class BindCommand extends Command {
if (listeningModule == null) return false;
KeySetting bind = listeningModule.getBind();
if (ClientSettingsPanel.clientSettings.multipleKeybinds.getValue()) {
// solo hay que avisar de que la tecla está cogida cuando NO se permiten teclas repetidas.
// antes esto entraba con multipleKeybinds activado y luego comprobaba !allowMultiple con el
// mismo valor, así que nunca se llegaba a avisar
if (!ClientSettingsPanel.clientSettings.multipleKeybinds.getValue()) {
List<KeyMapping> keys = new ArrayList<>(List.of(mc.options.keyMappings));
keys.removeAll(List.of(mc.options.debugKeys));
for (KeyMapping kb : keys) {
boolean keyAlreadyBound = kb.matches(new KeyEvent(key, 0, 0));
boolean allowMultiple = ClientSettingsPanel.clientSettings.multipleKeybinds.getValue();
if (keyAlreadyBound && !allowMultiple) {
ChatUtil.warn("esa tecla ya está cogida por "
+ ChatFormatting.GREEN + "\"" + I18n.get(kb.getName() + "\""));
if (kb.matches(new KeyEvent(key, 0, 0))) {
// la comilla iba dentro del I18n.get, así que buscaba una clave inexistente
ChatUtil.warn(Component.translatable("sputnik.commands.bind.keytaken",
ChatFormatting.GREEN + "\"" + I18n.get(kb.getName()) + "\""));
listeningModule = null;
return true;
}
@@ -70,10 +73,10 @@ public class BindCommand extends Command {
if (bind != null) bind.setValue(key);
ChatUtil.info(
Component.nullToEmpty("la tecla " + ChatFormatting.AQUA + KeyUtil.getKeyName(key) + ChatFormatting.RESET +
" ha sido asignada al módulo " + ChatFormatting.GREEN + listeningModule.getName())
);
ChatUtil.info(Component.translatable("sputnik.commands.bind.keybound",
ChatFormatting.AQUA + KeyUtil.getKeyName(key) + ChatFormatting.RESET,
ChatFormatting.GREEN + listeningModule.getDisplayName()
));
listeningModule = null;
return true;
@@ -13,7 +13,7 @@ import net.minecraft.world.inventory.ContainerInput;
public class ClickSlotCommand extends Command {
public ClickSlotCommand() {
super("clickslot", "simula un clic a un slot");
super("clickslot", "sputnik.commands.clickslot.desc", "cs");
}
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
@@ -23,7 +23,7 @@ public class ClickSlotCommand extends Command {
.executes(context -> {
int slot = context.getArgument("slot", Integer.class), button = context.getArgument("botón", Integer.class);
ContainerInput action = context.getArgument("acción", ContainerInput.class);
Screen screen = mc.screen;
Screen screen = mc.gui.screen();
int syncId = (screen instanceof ContainerScreen cScreen) ? cScreen.getMenu().containerId : 0;
if (mc.gameMode == null) return 0;
mc.gameMode.handleContainerInput(syncId, slot, button, action, mc.player);
@@ -13,7 +13,7 @@ import me.retucio.sputnik.module.Module;
public class ConfigCommand extends Command {
public ConfigCommand() {
super("config", "abre el marco de ajustes de un módulo", "ajustes", "settings");
super("config", "sputnik.commands.config.desc", "ajustes", "settings");
}
@Override
@@ -40,7 +40,7 @@ public class ConfigCommand extends Command {
private void openSf(CommandContext<SharedSuggestionProvider> ctx) {
Module module = ctx.getArgument("módulo", Module.class);
mc.setScreen(ClickGui.INSTANCE);
mc.gui.setScreen(ClickGui.INSTANCE);
if (ClickGui.INSTANCE.isSettingsPanelOpen(module)) return;
@@ -53,7 +53,7 @@ public class ConfigCommand extends Command {
private void openSfWithPos(CommandContext<SharedSuggestionProvider> ctx, int x, int y) {
Module module = ctx.getArgument("módulo", Module.class);
mc.setScreen(ClickGui.INSTANCE);
mc.gui.setScreen(ClickGui.INSTANCE);
if (ClickGui.INSTANCE.isSettingsPanelOpen(module)) {
SettingsPanel sf = ClickGui.INSTANCE.getSettingsPanelOfModule(module);
@@ -10,7 +10,7 @@ import net.minecraft.commands.SharedSuggestionProvider;
public class CopyScreenshotCommand extends Command {
public CopyScreenshotCommand() {
super("copiarcaptura", "copia la captura más reciente al portapapeles");
super("copiarcaptura", "sputnik.commands.copyscreenshot.desc", "copyscreenshot");
}
@Override
@@ -8,6 +8,7 @@ import me.retucio.sputnik.ui.screen.PreviewScreen;
import me.retucio.sputnik.util.ChatUtil;
import me.retucio.sputnik.util.InventoryUtil;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
// accede a los contenidos de tu enderchest
public class EnderChestCommand extends Command {
@@ -15,7 +16,7 @@ public class EnderChestCommand extends Command {
private boolean shouldOpenEchest = false;
public EnderChestCommand() {
super("echest", "muestra el contenido de tu enderchest (solo de ver)", "ec", "enderchest");
super("echest", "sputnik.commands.echest.desc", "ec", "enderchest");
}
@Override
@@ -24,7 +25,7 @@ public class EnderChestCommand extends Command {
mc.execute(() -> {
if (mc.player == null) return;
if (InventoryUtil.getEchestInv() == null) {
ChatUtil.warn("necesitas abrir un enderchest una vez primero");
ChatUtil.warn(Component.translatable( "sputnik.commands.echest.openfirst"));
return;
}
@@ -40,7 +41,7 @@ public class EnderChestCommand extends Command {
if (shouldOpenEchest) {
shouldOpenEchest = false;
if (InventoryUtil.getEchestInv() != null)
mc.setScreen(new PreviewScreen(InventoryUtil.getEchestInv(), null));
mc.gui.setScreen(new PreviewScreen(InventoryUtil.getEchestInv(), null));
}
}
}
@@ -8,12 +8,14 @@ import me.retucio.sputnik.friend.FriendManager;
import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
public class FriendCommand extends Command {
public FriendCommand() {
super("friend", "gestión de amigos", "amigo", "f");
super("friend", "sputnik.commands.friend.desc", "amigo", "f");
}
@Override
@@ -24,7 +26,10 @@ public class FriendCommand extends Command {
.executes(ctx -> {
Player friend = ctx.getArgument("amigo", Player.class);
FriendManager.INSTANCE.add(friend.getUUID());
ChatUtil.info(ChatFormatting.GREEN + friend.getName().getString() + ChatFormatting.RESET + " ahora es tu amigo :)");
ChatUtil.info(Component.translatable(
"sputnik.commands.friend.added",
ChatFormatting.GREEN + friend.getName().getString() + ChatFormatting.RESET
));
return SUCCESS;
})
)
@@ -34,14 +39,17 @@ public class FriendCommand extends Command {
.executes(ctx -> {
Player friend = ctx.getArgument("amigo", Player.class);
FriendManager.INSTANCE.remove(friend.getUUID());
ChatUtil.info(ChatFormatting.GREEN + friend.getName().getString() + ChatFormatting.RESET + " ya no es tu amigo :(");
ChatUtil.info(Component.translatable(
"sputnik.commands.friend.removed",
ChatFormatting.GREEN + friend.getName().getString() + ChatFormatting.RESET
));
return SUCCESS;
})
)
)
.then(literal("list")
.executes(ctx -> {
ChatUtil.info(ChatFormatting.GOLD + "amigos:");
ChatUtil.info(ChatFormatting.GOLD + Component.translatable("sputnik.commands.friend.list").getString());
for (Friend friend : FriendManager.INSTANCE.getFriends()) {
ChatUtil.info("- " + friend.getName());
}
@@ -9,14 +9,14 @@ import net.minecraft.network.chat.Component;
public class GarbageCleanerCommand extends Command {
public GarbageCleanerCommand() {
super("gc", "limpia la ram mediante el recolector de basura (gc)", "garbagecollect");
super("gc", "sputnik.commands.gc.desc", "garbagecollect");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder.executes(context -> {
builder.executes(ctx -> {
System.gc();
ChatUtil.info(Component.nullToEmpty("basura recolectada"));
ChatUtil.info(Component.translatable("sputnik.commands.gc.collected"));
return SUCCESS;
});
}
@@ -0,0 +1,28 @@
package me.retucio.sputnik.command.commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.retucio.sputnik.command.Command;
import me.retucio.sputnik.command.CommandManager;
import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
public class HelpCommand extends Command {
public HelpCommand() {
super("help", Component.translatable("sputnik.commands.help.desc").getString(), "?", "ayuda", "h");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder.executes(ctx -> {
for (Command command : CommandManager.commands) {
ChatUtil.info(CommandManager.INSTANCE.getPrefix()
+ ChatFormatting.BOLD + command.getName() + ": "
+ ChatFormatting.RESET + ChatFormatting.GRAY + command.getDescription().getString());
}
return SUCCESS;
});
}
}
@@ -0,0 +1,136 @@
package me.retucio.sputnik.command.commands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import me.retucio.sputnik.command.Command;
import me.retucio.sputnik.macro.Macro;
import me.retucio.sputnik.macro.MacroManager;
import me.retucio.sputnik.util.ChatUtil;
import me.retucio.sputnik.util.KeyUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import java.util.List;
public class MacroCommand extends Command {
private static final SuggestionProvider<SharedSuggestionProvider> MACROS = (ctx, builder) ->
SharedSuggestionProvider.suggest(
MacroManager.INSTANCE.getMacros().stream().map(Macro::getName), builder);
public MacroCommand() {
super("macro", "sputnik.commands.macro.desc", "macros");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder
.executes(ctx -> {
list();
return SUCCESS;
})
.then(literal("list").executes(ctx -> {
list();
return SUCCESS;
}))
// por ahora "add" solo crea macros de chat
.then(literal("add")
.then(argument("nombre", StringArgumentType.word()).executes(ctx -> {
String name = ctx.getArgument("nombre", String.class);
if (MacroManager.INSTANCE.add(new Macro.Chat(name)))
ChatUtil.info(Component.translatable("sputnik.commands.macro.added",
ChatFormatting.AQUA + name));
else
ChatUtil.error(Component.translatable("sputnik.commands.macro.exists", name));
return SUCCESS;
})))
.then(literal("remove")
.then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> {
Macro macro = get(ctx.getArgument("nombre", String.class));
if (macro == null) return SUCCESS;
MacroManager.INSTANCE.remove(macro);
ChatUtil.info(Component.translatable("sputnik.commands.macro.removed",
ChatFormatting.AQUA + macro.getName()));
return SUCCESS;
})))
.then(literal("line")
.then(argument("nombre", StringArgumentType.word()).suggests(MACROS)
.then(argument("línea", StringArgumentType.greedyString()).executes(ctx -> {
Macro.Chat macro = getChat(ctx.getArgument("nombre", String.class));
if (macro == null) return SUCCESS;
String line = ctx.getArgument("línea", String.class);
macro.getLines().add(line);
MacroManager.INSTANCE.save();
ChatUtil.info(Component.translatable("sputnik.commands.macro.lineadded",
ChatFormatting.AQUA + macro.getName()));
return SUCCESS;
}))))
.then(literal("clear")
.then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> {
Macro.Chat macro = getChat(ctx.getArgument("nombre", String.class));
if (macro == null) return SUCCESS;
macro.getLines().clear();
MacroManager.INSTANCE.save();
ChatUtil.info(Component.translatable("sputnik.commands.macro.cleared",
ChatFormatting.AQUA + macro.getName()));
return SUCCESS;
})))
.then(literal("run")
.then(argument("nombre", StringArgumentType.word()).suggests(MACROS).executes(ctx -> {
Macro macro = get(ctx.getArgument("nombre", String.class));
if (macro != null) macro.run();
return SUCCESS;
})));
}
private static Macro get(String name) {
Macro macro = MacroManager.INSTANCE.getByName(name);
if (macro == null) ChatUtil.error(Component.translatable("sputnik.commands.macro.notfound", name));
return macro;
}
// para los subcomandos que solo tienen sentido con líneas de chat (line, clear): si la
// macro existe pero es de otro tipo, avisa y devuelve null
private static Macro.Chat getChat(String name) {
Macro macro = get(name);
if (macro == null) return null;
if (!(macro instanceof Macro.Chat chat)) {
ChatUtil.error(Component.translatable("sputnik.commands.macro.notchat", macro.getName()));
return null;
}
return chat;
}
private static void list() {
List<Macro> macros = MacroManager.INSTANCE.getMacros();
if (macros.isEmpty()) {
ChatUtil.info(Component.translatable("sputnik.commands.macro.none"));
return;
}
ChatUtil.info(Component.translatable("sputnik.commands.macro.list", macros.size()));
for (Macro macro : macros) {
// líneas en las de chat, teclas en las Keybind
int count = switch (macro) {
case Macro.Chat chat -> chat.getLines().size();
case Macro.Keybind keybind -> keybind.getKeys().size();
default -> 0;
};
ChatUtil.addMessage(ChatFormatting.GRAY + " - " + macro.getName()
+ ChatFormatting.DARK_GRAY + " [" + KeyUtil.getKeyName(macro.getKey()) + "] "
+ count);
}
}
}
@@ -0,0 +1,265 @@
package me.retucio.sputnik.command.commands;
import com.github.retucio.neutrino.EventListener;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.command.Command;
import me.retucio.sputnik.event.TickEvent;
import me.retucio.sputnik.util.ChatUtil;
import me.retucio.sputnik.util.MiscUtil;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.WorldVersion;
import net.minecraft.client.Screenshot;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.metadata.pack.PackFormat;
import net.minecraft.sounds.SoundEvents;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.arguments.StringArgumentType;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.ThreadLocalRandom;
/**
* literalmente robado de <a href="https://github.com/0xTas/stardust/blob/main/src/main/java/dev/stardust/commands/Panorama.java">aquí</a>
*/
public class PanoramaCommand extends Command {
private int i = -1;
int timer = 10;
boolean ready = false;
boolean warming = false;
private float prevYaw;
private float prevPitch;
private int prevWidth;
private int prevHeight;
private boolean prevHudHidden;
@Nullable Path dir = null;
public PanoramaCommand() {
super("panorama", "sputnik.commands.panorama.desc");
}
private void takeScreenshot() {
if (dir == null) return;
Screenshot.grab(
dir.toFile(),
"panorama_" + i + ".png",
mc.gameRenderer.mainRenderTarget(),
1, msg -> Sputnik.LOGGER.info(msg.getString())
);
i++;
warming = false;
}
private void begin(String name) {
if (mc.player == null) return;
dir = FabricLoader.getInstance().getGameDir().resolve(Sputnik.MOD_ID + "/panoramas/" + name);
dir.toFile().mkdirs();
i = 0;
warming = false;
prevYaw = mc.player.getYRot();
prevPitch = mc.player.getXRot();
prevWidth = mc.getWindow().getWidth();
prevHeight = mc.getWindow().getHeight();
prevHudHidden = mc.gui.hud.isHidden();
mc.getWindow().setWidth(4096);
mc.getWindow().setHeight(4096);
mc.gameRenderer.mainRenderTarget().resize(4096, 4096);
}
private void assembleResourcePack() {
if (dir == null) return;
Path resourcePacks = FabricLoader.getInstance().getGameDir().resolve("resourcepacks");
Path screenshotsFolder = FabricLoader.getInstance().getGameDir().resolve(Sputnik.MOD_ID + "/panoramas/" + dir.getFileName() + "/screenshots");
Path customBaseFolder = resourcePacks.resolve(dir.getFileName());
Path customPackFolder = customBaseFolder.resolve("assets/minecraft/textures/gui/title/background/");
customPackFolder.toFile().mkdirs();
String mcMeta =
"""
{
"pack":
{
"min_format": [65, 0],
"max_format": [%d, 0],
"description": "%s"
}
}
""".formatted(
getPackVersion(),
Component.translatable("sputnik.commands.panorama.packdesc").getString()
);
try {
int luckyNumber = ThreadLocalRandom.current().nextInt(6);
Files.walkFileTree(screenshotsFolder, new SimpleFileVisitor<>() {
public @NonNull FileVisitResult visitFile(@NonNull Path file, @NonNull BasicFileAttributes attrs) {
MiscUtil.copy(file.toFile(), customPackFolder.resolve(file.getFileName()).toFile());
// pack.png random de alguna de los screenshots
if (file.getFileName().toString().contains(String.valueOf(luckyNumber))) {
MiscUtil.copy(file.toFile(), customBaseFolder.resolve("pack.png").toFile());
}
return FileVisitResult.CONTINUE;
}
});
File packMeta = customBaseFolder.resolve("pack.mcmeta").toFile();
if (!packMeta.createNewFile()) {
ChatUtil.error(Component.translatable("sputnik.commands.panorama.errorassemble").getString());
return;
}
Files.write(packMeta.toPath(), mcMeta.getBytes());
// eliminar el efecto vignette feo de cojones que usa Minecraft por defecto
BufferedImage transparentOverlay = new BufferedImage(16, 128, BufferedImage.TYPE_INT_ARGB);
File overlayImage = customPackFolder.resolve("panorama_overlay.png").toFile();
ImageIO.write(transparentOverlay, "PNG", overlayImage);
} catch (Exception e) {
ChatUtil.error("error de i/o");
Sputnik.LOGGER.error(e);
}
ready = false;
if (mc.player != null) {
mc.player.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, 1f, 1f);
ChatUtil.info(Component.translatable("sputnik.commands.panorama.ready").getString());
}
}
@EventListener
private void onTick(TickEvent.Post event) {
if (mc.player == null || dir == null) return;
if (timer > 0) {
--timer;
return;
} else {
timer = 10;
}
if (ready) {
assembleResourcePack();
}
if (i < 0) return;
switch (i) {
case 0 -> {
if (!warming) {
mc.gameRenderer.mainCamera().enablePanoramicMode();
mc.gameRenderer.setRenderBlockOutline(false);
mc.execute(() -> {
synchronized (mc.levelRenderer) {
mc.levelRenderer.notifyAll();
}
});
if (!mc.gui.hud.isHidden()) mc.gui.hud.toggle();
mc.player.setYRot(prevYaw);
mc.player.setXRot(0f);
warming = true;
} else takeScreenshot();
}
case 1 -> {
if (!warming) {
mc.player.setYRot((prevYaw + 90f) % 360f);
mc.player.setXRot(0f);
warming = true;
} else takeScreenshot();
}
case 2 -> {
if (!warming) {
mc.player.setYRot((prevYaw + 180f) % 360f);
mc.player.setXRot(0f);
warming = true;
} else takeScreenshot();
}
case 3 -> {
if (!warming) {
mc.player.setYRot((prevYaw - 90f) % 360f);
mc.player.setXRot(0f);
warming = true;
} else takeScreenshot();
}
case 4 -> {
if (!warming) {
mc.player.setYRot(prevYaw);
mc.player.setXRot(-90f);
warming = true;
} else takeScreenshot();
}
default -> {
if (!warming) {
mc.player.setYRot(prevYaw);
mc.player.setXRot(90f);
warming = true;
} else {
takeScreenshot();
i = -1;
mc.player.setYRot(prevYaw);
mc.player.setXRot(prevPitch);
mc.gameRenderer.mainCamera().disablePanoramicMode();
mc.gameRenderer.setRenderBlockOutline(true);
mc.getWindow().setWidth(prevWidth);
mc.getWindow().setHeight(prevHeight);
if (mc.gui.hud.isHidden() != prevHudHidden) mc.gui.hud.toggle();
mc.gameRenderer.mainRenderTarget().resize(prevWidth, prevHeight);
mc.execute(() -> {
synchronized (mc.levelRenderer) {
mc.levelRenderer.notifyAll();
}
});
timer = 100; // esperar a que las capturas se escriban
ready = true;
ChatUtil.info(Component.translatable("sputnik.commands.panorama.finalizing").getString());
}
}
}
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder.then(
argument("name", StringArgumentType.word()).executes(ctx -> {
String name = ctx.getArgument("name", String.class);
begin(name);
return SUCCESS;
})
);
}
public static int getPackVersion() {
WorldVersion version = SharedConstants.getCurrentVersion();
PackFormat format = version.packVersion(PackType.CLIENT_RESOURCES);
return format.major();
}
}
@@ -7,11 +7,13 @@ import me.retucio.sputnik.util.MiscUtil;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.commands.SharedSuggestionProvider;
import java.awt.*;
// comando que debería de mostrar el inventario de un jugador, pero por cómo funcionan los servers, solo te deja ver el ítem que tenga en la mano
public class PeekCommand extends Command {
public PeekCommand() {
super("inventario", "cotillea el inventario de otro jugador", "invsee", "peek", "inv");
super("inventario", "sputnik.commands.peek.desc", "invsee", "peek", "inv");
}
@Override
@@ -12,6 +12,7 @@ import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.multiplayer.ClientSuggestionProvider;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundCommandsPacket;
import java.util.HashSet;
@@ -23,7 +24,7 @@ public class PluginsCommand extends Command {
private boolean scanning = false;
public PluginsCommand() {
super("plugins", "busca los plugins que tenga el server", "pl", "serverplugins");
super("plugins", "sputnik.commands.plugins.desc", "pl", "serverplugins");
Sputnik.EVENT_BUS.subscribe(this);
}
@@ -31,12 +32,12 @@ public class PluginsCommand extends Command {
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder.executes(context -> {
if (mc.hasSingleplayerServer()) {
ChatUtil.error("estás en un solo jugador");
ChatUtil.error(Component.translatable("sputnik.commands.plugins.singleplayer"));
return SUCCESS;
}
if (scanning) {
ChatUtil.error("calmaaa");
ChatUtil.error(Component.translatable("sputnik.commands.plugins.calmdown"));
return SUCCESS;
}
@@ -60,7 +61,7 @@ public class PluginsCommand extends Command {
root.getChildren().forEach(this::checkNodeForPlugins);
if (!plugins.isEmpty()) {
ChatUtil.info(plugins.size() + " namespace(s) encontrado(s) de comandos:");
ChatUtil.info(Component.translatable("sputnik.commands.plugins.namespacesfound", plugins.size()));
plugins.stream()
.sorted()
.forEach(plugin -> ChatUtil.info(" - " + plugin));
@@ -69,7 +70,7 @@ public class PluginsCommand extends Command {
displayResults();
} catch (Exception e) {
ChatUtil.error("error analizando comandos: " + e.getMessage());
ChatUtil.error(Component.translatable("sputnik.commands.plugins.erroranalyzingcommands", e.getMessage()));
}
}
@@ -99,11 +100,11 @@ public class PluginsCommand extends Command {
private void displayResults() {
if (!plugins.isEmpty()) {
ChatUtil.info(plugins.size() + " plugin(s) encontrado(s)");
ChatUtil.info(Component.translatable("sputnik.commands.plugins.pluginsfound", plugins.size()));
plugins.stream().sorted()
.forEach(plugin -> ChatUtil.info(" - " + plugin));
} else {
ChatUtil.warn("sin plugins (probablemente)");
ChatUtil.warn(Component.translatable("sputnik.commands.plugins.noplugins"));
}
scanning = false;
@@ -13,17 +13,17 @@ import net.minecraft.network.chat.Component;
public class PrefixCommand extends Command {
public PrefixCommand() {
super("prefijo", "cambia el prefijo de los comandos", "prefix");
super("prefijo", "sputnik.commands.prefix.desc", "prefix");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder
.then(argument("prefijo", StringArgumentType.word()).executes(ctx -> {
String prefix = ctx.getArgument("prefijo", String.class);
ClientSettingsPanel.clientSettings.commandPrefix.setValue(prefix);
ChatUtil.info(Component.literal("prefijo cambiado a " + ChatFormatting.AQUA + prefix));
return SUCCESS;
}));
.then(argument("prefijo", StringArgumentType.word()).executes(ctx -> {
String prefix = ctx.getArgument("prefijo", String.class);
ClientSettingsPanel.clientSettings.commandPrefix.setValue(prefix);
ChatUtil.info(Component.translatable("sputnik.commands.prefix.changed", ChatFormatting.AQUA + prefix));
return SUCCESS;
}));
}
}
@@ -0,0 +1,92 @@
package me.retucio.sputnik.command.commands;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import me.retucio.sputnik.command.Command;
import me.retucio.sputnik.config.ConfigManager;
import me.retucio.sputnik.config.ProfileManager;
import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import java.util.List;
public class ProfileCommand extends Command {
private static final SuggestionProvider<SharedSuggestionProvider> PROFILES = (ctx, builder) ->
SharedSuggestionProvider.suggest(ProfileManager.INSTANCE.getProfileNames(), builder);
public ProfileCommand() {
super("perfil", "sputnik.commands.profile.desc", "profile", "perfiles", "profiles");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder
.executes(ctx -> {
list();
return SUCCESS;
})
.then(literal("list").executes(ctx -> {
list();
return SUCCESS;
}))
.then(literal("save")
.then(argument("nombre", StringArgumentType.word()).executes(ctx -> {
String name = ctx.getArgument("nombre", String.class);
if (!ProfileManager.isValidName(name)) {
ChatUtil.error(Component.translatable("sputnik.commands.profile.invalidname", name));
return SUCCESS;
}
if (ProfileManager.INSTANCE.save(name)) {
ConfigManager.INSTANCE.save();
ChatUtil.info(Component.translatable("sputnik.commands.profile.saved",
ChatFormatting.AQUA + name));
} else {
ChatUtil.error(Component.translatable("sputnik.commands.profile.savefailed", name));
}
return SUCCESS;
})))
.then(literal("load")
.then(argument("nombre", StringArgumentType.word()).suggests(PROFILES).executes(ctx -> {
String name = ctx.getArgument("nombre", String.class);
if (ProfileManager.INSTANCE.load(name))
ChatUtil.info(Component.translatable("sputnik.commands.profile.loaded",
ChatFormatting.AQUA + name));
else
ChatUtil.error(Component.translatable("sputnik.commands.profile.notfound", name));
return SUCCESS;
})))
.then(literal("delete")
.then(argument("nombre", StringArgumentType.word()).suggests(PROFILES).executes(ctx -> {
String name = ctx.getArgument("nombre", String.class);
if (ProfileManager.INSTANCE.delete(name))
ChatUtil.info(Component.translatable("sputnik.commands.profile.deleted",
ChatFormatting.AQUA + name));
else
ChatUtil.error(Component.translatable("sputnik.commands.profile.notfound", name));
return SUCCESS;
})));
}
private static void list() {
List<String> profiles = ProfileManager.INSTANCE.getProfileNames();
if (profiles.isEmpty()) {
ChatUtil.info(Component.translatable("sputnik.commands.profile.none"));
return;
}
String current = ProfileManager.INSTANCE.getCurrent();
ChatUtil.info(Component.translatable("sputnik.commands.profile.list", profiles.size()));
for (String profile : profiles)
ChatUtil.addMessage((profile.equals(current) ? ChatFormatting.GREEN : ChatFormatting.GRAY) + " - " + profile);
}
}
@@ -3,7 +3,7 @@ package me.retucio.sputnik.command.commands;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.retucio.sputnik.command.Command;
import me.retucio.sputnik.util.interfaces.IChatComponent;
import me.retucio.sputnik.mixinterfaces.IChatComponent;
import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.client.multiplayer.chat.GuiMessage;
import net.minecraft.commands.SharedSuggestionProvider;
@@ -13,7 +13,7 @@ import java.util.List;
public class PurgeCommand extends Command {
public PurgeCommand() {
super("purgar", "elimina mensajes del chat", "purge");
super("purgar", "sputnik.commands.purge.desc", "purge");
}
@Override
@@ -28,7 +28,7 @@ public class PurgeCommand extends Command {
}
private void purgeMessages(int amount) {
ChatComponent chatHud = mc.gui.getChat();
ChatComponent chatHud = mc.gui.hud.getChat();
synchronized (chatHud) {
List<GuiMessage.Line> visibleMessages = ((IChatComponent) chatHud).sputnik$getVisibleMessages();
@@ -10,7 +10,7 @@ import net.minecraft.commands.SharedSuggestionProvider;
public class RepeatCommand extends Command {
public RepeatCommand() {
super("repetir", "repite un comando x veces", "repeat");
super("repetir", "sputnik.commands.repeat.desc", "repeat");
}
@Override
@@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit;
public class RepeatWaitCommand extends Command {
public RepeatWaitCommand() {
super("repesperar", "combinación de $repetir y $esperar");
super("repesperar", "sputnik.commands.repeatwait.desc", "repeatwait");
}
@Override
@@ -6,6 +6,7 @@ import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.misc.ScreenshotPlus;
import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import java.io.IOException;
@@ -13,7 +14,7 @@ import java.io.IOException;
public class SaveScreenshotCommand extends Command {
public SaveScreenshotCommand() {
super("guardarcaptura", "guarda localmente la captura más reciente");
super("guardarcaptura", "sputnik.commands.savescreenshot.desc", "savescreenshot");
}
@Override
@@ -26,7 +27,7 @@ public class SaveScreenshotCommand extends Command {
try {
screenshotPlus.saveScreenshot();
} catch (IOException e) {
ChatUtil.error("no se pudo guardar la captura");
ChatUtil.error(Component.translatable("sputnik.commands.savescreenshot.error"));
}
}
});
@@ -18,7 +18,7 @@ import java.time.Instant;
public class SendCommand extends Command {
public SendCommand() {
super("enviar", "manda un mensaje en el chat", "send");
super("enviar", "sputnik.commands.send.desc", "send");
}
@Override
@@ -37,7 +37,7 @@ public class SendCommand extends Command {
LastSeenMessagesTracker tracker = ((ClientPacketListenerAccessor) handler).getLastSeenMessagesTracker();
LastSeenMessagesTracker.Update lastSeenMessages = tracker.generateAndApplyUpdate();
// empacar firma para un chat seguro
// empacar firma
MessageSignature messageSignature = ((ClientPacketListenerAccessor) handler)
.getSignedMessageEncoder()
.pack(new SignedMessageBody(
@@ -50,7 +50,7 @@ public class SendCommand extends Command {
message,
instant,
salt,
messageSignature,
java.util.Optional.ofNullable(messageSignature),
lastSeenMessages.update()
));
}
@@ -58,4 +58,4 @@ public class SendCommand extends Command {
return SUCCESS;
}));
}
}
}
@@ -8,18 +8,20 @@ import me.retucio.sputnik.event.input.KeyEvent;
import me.retucio.sputnik.util.EntityUtil;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.world.entity.Entity;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.platform.InputConstants;
import java.awt.*;
public class SpectateCommand extends Command {
public SpectateCommand() {
super("espectar", "te permite adquirir el punto de vista de una entidad", "spectate", "spec");
super("espectar", "sputnik.commands.spectate.desc", "spectate", "spec");
}
@Override
public void build(LiteralArgumentBuilder<SharedSuggestionProvider> builder) {
builder
.executes(ctx -> { // If no argument, use the entity player is looking at
.executes(ctx -> {
mc.execute(() -> {
Entity target = EntityUtil.getEntityPlayerIsLookingAt();
if (target != null)
@@ -40,8 +42,8 @@ public class SpectateCommand extends Command {
@EventListener
public void onKey(KeyEvent event) {
if (mc.getCameraEntity() != mc.player && mc.screen == null
&& event.getKey() == GLFW.GLFW_KEY_ESCAPE && event.getAction() != GLFW.GLFW_RELEASE ) {
if (mc.getCameraEntity() != mc.player && mc.gui.screen() == null
&& event.getKey() == InputConstants.KEY_ESCAPE && event.getAction() != InputConstants.RELEASE ) {
event.cancel();
mc.setCameraEntity(mc.player);
}
@@ -7,11 +7,13 @@ import me.retucio.sputnik.module.Module;
import me.retucio.sputnik.module.ModuleManager;
import net.minecraft.commands.SharedSuggestionProvider;
import java.awt.*;
// porque usar el ratón es más lento que escribir, a menos que tengas toque con el teclado como yo
public class ToggleCommand extends Command {
public ToggleCommand() {
super("alternar", "enciende / apaga módulos", "toggle");
super("alternar", "sputnik.commands.toggle.desc", "toggle");
}
@Override
@@ -16,7 +16,7 @@ public class WaitCommand extends Command {
private static final ScheduledExecutorService DELAYED_EXECUTOR = new ScheduledThreadPoolExecutor(2);
public WaitCommand() {
super("wait", "espera un tiempo determinado antes de ejecutar otro comando", "esperar", "sleep", "delay");
super("wait", "sputnik.commands.wait.desc", "esperar", "sleep", "delay");
}
@Override
@@ -1,5 +1,8 @@
package me.retucio.sputnik.config;
import me.retucio.sputnik.util.I18nUtil;
import me.retucio.sputnik.util.KeyUtil;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -12,7 +15,8 @@ import java.util.Map;
public class BinarySerializer {
private static final int MAGIC_NUMBER = 0x53505554; // "SPUT"
private static final int VERSION = 2;
private static final int VERSION = 6;
private static final int MIN_VERSION = 2;
private static final byte TYPE_NULL = 0;
private static final byte TYPE_BOOLEAN = 1;
@@ -23,6 +27,9 @@ public class BinarySerializer {
private static final byte TYPE_INT_ARRAY = 6;
private static final byte TYPE_FRAME_DATA = 7;
private static final byte MACRO_CHAT = 0;
private static final byte MACRO_KEYBIND = 1;
public static void writeConfig(ClientConfig config, File file) throws IOException {
byte[] bytes = toBytes(config);
writeBytes(bytes, file);
@@ -69,7 +76,9 @@ public class BinarySerializer {
writeStringIntArrayMap(out, config.hudPositions);
writeStringBooleanMap(out, config.hudVisibilities);
writeStringStringMap(out, config.hudImagePaths);
writeStringStringMap(out, config.friends);
writeFriendsMap(out, config.friends);
writeMacros(out, config.macros);
writeString(out, config.currentProfile);
if (config.searchBarPosition != null) {
out.writeBoolean(true);
@@ -83,48 +92,86 @@ public class BinarySerializer {
public static ClientConfig readConfig(File file)
throws IOException {
if (!file.exists() || file.length() == 0) {
throw new IOException("archivo de config. vacío o inexistente");
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("archivo demasiado pequeño para ser válido");
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("formato de archivo inválido: " + magic);
throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidformat",
"formato de archivo inválido: %s", magic));
}
int version = in.readInt();
if (version != VERSION) {
throw new IOException("versión inválida: " + version);
if (version < MIN_VERSION || version > VERSION) {
throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidversion",
"versión inválida: %s", version));
}
ClientConfig config = new ClientConfig();
// 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, ClientConfig.FriendData> friends = version >= 4
? readFriendsMap(in)
: migrateFriendsMap(readStringStringMap(in));
// 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);
// v3 y v4: macros sin tipo (todas eran de chat). v5+: con tipo
List<ClientConfig.MacroData> macros;
if (version >= 5) macros = readMacros(in);
else if (version >= 3) macros = readLegacyMacros(in);
else macros = new ArrayList<>();
String currentProfile = version >= 3 ? readString(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;
config.macros = macros;
config.currentProfile = currentProfile == null ? "" : currentProfile;
if (searchBarPosition != null) config.searchBarPosition = searchBarPosition;
config.legacyKeyCodes = version < 6;
return config;
}
}
public static void migrateLegacyKeyCodes(ClientConfig config) {
config.macros = config.macros.stream().map(macro -> {
int key = KeyUtil.fromLegacyGlfwKey(macro.key());
List<ClientConfig.KeyData> keys = macro.keys() == null ? List.of() : macro.keys().stream()
.map(entry -> new ClientConfig.KeyData(KeyUtil.fromLegacyGlfwKey(entry.key()), entry.action()))
.toList();
return new ClientConfig.MacroData(macro.type(), macro.name(), key, macro.lines(), keys);
}).toList();
}
private static void writeStringBooleanMap(DataOutputStream out, Map<String, Boolean> map)
throws IOException {
if (map == null) {
@@ -344,7 +391,7 @@ public class BinarySerializer {
}
private static void writeStringStringMap(DataOutputStream out, Map<String, String> map)
throws IOException {
throws IOException {
if (map == null) {
out.writeInt(0);
return;
@@ -357,7 +404,7 @@ public class BinarySerializer {
}
private static Map<String, String> readStringStringMap(DataInputStream in)
throws IOException {
throws IOException {
int size = in.readInt();
Map<String, String> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
@@ -368,6 +415,122 @@ public class BinarySerializer {
return map;
}
// v5+: por macro va un id de tipo, luego nombre y tecla, y por último lo propio del tipo
// (líneas en las de chat, pares tecla/acción en las Keybind). los null (que pueden venir
// de un json antiguo) se tratan como chat / lista vacía
private static void writeMacros(DataOutputStream out, List<ClientConfig.MacroData> macros)
throws IOException {
if (macros == null) {
out.writeInt(0);
return;
}
out.writeInt(macros.size());
for (ClientConfig.MacroData macro : macros) {
boolean keybind = macro.type() == ClientConfig.MacroType.KEYBIND;
out.writeByte(keybind ? MACRO_KEYBIND : MACRO_CHAT);
writeString(out, macro.name());
out.writeInt(macro.key());
if (keybind) {
List<ClientConfig.KeyData> keys = macro.keys() == null ? List.of() : macro.keys();
out.writeInt(keys.size());
for (ClientConfig.KeyData k : keys) {
out.writeInt(k.key());
out.writeInt(k.action());
}
} else {
List<String> lines = macro.lines() == null ? List.of() : macro.lines();
out.writeInt(lines.size());
for (String line : lines) writeString(out, line);
}
}
}
private static List<ClientConfig.MacroData> readMacros(DataInputStream in) throws IOException {
int size = in.readInt();
List<ClientConfig.MacroData> macros = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
byte type = in.readByte();
String name = readString(in);
int key = in.readInt();
switch (type) {
case MACRO_CHAT -> {
int lineCount = in.readInt();
List<String> lines = new ArrayList<>(lineCount);
for (int j = 0; j < lineCount; j++) lines.add(readString(in));
macros.add(ClientConfig.MacroData.chat(name, key, lines));
}
case MACRO_KEYBIND -> {
int keyCount = in.readInt();
List<ClientConfig.KeyData> keys = new ArrayList<>(keyCount);
for (int j = 0; j < keyCount; j++) keys.add(new ClientConfig.KeyData(in.readInt(), in.readInt()));
macros.add(ClientConfig.MacroData.keybind(name, key, keys));
}
// no hay forma de saltarse una macro de un tipo que no conocemos (no se sabe
// cuánto ocupa), así que lo único seguro es fallar
default -> throw new IOException(I18nUtil.fmt("sputnik.config.error.invalidmacro",
"tipo de macro desconocido: %s", type));
}
}
return macros;
}
// v3 y v4: formato de antes de que hubiera tipos (nombre, tecla, líneas). eran todas de chat
private static List<ClientConfig.MacroData> readLegacyMacros(DataInputStream in) throws IOException {
int size = in.readInt();
List<ClientConfig.MacroData> macros = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
String name = readString(in);
int key = in.readInt();
int lineCount = in.readInt();
List<String> lines = new ArrayList<>(lineCount);
for (int j = 0; j < lineCount; j++) lines.add(readString(in));
macros.add(ClientConfig.MacroData.chat(name, key, lines));
}
return macros;
}
private static void writeFriendsMap(DataOutputStream out, Map<String, ClientConfig.FriendData> map)
throws IOException {
if (map == null) {
out.writeInt(0);
return;
}
out.writeInt(map.size());
for (Map.Entry<String, ClientConfig.FriendData> entry : map.entrySet()) {
writeString(out, entry.getKey());
ClientConfig.FriendData data = entry.getValue();
writeString(out, data.name());
writeString(out, data.nick());
out.writeLong(data.befriendedAt());
}
}
private static Map<String, ClientConfig.FriendData> readFriendsMap(DataInputStream in)
throws IOException {
int size = in.readInt();
Map<String, ClientConfig.FriendData> map = new HashMap<>(size);
for (int i = 0; i < size; i++) {
String uuid = readString(in);
String name = readString(in);
String nick = readString(in);
long befriendedAt = in.readLong();
map.put(uuid, new ClientConfig.FriendData(name, nick, befriendedAt));
}
return map;
}
private static Map<String, ClientConfig.FriendData> migrateFriendsMap(Map<String, String> legacy) {
Map<String, ClientConfig.FriendData> map = new HashMap<>(legacy.size());
long now = System.currentTimeMillis();
for (Map.Entry<String, String> entry : legacy.entrySet()) {
map.put(entry.getKey(), new ClientConfig.FriendData(entry.getValue(), entry.getValue(), now));
}
return map;
}
private static void writeString(DataOutputStream out, String str) throws IOException {
if (str == null) {
out.writeInt(-1);
@@ -4,8 +4,6 @@ import com.github.retucio.neutrino.EventListener;
import me.retucio.sputnik.event.sputnik.*;
import me.retucio.sputnik.module.setting.settings.*;
import me.retucio.sputnik.ui.screen.ClickGui;
import me.retucio.sputnik.ui.widgets.panels.FriendsPanel;
import me.retucio.sputnik.util.ChatUtil;
import java.io.Serial;
import java.io.Serializable;
@@ -17,7 +15,7 @@ import java.util.Map;
import static me.retucio.sputnik.Sputnik.EVENT_BUS;
import static me.retucio.sputnik.Sputnik.mc;
// clase donde se guardan las configuraciones temporalmente hasta escribirlas en camera_tweaks.json
// clase donde se guardan las configuraciones temporalmente hasta escribirlas en sputnik.dat
public class ClientConfig {
// nombre del módulo -> estado del módulo
@@ -44,22 +42,38 @@ public class ClientConfig {
// posición de la barra de búsqueda (x, y)
public int[] searchBarPosition = new int[] { 340, 16 };
// amigos: uuid -> nombre
public Map<String, String> friends = new HashMap<>();
// amigos: uuid -> FriendData (nombre, apodo, fecha)
public Map<String, FriendData> friends = new HashMap<>();
// macros, de cualquier tipo (ver MacroData)
public List<MacroData> macros = new ArrayList<>();
// perfil cargado actualmente, vacío si es la config. suelta
public String currentProfile = "";
// Set only when loading a config written with GLFW keycodes (pre-26.3).
public transient boolean legacyKeyCodes;
public ClientConfig() {
EVENT_BUS.subscribe(this);
}
// mientras se aplica una config. los cambios vienen de ella, así que reescribirla es
// como poco redundante, y al cambiar de perfil pisaba lo que aún no se había aplicado
private static boolean isApplying() {
return ConfigManager.INSTANCE != null && ConfigManager.INSTANCE.isApplying();
}
@EventListener
public void onToggleModule(ToggleModuleEvent event) {
if (isApplying()) return;
ConfigManager.INSTANCE.setModuleState(event.getModule());
}
@SuppressWarnings("rawtypes")
@EventListener
public void onUpdateSetting(UpdateSettingEvent event) {
if (mc == null || !event.shouldSave()) return;
if (mc == null || !event.shouldSave() || isApplying()) return;
// guardar ajustes con su respectivo tipo de valor, en formato "nombreMódulo:nombreAjuste"
Object value = null;
@@ -71,7 +85,8 @@ 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;
case FileSetting f: value = f.getValue(); break;
default: break;
}
@@ -104,9 +119,11 @@ public class ClientConfig {
ConfigManager.INSTANCE.markDirty();
}
// put y no replace: replace no hace nada si la clave no está todavía, así que mover un
// marco sin haberlo plegado antes (que es lo que crea la entrada) no se guardaba
@EventListener
public void onMoveModulePanel(ModulePanelEvent.Move event) {
extendablePanels.replace(event.getPanel().category.toString(), new PanelData(
extendablePanels.put(event.getPanel().category.toString(), new PanelData(
event.getPanel().getX(),
event.getPanel().getY(),
event.getPanel().isExtended()));
@@ -124,48 +141,69 @@ public class ClientConfig {
@EventListener
public void onMoveClientSettingsPanel(ClientSettingsPanelEvent.Move event) {
extendablePanels.replace("ajustes", new PanelData(
extendablePanels.put("ajustes", new PanelData(
ClickGui.INSTANCE.getClientSettingsPanel().getX(),
ClickGui.INSTANCE.getClientSettingsPanel().getY(),
ClickGui.INSTANCE.getClientSettingsPanel().isExtended()));
ConfigManager.INSTANCE.markDirty();
}
@EventListener
public void onExtendFriendsPanel(FriendPanelEvent.Extend event) {
extendablePanels.put("amigos", new PanelData(
ClickGui.INSTANCE.getFriendsPanel().getX(),
ClickGui.INSTANCE.getFriendsPanel().getY(),
ClickGui.INSTANCE.getFriendsPanel().isExtended()));
ConfigManager.INSTANCE.markDirty();
}
@EventListener
public void onMoveFriendsPanel(FriendPanelEvent.Move event) {
extendablePanels.replace("amigos", new PanelData(
ClickGui.INSTANCE.getFriendsPanel().getX(),
ClickGui.INSTANCE.getFriendsPanel().getY(),
ClickGui.INSTANCE.getFriendsPanel().isExtended()));
ConfigManager.INSTANCE.markDirty();
}
@EventListener
public void onAddFriend(FriendEvent.Add event) {
if (isApplying()) return;
String uuid = event.getFriend().getUuid().toString();
String name = event.getFriend().getName();
if (!friends.containsKey(uuid)) {
friends.put(uuid, name);
friends.put(uuid, new FriendData(
event.getFriend().getName(),
event.getFriend().getNick(),
event.getFriend().getBefriendedAt()));
}
ConfigManager.INSTANCE.markDirty();
}
@EventListener
public void onRemoveFriend(FriendEvent.Remove event) {
if (isApplying()) return;
friends.remove(event.getFriend().getUuid().toString());
ConfigManager.INSTANCE.markDirty();
}
@EventListener
public void onRenameFriend(FriendEvent.Rename event) {
if (isApplying()) return;
String uuid = event.getFriend().getUuid().toString();
FriendData existing = friends.get(uuid);
if (existing == null) return; // no debería pasar, pero porsiaca
friends.put(uuid, new FriendData(existing.name(), event.getFriend().getNick(), existing.befriendedAt()));
ConfigManager.INSTANCE.markDirty();
}
public record PanelData(int x, int y, boolean extended) implements Serializable {
@Serial private static final long serialVersionUID = 1L;
}
public record FriendData(String name, String nick, long befriendedAt) implements Serializable {
@Serial private static final long serialVersionUID = 1L;
}
public enum MacroType { CHAT, KEYBIND }
public record KeyData(int key, int action) implements Serializable {
@Serial private static final long serialVersionUID = 1L;
}
public record MacroData(MacroType type, String name, int key,
List<String> lines, List<KeyData> keys
) implements Serializable {
@Serial private static final long serialVersionUID = 2L;
public static MacroData chat(String name, int key, List<String> lines) {
return new MacroData(MacroType.CHAT, name, key, List.copyOf(lines), List.of());
}
public static MacroData keybind(String name, int key, List<KeyData> keys) {
return new MacroData(MacroType.KEYBIND, name, key, List.of(), List.copyOf(keys));
}
}
}
@@ -8,27 +8,34 @@ import me.retucio.sputnik.friend.FriendManager;
import me.retucio.sputnik.module.Module;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.client.ClientSettingsModule;
import me.retucio.sputnik.macro.Macro;
import me.retucio.sputnik.macro.MacroManager;
import me.retucio.sputnik.module.setting.*;
import me.retucio.sputnik.module.setting.settings.*;
import me.retucio.sputnik.ui.hud.HudElement;
import me.retucio.sputnik.ui.hud.HudRenderer;
import me.retucio.sputnik.ui.widgets.Panel;
import me.retucio.sputnik.ui.widgets.panels.FriendsPanel;
import me.retucio.sputnik.ui.widgets.panels.ExtendablePanel;
import me.retucio.sputnik.ui.widgets.panels.ModulePanel;
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 me.retucio.sputnik.util.KeyUtil;
import net.minecraft.network.chat.Component;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
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
@@ -40,8 +47,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 -> {
@@ -50,7 +58,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 ------------------
@@ -58,58 +69,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("error al serializar ajustes", 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("ajustes guardados");
} catch (IOException e) {
Sputnik.LOGGER.error("error al guardar ajustes", 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("el hilo de guardado no terminó a tiempo");
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("cargando ajustes...");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.loading", "cargando ajustes..."));
if (CONFIG_FILE.exists()) {
try {
config = BinarySerializer.readConfig(CONFIG_FILE);
Sputnik.LOGGER.info("ajustes cargados");
setConfig(BinarySerializer.readConfig(CONFIG_FILE));
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.loaded", "ajustes cargados"));
return;
} catch (IOException e) {
Sputnik.LOGGER.error("error cargando ajustes, intentando json", e);
Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.loading",
"error cargando ajustes, intentando json"), e);
}
}
@@ -117,35 +151,122 @@ 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("ajustes json cargados (migración)");
setConfig(gson.fromJson(reader, ClientConfig.class));
config.legacyKeyCodes = true;
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("error cargando json, usando defaults", e);
Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.config.error.json",
"error cargando json, usando defaults"), e);
}
}
// crear nuevo config
ensureConfig();
writeSnapshot(true);
}
// 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;
applying = true;
boolean migratedKeys = config.legacyKeyCodes;
try {
migrateLegacyKeyCodes();
// 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();
applyMacros();
applyHudElements();
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;
}
if (migratedKeys) writeSnapshot(true);
return true;
}
private void migrateLegacyKeyCodes() {
if (!config.legacyKeyCodes) return;
ClientSettingsPanel.clientSettings.getSettings().forEach(this::migrateLegacyKeySetting);
ModuleManager.INSTANCE.getModules().forEach(module ->
module.getSettings().forEach(this::migrateLegacyKeySetting));
BinarySerializer.migrateLegacyKeyCodes(config);
config.legacyKeyCodes = false;
}
private void migrateLegacyKeySetting(Setting<?> setting) {
if (!(setting instanceof KeySetting)) return;
String key = getSettingKey(setting);
Object value = config.settings.get(key);
if (value instanceof Integer code) {
config.settings.put(key, KeyUtil.fromLegacyGlfwKey(code));
} else if (value instanceof Double code) {
config.settings.put(key, KeyUtil.fromLegacyGlfwKey(code.intValue()));
}
}
// 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, y ClientConfig
// para no reescribir en la config. lo que se le está aplicando
public boolean isApplying() {
return applying;
}
// sustituye la config. viva por otra y la vuelca sobre el estado actual del cliente
public void replaceConfig(ClientConfig newConfig) {
applying = true;
try {
resetLiveState();
} finally {
applying = false;
}
setConfig(newConfig);
loaded = false;
applyConfig();
save();
}
// aplicar configuraciones cargadas
public void applyConfig() {
if (!shouldApply()) return;
// lo que no venga en la config. nueva tiene que quedarse en su valor por defecto, no en
// el que tuviera el perfil anterior
private void resetLiveState() {
if (ClickGui.INSTANCE != null) ClickGui.INSTANCE.closeAllSettingsPanels();
applyModuleStates();
applyModuleSettings();
applySettingsPanels();
applySearchBarPosition();
applyClientSettings();
applyExtendablePanels();
applyFriends();
for (Module module : ModuleManager.INSTANCE.getModules()) {
module.setEnabled(false);
module.getSettings().forEach(Setting::reset);
}
ClientSettingsPanel.clientSettings.getSettings().forEach(Setting::reset);
loaded = true;
Sputnik.LOGGER.info("ajustes aplicados");
for (Friend friend : new ArrayList<>(FriendManager.INSTANCE.getFriends()))
FriendManager.INSTANCE.remove(friend);
MacroManager.INSTANCE.setMacros(List.of());
}
// obtener configuración actual
@@ -202,16 +323,79 @@ public class ConfigManager {
markDirty();
}
public void setMacros(List<Macro> macros) {
ensureConfig();
config.macros = macros.stream()
.map(ConfigManager::toMacroData)
.toList();
markDirty();
}
public void setCurrentProfile(String name) {
ensureConfig();
config.currentProfile = name;
markDirty();
}
// ------------------ APLICAR AJUSTES ------------------
public void applyFriends() {
config.friends.forEach((uuid, name) -> {
Friend friend = new Friend(UUID.fromString(uuid));
friend.setName(name);
config.friends.forEach((uuid, data) -> {
Friend friend = new Friend(UUID.fromString(uuid), data.befriendedAt());
friend.setName(data.name());
friend.setNick(data.nick());
FriendManager.INSTANCE.add(friend);
});
Sputnik.LOGGER.info("amigos aplicados");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.friends",
"amigos aplicados"));
}
public void applyMacros() {
MacroManager.INSTANCE.setMacros(config.macros.stream()
.map(ConfigManager::fromMacroData)
.toList());
}
private static ClientConfig.MacroData toMacroData(Macro macro) {
return switch (macro) {
case Macro.Chat chat -> ClientConfig.MacroData.chat(
chat.getName(), chat.getKey(), chat.getLines());
case Macro.Keybind keybind -> ClientConfig.MacroData.keybind(
keybind.getName(), keybind.getKey(),
keybind.getKeys().stream()
.map(k -> new ClientConfig.KeyData(k.key(), k.action()))
.toList());
default -> throw new IllegalArgumentException(
"tipo de macro sin serializar: " + macro.getClass().getName());
};
}
private static Macro fromMacroData(ClientConfig.MacroData data) {
if (data.type() == ClientConfig.MacroType.KEYBIND) {
List<Macro.Keybind.Key> keys = data.keys() == null ? List.of() : data.keys().stream()
.map(k -> new Macro.Keybind.Key(k.key(), k.action()))
.toList();
return new Macro.Keybind(data.name(), data.key(), keys);
}
return new Macro.Chat(data.name(), data.key(), data.lines() == null ? List.of() : data.lines());
}
// el HudRenderer coloca sus elementos al construirse, pero al cambiar de perfil ya existen
public void applyHudElements() {
if (HudRenderer.INSTANCE == null) return;
for (HudElement element : HudRenderer.INSTANCE.getElements()) {
int[] position = config.hudPositions.get(element.getId());
if (position != null && position.length == 2) element.setPosition(position[0], position[1]);
else element.resetPosition();
Boolean visible = config.hudVisibilities.get(element.getId());
element.setVisible(visible == null || visible);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
@@ -231,7 +415,9 @@ 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("watafac queseso: {}", setting.getClass().getSimpleName());
case FileSetting fileSetting -> applyFileSetting(fileSetting, value);
default -> Sputnik.LOGGER.warn(I18nUtil.fmt("sputnik.config.warn.unknownsetting",
"watafac queseso: %s", setting.getClass().getSimpleName()));
}
}
@@ -241,14 +427,16 @@ public class ConfigManager {
module.setEnabled(config.moduleStates.get(module.getName()));
}
});
Sputnik.LOGGER.info("estados de módulos aplicados");
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("ajustes de módulos aplicados");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.modulesettings",
"ajustes de módulos aplicados"));
}
private void applySettingsPanels() {
@@ -256,14 +444,16 @@ public class ConfigManager {
Module module = ModuleManager.INSTANCE.getModuleByName(moduleName);
if (module == null) {
Sputnik.LOGGER.warn("\"null\" como módulo (\"{}\")", moduleName);
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("estados de marcos de ajustes aplicados");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.settingspanels",
"estados de marcos de ajustes aplicados"));
}
private void applySearchBarPosition() {
@@ -271,13 +461,15 @@ public class ConfigManager {
ClickGui.INSTANCE.getSearchBar().setX(config.searchBarPosition[0]);
ClickGui.INSTANCE.getSearchBar().setY(config.searchBarPosition[1]);
}
Sputnik.LOGGER.info("posición de la barra de búsqueda aplicada");
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("ajustes del cliente aplicados");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.clientsettings",
"ajustes del cliente aplicados"));
}
private void applyExtendablePanels() {
@@ -285,9 +477,9 @@ public class ConfigManager {
applyExtendablePanel(panel);
}
applyExtendablePanel(ClickGui.INSTANCE.getClientSettingsPanel());
applyExtendablePanel(ClickGui.INSTANCE.getFriendsPanel());
Sputnik.LOGGER.info("posiciones de marcos extendibles aplicadas");
Sputnik.LOGGER.info(I18nUtil.fmt("sputnik.config.info.extendablepanels",
"posiciones de marcos extendibles aplicadas"));
ClickGui.INSTANCE.refreshListButtons();
}
@@ -300,22 +492,13 @@ public class ConfigManager {
if (panel instanceof ClientSettingsPanel) {
panelData = config.extendablePanels.get("ajustes");
}
if (panel instanceof FriendsPanel) {
panelData = config.extendablePanels.get("amigos");
}
if (panelData != null) {
panel.setX(panelData.x());
panel.setY(panelData.y());
if (panel instanceof ModulePanel mPanel) {
mPanel.setExtended(panelData.extended());
}
if (panel instanceof ClientSettingsPanel sPanel) {
sPanel.setExtended(panelData.extended());
}
if (panel instanceof FriendsPanel fPanel) {
fPanel.setExtended(panelData.extended());
if (panel instanceof ExtendablePanel<?> ePanel) {
ePanel.setExtended(panelData.extended());
}
}
}
@@ -344,8 +527,8 @@ public class ConfigManager {
}
private static void applyStringSetting(StringSetting setting, Object value) {
if (value instanceof String) {
setting.setValue((String) value);
if (value instanceof String v) {
setting.setValue(v);
}
}
@@ -362,8 +545,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);
}
@@ -400,6 +589,12 @@ public class ConfigManager {
}
}
private static void applyFileSetting(FileSetting setting, Object value) {
if (value instanceof String v) {
setting.setValue(v);
}
}
private static <T> void applyOptionalProperty(Map<?, ?> map, String key, Class<T> type, Consumer<T> setter) {
Object value = map.get(key);
if (type.isInstance(value)) {
@@ -414,12 +609,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() {
@@ -428,7 +635,6 @@ public class ConfigManager {
public void markDirty() {
this.dirty = true;
this.lastDirtyTime = System.currentTimeMillis();
}
public boolean isDirty() {
@@ -442,10 +648,11 @@ public class ConfigManager {
if (!settings.autosave.getValue()) return;
long intervalMs = (long) (settings.autosaveInterval.getValue() * 1000);
if (System.currentTimeMillis() - lastDirtyTime < intervalMs) return;
if (System.currentTimeMillis() - lastSaveTime < intervalMs) return;
HudRenderer.INSTANCE.pushNotification("autoguardando...", "guardando configuración...");
HudRenderer.INSTANCE.pushNotification(
Component.translatable("sputnik.config.autosaving.title").getString(),
Component.translatable("sputnik.config.autosaving.desc").getString());
save();
dirty = false;
}
}
}
@@ -0,0 +1,99 @@
package me.retucio.sputnik.config;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.util.I18nUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class ProfileManager {
public static final int MAX_NAME_LENGTH = 32;
private static final File DIR = new File("sputnik/profiles");
private static final String EXTENSION = ".dat";
public static ProfileManager INSTANCE;
public List<String> getProfileNames() {
File[] files = DIR.listFiles((dir, name) -> name.endsWith(EXTENSION));
if (files == null) return List.of();
List<String> names = new ArrayList<>();
for (File file : files) {
String name = file.getName();
names.add(name.substring(0, name.length() - EXTENSION.length()));
}
names.sort(Comparator.naturalOrder());
return names;
}
public boolean exists(String name) {
return fileOf(name).exists();
}
public String getCurrent() {
return ConfigManager.INSTANCE.getConfig().currentProfile;
}
// vuelca la config. viva en el perfil y lo deja como actual
public boolean save(String name) {
if (!isValidName(name)) return false;
if (!DIR.exists() && !DIR.mkdirs()) {
Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.dir",
"no se pudo crear el directorio de perfiles"));
return false;
}
ConfigManager.INSTANCE.setCurrentProfile(name);
try {
BinarySerializer.writeConfig(ConfigManager.INSTANCE.getConfig(), fileOf(name));
return true;
} catch (IOException e) {
Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.saving",
"error al guardar el perfil \"%s\"", name), e);
return false;
}
}
public boolean load(String name) {
if (!exists(name)) return false;
ClientConfig profile;
try {
profile = BinarySerializer.readConfig(fileOf(name));
} catch (IOException e) {
Sputnik.LOGGER.error(I18nUtil.fmt("sputnik.profile.error.loading",
"error al cargar el perfil \"%s\"", name), e);
return false;
}
profile.currentProfile = name;
ConfigManager.INSTANCE.replaceConfig(profile);
return true;
}
public boolean delete(String name) {
if (!exists(name)) return false;
if (!fileOf(name).delete()) return false;
if (name.equals(getCurrent())) ConfigManager.INSTANCE.setCurrentProfile("");
return true;
}
public static boolean isValidName(String name) {
return name != null
&& !name.isBlank()
&& name.length() <= MAX_NAME_LENGTH
&& name.matches("[a-zA-Z0-9 _-]+");
}
private static File fileOf(String name) {
return new File(DIR, name + EXTENSION);
}
}
@@ -0,0 +1,21 @@
package me.retucio.sputnik.event.interact;
import com.github.retucio.neutrino.Event;
import net.minecraft.world.item.ItemStack;
import java.util.List;
public class UpdateInventoryEvent extends Event {
private final List<ItemStack> prev;
public UpdateInventoryEvent(List<ItemStack> prev) {
this.prev = prev;
}
public List<ItemStack> getPrev() {
return prev;
}
}
@@ -0,0 +1,24 @@
package me.retucio.sputnik.event.render;
import com.github.retucio.neutrino.Event;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
public class ApplyTransformationEvent extends Event {
private final ItemTransform transformation;
private final boolean leftHanded;
public ApplyTransformationEvent(ItemTransform transformation, boolean leftHanded) {
this.transformation = transformation;
this.leftHanded = leftHanded;
}
public ItemTransform getTransformation() {
return transformation;
}
public boolean isLeftHanded() {
return leftHanded;
}
}
@@ -0,0 +1,25 @@
package me.retucio.sputnik.event.render;
import com.github.retucio.neutrino.Event;
import com.mojang.blaze3d.vertex.PoseStack;
import me.retucio.sputnik.mixinterfaces.IEntityRenderState;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.state.ItemEntityRenderState;
import net.minecraft.world.entity.item.ItemEntity;
public class RenderItemEntityEvent extends Event {
public final ItemEntity itemEntity;
public final ItemEntityRenderState renderState;
public final PoseStack matrixStack;
public final SubmitNodeCollector renderCommandQueue;
public final int light;
public RenderItemEntityEvent(ItemEntityRenderState renderState, PoseStack matrixStack, SubmitNodeCollector renderCommandQueue, int light) {
this.itemEntity = (ItemEntity) ((IEntityRenderState) renderState).sputnik$getEntity();
this.renderState = renderState;
this.matrixStack = matrixStack;
this.renderCommandQueue = renderCommandQueue;
this.light = light;
}
}
@@ -3,7 +3,8 @@ package me.retucio.sputnik.event.sputnik;
import com.github.retucio.neutrino.Event;
import me.retucio.sputnik.friend.Friend;
// evento lanzado cuando se agrega o se elimina un amigo
// evento lanzado cuando se agrega, elimina o renombra un amigo
public class FriendEvent extends Event {
private final Friend friend;
@@ -23,4 +24,8 @@ public class FriendEvent extends Event {
public static class Remove extends FriendEvent {
public Remove(Friend friend) { super(friend); }
}
}
public static class Rename extends FriendEvent {
public Rename(Friend friend) { super(friend); }
}
}
@@ -1,9 +0,0 @@
package me.retucio.sputnik.event.sputnik;
import com.github.retucio.neutrino.Event;
public class FriendPanelEvent {
public static class Extend extends Event {} // al extenderlo / contraerlo
public static class Move extends Event {} // al cambiarlo de posición
}
@@ -1,6 +1,7 @@
package me.retucio.sputnik.friend;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
@@ -13,12 +14,20 @@ public class Friend {
private static final Minecraft mc = Minecraft.getInstance();
private String name;
private String nick;
private final UUID uuid;
private final long befriendedAt;
private boolean searchMatch;
public Friend(UUID uuid) {
this.uuid = uuid;
this.befriendedAt = System.currentTimeMillis();
}
public Friend(UUID uuid, long befriendedAt) {
this.uuid = uuid;
this.befriendedAt = befriendedAt;
}
public String getName() {
@@ -29,10 +38,27 @@ public class Friend {
this.name = name;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
// apodo si existe, si no el nombre real
public String getDisplayName() {
return (nick == null || nick.isBlank()) ? name : nick;
}
public UUID getUuid() {
return uuid;
}
public long getBefriendedAt() {
return befriendedAt;
}
public @Nullable Player getPlayer() {
if (mc.level == null) return null;
Entity entity = mc.level.getEntity(uuid);
@@ -40,6 +66,11 @@ public class Friend {
return player;
}
public @Nullable PlayerInfo getPlayerInfo() {
if (mc.getConnection() == null) return null;
return mc.getConnection().getPlayerInfo(uuid);
}
public boolean isSearchMatch() {
return searchMatch;
}
@@ -56,4 +87,4 @@ public class Friend {
NOT_BEFRIENDED,
NO_FRIENDS; // :(
}
}
}
@@ -29,6 +29,7 @@ public class FriendManager {
public void add(UUID uuid) {
Friend friend = new Friend(uuid);
friend.setName(friend.getPlayer().getName().getString());
friend.setNick(friend.getName());
add(friend);
}
@@ -61,6 +62,11 @@ public class FriendManager {
return Friend.Status.SUCCESS;
}
public void rename(Friend friend, String nick) {
friend.setNick(nick);
Sputnik.EVENT_BUS.post(new FriendEvent.Rename(friend));
}
public Friend get(UUID uuid) {
for (Friend friend : friends) {
if (friend.getUuid().equals(uuid)) {
@@ -0,0 +1,104 @@
package me.retucio.sputnik.macro;
import me.retucio.sputnik.util.ChatUtil;
import me.retucio.sputnik.util.KeyUtil;
import net.minecraft.client.Minecraft;
import com.mojang.blaze3d.platform.InputConstants;
import java.util.ArrayList;
import java.util.List;
public abstract class Macro {
protected String name;
protected int key;
protected Macro(String name, int key) {
this.name = name;
this.key = key;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getKey() { return key; }
public void setKey(int key) { this.key = key; }
private boolean searchMatch = true;
public boolean isSearchMatch() { return searchMatch; }
public void setSearchMatch(boolean searchMatch) { this.searchMatch = searchMatch; }
public abstract void run();
// cada línea se manda como si el usuario la hubiera escrito en el chat, así que valen
// comandos del mod, comandos del servidor y mensajes normales
public static class Chat extends Macro {
private final List<String> lines = new ArrayList<>();
public Chat(String name) {
this(name, InputConstants.UNKNOWN.getValue(), List.of());
}
public Chat(String name, int key, List<String> lines) {
super(name, key);
this.lines.addAll(lines);
}
@Override
public void run() {
if (Minecraft.getInstance().player == null) return;
for (String line : lines) {
if (!line.isBlank()) ChatUtil.simulateChatMessage(line);
}
}
public List<String> getLines() {
return lines;
}
public void setLines(List<String> lines) {
this.lines.clear();
this.lines.addAll(lines);
}
}
public static class Keybind extends Macro {
private final List<Key> keys = new ArrayList<>();
public Keybind(String name) {
this(name, InputConstants.UNKNOWN.getValue(), List.of());
}
public Keybind(String name, int key, List<Key> keys) {
super(name, key);
this.keys.addAll(keys);
}
@Override
public void run() {
for (Key k : keys) {
k.use();
}
}
public List<Key> getKeys() {
return keys;
}
public void setKeys(List<Key> keys) {
this.keys.clear();
this.keys.addAll(keys);
}
public record Key(int key, int action) {
public void use() {
KeyUtil.pressKey(key, action);
}
}
}
}
@@ -0,0 +1,68 @@
package me.retucio.sputnik.macro;
import me.retucio.sputnik.config.ConfigManager;
import net.minecraft.client.Minecraft;
import com.mojang.blaze3d.platform.InputConstants;
import java.util.ArrayList;
import java.util.List;
public class MacroManager {
public static MacroManager INSTANCE;
private final List<Macro> macros = new ArrayList<>();
// para evitar disparar un macro que presiona su propia tecla infinitamente
private boolean running = false;
public boolean add(Macro macro) {
if (getByName(macro.getName()) != null) return false;
macros.add(macro);
save();
return true;
}
public void remove(Macro macro) {
macros.remove(macro);
save();
}
public Macro getByName(String name) {
for (Macro macro : macros) {
if (macro.getName().equalsIgnoreCase(name)) return macro;
}
return null;
}
public boolean onKeyPress(int key) {
if (running || key == InputConstants.UNKNOWN.getValue() || Minecraft.getInstance().gui.screen() != null) return false;
running = true;
try {
boolean ran = false;
for (Macro macro : new ArrayList<>(macros)) {
if (macro.getKey() != key) continue;
macro.run();
ran = true;
}
return ran;
} finally {
running = false;
}
}
public void setMacros(List<Macro> macros) {
this.macros.clear();
this.macros.addAll(macros);
}
public void save() {
if (ConfigManager.INSTANCE != null) ConfigManager.INSTANCE.setMacros(macros);
}
public List<Macro> getMacros() {
return macros;
}
}
@@ -0,0 +1,16 @@
package me.retucio.sputnik.mixin.accessors;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ItemStackRenderState.class)
public interface ItemStackRenderStateAccessor {
@Accessor("activeLayerCount")
int getActiveLayerCount();
@Accessor("layers")
ItemStackRenderState.LayerRenderState[] getLayers();
}
@@ -0,0 +1,18 @@
package me.retucio.sputnik.mixin.accessors;
import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.geometry.ItemQuads;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ItemStackRenderState.LayerRenderState.class)
public interface LayerRenderStateAccessor {
@Accessor("itemTransform")
ItemTransform sputnik$getTransform();
@Accessor("quads")
ItemQuads sputnik$getQuads();
}
@@ -11,9 +11,7 @@ import me.retucio.sputnik.module.modules.misc.AntiInvis;
import me.retucio.sputnik.module.modules.movement.BoatFly;
import me.retucio.sputnik.module.modules.movement.Velocity;
import me.retucio.sputnik.module.modules.render.Nametags;
import me.retucio.sputnik.util.interfaces.IVec3;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -25,8 +23,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.arrow.AbstractArrow;
import net.minecraft.world.entity.vehicle.boat.AbstractBoat;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.joml.Vector3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -3,13 +3,15 @@ package me.retucio.sputnik.mixin.mixins.entity;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.render.EntityESP;
import me.retucio.sputnik.module.modules.render.EntityEsp;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -17,21 +19,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class EntityRendererMixin {
@Unique
private EntityESP esp;
private EntityEsp esp;
@Inject(method = "<init>", at = @At("TAIL"))
private void getModules(CallbackInfo ci) {
esp = ModuleManager.INSTANCE.getModuleByClass(EntityESP.class);
esp = ModuleManager.INSTANCE.getModuleByClass(EntityEsp.class);
}
@ModifyExpressionValue(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;shouldEntityAppearGlowing(Lnet/minecraft/world/entity/Entity;)Z"))
private boolean renderGlowESP(boolean original, @Local(argsOnly = true) Entity entity) {
return (esp.isEnabled() && esp.mode.is(EntityESP.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) || original;
return (esp.isEnabled() && esp.mode.is(EntityEsp.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) || original;
}
@ModifyExpressionValue(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getTeamColor()I"))
private int setGlowESPColor(int original, @Local(argsOnly = true) Entity entity) {
if (esp.isEnabled() && esp.mode.is(EntityESP.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) return esp.glowColor.getRGB();
if (esp.isEnabled() && esp.mode.is(EntityEsp.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) return esp.glowColor.getRgb();
return original;
}
}
@@ -8,6 +8,7 @@ import me.retucio.sputnik.module.modules.movement.Velocity;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.Vec3;
@@ -61,8 +62,8 @@ public abstract class LivingEntityMixin {
if (step.isEnabled()) cir.setReturnValue(step.height.getFloatValue());
}
@Inject(method = "knockback", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setDeltaMovement(DDD)V"), cancellable = true)
private void onTakeKb(double strength, double x, double z, CallbackInfo ci, @Local(name = "deltaMovement") Vec3 dm, @Local(name = "deltaVector") Vec3 dv) {
@Inject(method = "knockback(DDDLnet/minecraft/world/damagesource/DamageSource;FZ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setDeltaMovement(DDD)V"), cancellable = true)
private void onTakeKb(double strength, double x, double z, DamageSource source, float damage, boolean comesFromEffect, CallbackInfo ci, @Local(name = "deltaMovement") Vec3 dm, @Local(name = "deltaVector") Vec3 dv) {
Velocity velocity = ModuleManager.INSTANCE.getModuleByClass(Velocity.class);
if (!velocity.isEnabled() || velocity.hits.getValue()) return;
boolean onGround = ((LivingEntity) (Object) this).onGround();
@@ -5,10 +5,10 @@ import me.retucio.sputnik.event.network.ReceiveMessageEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.misc.ChatPlus;
import me.retucio.sputnik.util.interfaces.IChatComponent;
import me.retucio.sputnik.util.interfaces.IGuiMessage;
import me.retucio.sputnik.util.interfaces.IGuiMessageLine;
import me.retucio.sputnik.util.interfaces.IChatListener;
import me.retucio.sputnik.mixinterfaces.IChatComponent;
import me.retucio.sputnik.mixinterfaces.IGuiMessage;
import me.retucio.sputnik.mixinterfaces.IGuiMessageLine;
import me.retucio.sputnik.mixinterfaces.IChatListener;
import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.client.multiplayer.chat.GuiMessage;
@@ -83,7 +83,7 @@ public abstract class ChatComponentMixin implements IChatComponent {
@ModifyExpressionValue(method = "addMessageToDisplayQueue", at = @At(value = "NEW", target = "(Lnet/minecraft/client/multiplayer/chat/GuiMessage;Lnet/minecraft/util/FormattedCharSequence;Z)Lnet/minecraft/client/multiplayer/chat/GuiMessage$Line;"))
private GuiMessage.Line onAddMessage_modifyChatHudLineVisible(GuiMessage.Line line, @Local(name = "i") int i) {
IChatListener iListener = (IChatListener) Sputnik.mc.getChatListener();
IChatListener iListener = (IChatListener) Sputnik.mc.gui.chatListener();
IGuiMessageLine iLine = (IGuiMessageLine) (Object) line;
iLine.sputnik$setSender(iListener.sputnik$getSender());
@@ -94,7 +94,7 @@ public abstract class ChatComponentMixin implements IChatComponent {
@ModifyExpressionValue(method = "addMessage", at = @At(value = "NEW", target = "(ILnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/MessageSignature;Lnet/minecraft/client/multiplayer/chat/GuiMessageSource;Lnet/minecraft/client/multiplayer/chat/GuiMessageTag;)Lnet/minecraft/client/multiplayer/chat/GuiMessage;"))
private GuiMessage onAddMessage_modifyChatHudLine(GuiMessage line) {
IChatListener iListener = (IChatListener) Sputnik.mc.getChatListener();
IChatListener iListener = (IChatListener) Sputnik.mc.gui.chatListener();
((IGuiMessage) (Object) line).sputnik$setSender(iListener.sputnik$getSender());
return line;
}
@@ -1,7 +1,7 @@
package me.retucio.sputnik.mixin.mixins.hud;
import com.mojang.authlib.GameProfile;
import me.retucio.sputnik.util.interfaces.IGuiMessageLine;
import me.retucio.sputnik.mixinterfaces.IGuiMessageLine;
import net.minecraft.client.multiplayer.chat.GuiMessage;
import net.minecraft.util.FormattedCharSequence;
import org.spongepowered.asm.mixin.Final;
@@ -1,7 +1,7 @@
package me.retucio.sputnik.mixin.mixins.hud;
import com.mojang.authlib.GameProfile;
import me.retucio.sputnik.util.interfaces.IGuiMessage;
import me.retucio.sputnik.mixinterfaces.IGuiMessage;
import net.minecraft.client.multiplayer.chat.GuiMessage;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Final;
@@ -1,107 +1,21 @@
package me.retucio.sputnik.mixin.mixins.hud;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.render.Render2DEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.camera.CrosshairPlus;
import me.retucio.sputnik.module.modules.misc.ChatPlus;
import me.retucio.sputnik.module.modules.camera.Freecam;
import me.retucio.sputnik.module.modules.render.NoRender;
import me.retucio.sputnik.ui.hud.HudRenderer;
import net.minecraft.client.DeltaTracker;
import me.retucio.sputnik.event.interact.OpenScreenEvent;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.resources.Identifier;
import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArgs;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
// lo del hud se mudó a HudMixin; Gui ya sólo lleva las pantallas
@Mixin(Gui.class)
public abstract class GuiMixin {
@Unique
NoRender noRender;
@Inject(method = "<init>", at = @At("TAIL"))
private void getModules(CallbackInfo ci) {
noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.class);
}
@Inject(method = "extractRenderState", at = @At("TAIL"))
private void onRenderHud(GuiGraphicsExtractor gui, DeltaTracker dt, CallbackInfo ci) {
Sputnik.EVENT_BUS.post(new Render2DEvent(gui, dt));
}
// norender
@Inject(method = "extractScoreboardSidebar", at = @At("HEAD"), cancellable = true)
private void noRenderScoreboard(CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.scoreboard.getValue()) ci.cancel();
}
@Inject(method = "extractTitle", at = @At("HEAD"), cancellable = true)
private void noRenderTitles(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.titles.getValue()) ci.cancel();
}
@Inject(method = "extractConfusionOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderNausea(GuiGraphicsExtractor graphics, float strength, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.nauseaEffect.getValue()) ci.cancel();
}
@Inject(method = "extractSpyglassOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderSpyglass(GuiGraphicsExtractor graphics, float scale, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.spyglassOverlay.getValue()) ci.cancel();
}
@Inject(method = "extractPortalOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderPortal(GuiGraphicsExtractor graphics, float alpha, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.portalOverlay.getValue()) ci.cancel();
}
@Inject(method = "extractTextureOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderPumpkinBlur(GuiGraphicsExtractor graphics, Identifier texture, float alpha, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.pumpkinOverlay.getValue() && texture.getPath().equals("textures/misc/pumpkinblur.png")) ci.cancel();
}
// otros
@Inject(method = "extractRenderState", at = @At("RETURN"))
private void renderHUD(GuiGraphicsExtractor gui, DeltaTracker dt, CallbackInfo ci) {
HudRenderer.INSTANCE.render(gui, dt);
}
@Redirect(method = "extractCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;blitSprite(Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/resources/Identifier;IIII)V", ordinal = 0))
private void onRenderCrosshair(GuiGraphicsExtractor gui, RenderPipeline pipeline, Identifier id, int x, int y, int width, int height) {
// dangerous abbreviation
CrosshairPlus cp = ModuleManager.INSTANCE.getModuleByClass(CrosshairPlus.class);
int color = cp.isEnabled() ? cp.color.getRGB() : -1;
int w = cp.isEnabled() ? cp.width.getIntValue() : width;
int h = cp.isEnabled() ? cp.height.getIntValue() : height;
gui.blitSprite(pipeline, id, (gui.guiWidth() - w) / 2, (gui.guiHeight() - h) / 2, w, h, color);
}
@ModifyExpressionValue(method = "extractCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/CameraType;isFirstPerson()Z"))
private boolean alwaysRenderCrosshairInFreecam(boolean firstPerson) {
return ModuleManager.INSTANCE.getModuleByClass(Freecam.class).isEnabled() || firstPerson;
}
@Inject(method = "onDisconnected", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/ChatComponent;clearMessages(Z)V"), cancellable = true)
private void onClear(CallbackInfo ci) {
ChatPlus chatPlus = ModuleManager.INSTANCE.getModuleByClass(ChatPlus.class);
if (chatPlus.isEnabled() && chatPlus.keepHistory.getValue()) ci.cancel();
@Inject(method = "setScreen", at = @At("HEAD"), cancellable = true)
private void onOpenScreen(Screen screen, CallbackInfo ci) {
OpenScreenEvent event = Sputnik.EVENT_BUS.post(new OpenScreenEvent(screen));
if (event.isCancelled()) ci.cancel();
}
}
@@ -0,0 +1,104 @@
package me.retucio.sputnik.mixin.mixins.hud;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.render.Render2DEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.camera.CrosshairPlus;
import me.retucio.sputnik.module.modules.misc.ChatPlus;
import me.retucio.sputnik.module.modules.camera.Freecam;
import me.retucio.sputnik.module.modules.render.NoRender;
import me.retucio.sputnik.ui.hud.HudRenderer;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.Hud;
import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
// 26.2 partió Gui en Gui (pantallas) y Hud (el hud en sí); todo esto vivía en GuiMixin
@Mixin(Hud.class)
public abstract class HudMixin {
@Unique
NoRender noRender;
@Inject(method = "<init>", at = @At("TAIL"))
private void getModules(CallbackInfo ci) {
noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.class);
}
@Inject(method = "extractRenderState", at = @At("TAIL"))
private void onRenderHud(GuiGraphicsExtractor gui, DeltaTracker dt, CallbackInfo ci) {
Sputnik.EVENT_BUS.post(new Render2DEvent(gui, dt));
}
// norender
@Inject(method = "extractScoreboardSidebar", at = @At("HEAD"), cancellable = true)
private void noRenderScoreboard(CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.scoreboard.getValue()) ci.cancel();
}
@Inject(method = "extractTitle", at = @At("HEAD"), cancellable = true)
private void noRenderTitles(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.titles.getValue()) ci.cancel();
}
@Inject(method = "extractConfusionOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderNausea(GuiGraphicsExtractor graphics, float strength, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.nauseaEffect.getValue()) ci.cancel();
}
@Inject(method = "extractSpyglassOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderSpyglass(GuiGraphicsExtractor graphics, float scale, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.spyglassOverlay.getValue()) ci.cancel();
}
@Inject(method = "extractPortalOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderPortal(GuiGraphicsExtractor graphics, float alpha, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.portalOverlay.getValue()) ci.cancel();
}
@Inject(method = "extractTextureOverlay", at = @At("HEAD"), cancellable = true)
private void noRenderPumpkinBlur(GuiGraphicsExtractor graphics, Identifier texture, float alpha, CallbackInfo ci) {
if (noRender.isEnabled() && !noRender.pumpkinOverlay.getValue() && texture.getPath().equals("textures/misc/pumpkinblur.png")) ci.cancel();
}
// otros
@Inject(method = "extractRenderState", at = @At("RETURN"))
private void renderHUD(GuiGraphicsExtractor gui, DeltaTracker dt, CallbackInfo ci) {
HudRenderer.INSTANCE.render(gui, dt);
}
@Redirect(method = "extractCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;blitSprite(Lcom/mojang/renderpearl/api/pipeline/RenderPipeline;Lnet/minecraft/resources/Identifier;IIII)V", ordinal = 0))
private void onRenderCrosshair(GuiGraphicsExtractor gui, RenderPipeline pipeline, Identifier id, int x, int y, int width, int height) {
// dangerous abbreviation
CrosshairPlus cp = ModuleManager.INSTANCE.getModuleByClass(CrosshairPlus.class);
int color = cp.isEnabled() ? cp.color.getRgb() : -1;
int w = cp.isEnabled() ? cp.width.getIntValue() : width;
int h = cp.isEnabled() ? cp.height.getIntValue() : height;
gui.blitSprite(pipeline, id, (gui.guiWidth() - w) / 2, (gui.guiHeight() - h) / 2, w, h, color);
}
@ModifyExpressionValue(method = "extractCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/CameraType;isFirstPerson()Z"))
private boolean alwaysRenderCrosshairInFreecam(boolean firstPerson) {
return ModuleManager.INSTANCE.getModuleByClass(Freecam.class).isEnabled() || firstPerson;
}
@Inject(method = "onDisconnected", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/components/ChatComponent;clearMessages(Z)V"), cancellable = true)
private void onClear(CallbackInfo ci) {
ChatPlus chatPlus = ModuleManager.INSTANCE.getModuleByClass(ChatPlus.class);
if (chatPlus.isEnabled() && chatPlus.keepHistory.getValue()) ci.cancel();
}
}
@@ -2,6 +2,7 @@ package me.retucio.sputnik.mixin.mixins.io;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.input.KeyEvent;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyboardHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -14,9 +15,9 @@ public abstract class KeyboardHandlerMixin {
@Inject(method = "keyPress", at = @At("HEAD"), cancellable = true)
private void onKeyPress(long handle, int action, net.minecraft.client.input.KeyEvent input, CallbackInfo ci) {
if (input.key() == -1) return;
if (input.key() == InputConstants.UNKNOWN.getValue() || input.key() == -1) return;
Sputnik.INSTANCE.onInput(input.key(), action);
KeyEvent event = Sputnik.EVENT_BUS.post(new KeyEvent(input.key(), input.scancode(), action));
KeyEvent event = Sputnik.EVENT_BUS.post(new KeyEvent(input.key(), input.keycode(), action));
if (event.isCancelled()) ci.cancel();
}
}
@@ -16,7 +16,10 @@ public abstract class MouseHandlerMixin {
@Inject(method = "onButton", at = @At("HEAD"), cancellable = true)
private void onMouseButton(long handle, MouseButtonInfo input, int action, CallbackInfo ci) {
Sputnik.INSTANCE.onInput(input.button(), action);
if (Sputnik.INSTANCE.onMouseInput(input.button(), action)) {
ci.cancel();
return;
}
MouseClickEvent event = Sputnik.EVENT_BUS.post(new MouseClickEvent(action, input.button()));
if (event.isCancelled()) ci.cancel();
}
@@ -5,6 +5,7 @@ import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.misc.ScreenshotPlus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -19,14 +20,14 @@ import java.util.function.Consumer;
@Mixin(Screenshot.class)
public abstract class ScreenshotMixin {
@Inject(method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"), cancellable = true)
private static void sendActionsMessage(NativeImage image, File file, Consumer<?> callback, CallbackInfo ci) {
@Inject(method = "lambda$grab$3", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"), cancellable = true)
private static void sendActionsMessage(NativeImage image, File file, Consumer<Component> callback, CallbackInfo ci) {
ScreenshotPlus screenshotPlus = getScreenshotPlus();
if (screenshotPlus.isEnabled()) ci.cancel();
}
@Inject(method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;writeToFile(Ljava/io/File;)V"), cancellable = true)
private static void interceptScreenshotFile(NativeImage image, File file, Consumer<?> callback, CallbackInfo ci) throws IOException {
@Inject(method = "lambda$grab$3", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;writeToFile(Ljava/io/File;)V"), cancellable = true)
private static void interceptScreenshotFile(NativeImage image, File file, Consumer<Component> callback, CallbackInfo ci) throws IOException {
ScreenshotPlus screenshotPlus = getScreenshotPlus();
// aunque el módulo esté apagado, pasar las referencias para poder usar los comandos para las capturas
@@ -0,0 +1,36 @@
package me.retucio.sputnik.mixin.mixins.item;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.render.RenderItemEntityEvent;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.ItemEntityRenderer;
import net.minecraft.client.renderer.entity.state.ItemEntityRenderState;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemEntityRenderer.class)
public class ItemEntityRendererMixin {
@Inject(method = "submit(Lnet/minecraft/client/renderer/entity/state/ItemEntityRenderState;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;Lnet/minecraft/client/renderer/state/level/CameraRenderState;)V", at = @At("HEAD"), cancellable = true)
private void onSubmit(ItemEntityRenderState state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera, CallbackInfo ci) {
RenderItemEntityEvent event = Sputnik.EVENT_BUS.post(
new RenderItemEntityEvent(
state,
poseStack,
submitNodeCollector,
state.lightCoords
)
);
if (event.isCancelled()) {
ci.cancel();
}
}
}
@@ -27,13 +27,13 @@ public abstract class ItemMixin {
if (stack.getItem() instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock) {
if (ShulkerPeek.isShulkerEmpty(stack)) {
builder.accept(Component.literal("vacío").withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC));
builder.accept(Component.translatable("sputnik.module.shulkerpeek.empty")
.withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC));
} else {
builder.accept(
Component.literal("mantén ")
.append(Component.literal(shulkerPeek.previewKey.getKeyName())
.withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD))
.append(" para previsualizar")
Component.translatable("sputnik.module.shulkerpeek.holdtopreview",
Component.literal(shulkerPeek.previewKey.getKeyName())
.withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD))
.withStyle(ChatFormatting.GRAY)
);
}
@@ -0,0 +1,21 @@
package me.retucio.sputnik.mixin.mixins.item;
import com.mojang.blaze3d.vertex.PoseStack;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.render.ApplyTransformationEvent;
import net.minecraft.client.resources.model.cuboid.ItemTransform;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemTransform.class)
public abstract class ItemTransformMixin {
@Inject(method = "apply", at = @At("HEAD"), cancellable = true)
private void onApply(boolean applyLeftHandFix, PoseStack.Pose pose, CallbackInfo ci) {
ApplyTransformationEvent event = Sputnik.EVENT_BUS.post(new ApplyTransformationEvent((ItemTransform) (Object) this, applyLeftHandFix));
if (event.isCancelled()) {
ci.cancel();
}
}
}
@@ -3,16 +3,14 @@ package me.retucio.sputnik.mixin.mixins.misc;
import com.llamalad7.mixinextras.sugar.Local;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.network.JoinWorldEvent;
import me.retucio.sputnik.event.interact.OpenScreenEvent;
import me.retucio.sputnik.event.ShutdownEvent;
import me.retucio.sputnik.event.TickEvent;
import me.retucio.sputnik.event.interact.UseItemEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.camera.Freecam;
import me.retucio.sputnik.module.modules.player.FastUse;
import me.retucio.sputnik.util.interfaces.IVec3;
import me.retucio.sputnik.mixinterfaces.IVec3;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
@@ -38,9 +36,6 @@ public abstract class MinecraftMixin {
// eventos
@Shadow @Nullable
public Screen screen;
@Shadow
public abstract @Nullable Entity getCameraEntity();
@@ -63,12 +58,6 @@ public abstract class MinecraftMixin {
Sputnik.EVENT_BUS.post(new ShutdownEvent());
}
@Inject(method = "setScreen", at = @At("HEAD"), cancellable = true)
private void onOpenScreen(Screen screen, CallbackInfo ci) {
OpenScreenEvent event = Sputnik.EVENT_BUS.post(new OpenScreenEvent(screen));
if (event.isCancelled()) ci.cancel();
}
@Inject(method = "setLevel", at = @At("HEAD"), cancellable = true)
private void onJoinWorld(ClientLevel level, CallbackInfo ci) {
JoinWorldEvent event = Sputnik.EVENT_BUS.post(new JoinWorldEvent(level));
@@ -0,0 +1,33 @@
package me.retucio.sputnik.mixin.mixins.misc;
import me.retucio.sputnik.util.Lists;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
import org.jspecify.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Holder.Reference.class)
public abstract class ReferenceMixin<T> {
@Shadow
private @Nullable DataComponentMap components;
@Shadow
private @Nullable T value;
@Inject(method = "bindComponents", at = @At("TAIL"))
private void onBindComponents(DataComponentMap components, CallbackInfo ci) {
if (this.components == null || !this.components.has(DataComponents.FOOD)) return;
if (!(this.value instanceof Item item)) return;
Lists.foodList.add(item);
Lists.foodNames.put(item, BuiltInRegistries.ITEM.getKey(item).toLanguageKey());
}
}
@@ -1,6 +1,6 @@
package me.retucio.sputnik.mixin.mixins.misc;
import me.retucio.sputnik.util.interfaces.IVec3;
import me.retucio.sputnik.mixinterfaces.IVec3;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -1,7 +1,7 @@
package me.retucio.sputnik.mixin.mixins.network;
import com.mojang.authlib.GameProfile;
import me.retucio.sputnik.util.interfaces.IChatListener;
import me.retucio.sputnik.mixinterfaces.IChatListener;
import net.minecraft.client.multiplayer.chat.ChatListener;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.Component;
@@ -4,7 +4,7 @@ import me.retucio.sputnik.mixin.accessors.PackConfirmScreenAccessor;
import me.retucio.sputnik.mixin.accessors.PendingRequestAccessor;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.network.RPackBypass;
import me.retucio.sputnik.util.interfaces.IConfirmScreen;
import me.retucio.sputnik.mixinterfaces.IConfirmScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ClientCommonPacketListenerImpl;
@@ -46,7 +46,7 @@ public abstract class ClientCommonPacketListenerImplMixin {
final Screen screen = cir.getReturnValue();
((IConfirmScreen) screen).sputnik$setBypassAction(() -> {
minecraft.setScreen(((PackConfirmScreenAccessor) screen).getParentScreen());
minecraft.gui.setScreen(((PackConfirmScreenAccessor) screen).getParentScreen());
if (serverData != null) {
serverData.setResourcePackStatus(bypassPack.getStatus());
minecraft.getDownloadedPackSource().allowServerPacks();
@@ -3,18 +3,13 @@ package me.retucio.sputnik.mixin.mixins.network;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import me.retucio.sputnik.command.CommandManager;
import me.retucio.sputnik.event.network.SendMessageEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.render.NoRender;
import me.retucio.sputnik.util.ChatUtil;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static me.retucio.sputnik.Sputnik.*;
@@ -48,14 +43,7 @@ public abstract class ClientPacketListenerMixin {
ChatUtil.error(e.getMessage());
}
mc.gui.getChat().addRecentChat(content);
mc.gui.hud.getChat().addRecentChat(content);
ci.cancel();
}
@Redirect(method = "handleEntityEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;displayItemActivation(Lnet/minecraft/world/item/ItemStack;)V"))
private void noRenderTotemPop(GameRenderer instance, ItemStack itemStack) {
NoRender noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.class);
if (!noRender.isEnabled() || noRender.totemPop.getValue())
mc.gameRenderer.displayItemActivation(itemStack);
}
}
@@ -1,17 +1,17 @@
package me.retucio.sputnik.mixin.mixins.network;
import com.mojang.authlib.yggdrasil.YggdrasilUserApiService;
import com.mojang.authlib.yggdrasil.response.UserAttributesResponse;
import com.mojang.authlib.services.MinecraftServicesUserApiService;
import com.mojang.authlib.services.response.UserAttributesResponse;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
// código robado de: https://github.com/kb-1000/no-telemetry
@Mixin(YggdrasilUserApiService.class)
@Mixin(MinecraftServicesUserApiService.class)
public abstract class YggdrasilUserApiServiceMixin {
@Redirect(method = "fetchProperties", at = @At(value = "INVOKE", target = "Lcom/mojang/authlib/yggdrasil/response/UserAttributesResponse$Privileges;getTelemetry()Z", remap = false), remap = false, require = 0)
@Redirect(method = "fetchProperties", at = @At(value = "INVOKE", target = "Lcom/mojang/authlib/services/response/UserAttributesResponse$Privileges;getTelemetry()Z", remap = false), remap = false, require = 0)
private boolean getTelemetry(UserAttributesResponse.Privileges instance) {
return false;
}
@@ -0,0 +1,27 @@
package me.retucio.sputnik.mixin.mixins.player;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.interact.UpdateInventoryEvent;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Inventory.class)
public abstract class InventoryMixin {
@Shadow @Final
private NonNullList<ItemStack> items;
@Inject(method = "setChanged", at = @At("HEAD"))
private void onChange(CallbackInfo ci) {
Sputnik.EVENT_BUS.post(new UpdateInventoryEvent(items));
}
}
@@ -5,14 +5,12 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.world.NoMiningInterruptions;
import me.retucio.sputnik.module.modules.inventory.PortalGUI;
import me.retucio.sputnik.module.modules.inventory.PortalGui;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -28,9 +26,9 @@ public abstract class LocalPlayerMixin {
throw new UnsupportedOperationException("Implemented via mixin");
}
@ModifyExpressionValue(method = "handlePortalTransitionEffect", at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;", opcode = Opcodes.GETFIELD))
@ModifyExpressionValue(method = "handlePortalTransitionEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;screen()Lnet/minecraft/client/gui/screens/Screen;"))
private Screen allowScreensInPortals(Screen original) {
if (ModuleManager.INSTANCE.getModuleByClass(PortalGUI.class).isEnabled()) return null;
if (ModuleManager.INSTANCE.getModuleByClass(PortalGui.class).isEnabled()) return null;
return original;
}
@@ -1,5 +1,6 @@
package me.retucio.sputnik.mixin.mixins.render;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.render.GetFOVEvent;
@@ -78,6 +79,13 @@ public abstract class CameraMixin {
if (freecam.isEnabled()) this.detached = true;
}
// el culling inteligente se desactiva estando en espectador dentro de un bloque sólido,
// así el terreno se sigue renderizando con la cámara metida en bloques
@ModifyExpressionValue(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;isSpectator()Z"))
private boolean noSmartCullFreecam(boolean original) {
return freecam.isEnabled() || original;
}
@ModifyArgs(method = "alignWithEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
private void onUpdateSetPosArgs(Args args) {
if (freecam.isEnabled()) {
@@ -15,7 +15,7 @@ public abstract class EntityHitboxDebugRendererMixin {
@ModifyVariable(method = "showHitboxes", at = @At("STORE"), name = "mainColor")
private int modifyHitboxColor(int original) {
Hitboxes hitboxes = ModuleManager.INSTANCE.getModuleByClass(Hitboxes.class);
if (hitboxes.isEnabled()) return hitboxes.color.getRGB();
if (hitboxes.isEnabled()) return hitboxes.color.getRgb();
return original;
}
@@ -0,0 +1,22 @@
package me.retucio.sputnik.mixin.mixins.render;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import me.retucio.sputnik.mixinterfaces.IEntityRenderState;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(EntityRenderDispatcher.class)
public abstract class EntityRenderDispatcherMixin {
@ModifyExpressionValue(
method = "extractEntity(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/entity/state/EntityRenderState;",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderer;createRenderState(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/entity/state/EntityRenderState;")
)
private <E extends Entity> EntityRenderState getAndUpdateRenderState$setEntity(EntityRenderState state, E entity, float partialTicks) {
((IEntityRenderState) state).sputnik$setEntity(entity);
return state;
}
}
@@ -0,0 +1,26 @@
package me.retucio.sputnik.mixin.mixins.render;
import me.retucio.sputnik.mixinterfaces.IEntityRenderState;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(EntityRenderState.class)
public abstract class EntityRenderStateMixin implements IEntityRenderState {
@Unique
private Entity entity;
@Override
@Nullable
public Entity sputnik$getEntity() {
return entity;
}
@Override
public void sputnik$setEntity(Entity entity) {
this.entity = entity;
}
}
@@ -4,6 +4,7 @@ import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.render.GlintPlus;
import net.minecraft.client.renderer.entity.layers.EquipmentLayerRenderer;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.resources.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@@ -12,9 +13,9 @@ import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(EquipmentLayerRenderer.class)
public abstract class EquipmentLayerRendererMixin {
@Redirect(method = "renderLayers(Lnet/minecraft/client/resources/model/EquipmentClientInfo$LayerType;Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/resources/Identifier;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;armorEntityGlint()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private RenderType redirectArmorEntityGlint() {
@Redirect(method = "renderLayers(Lnet/minecraft/client/resources/model/EquipmentClientInfo$LayerType;Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/resources/Identifier;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;armorCutoutNoCullGlint(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private RenderType redirectArmorEntityGlint(Identifier texture) {
GlintPlus glintPlus = ModuleManager.INSTANCE.getModuleByClass(GlintPlus.class);
return glintPlus.getArmorEntityGlint();
return glintPlus.getArmorEntityGlint(texture);
}
}
}
@@ -6,16 +6,20 @@ import me.retucio.sputnik.event.render.GetFOVEvent;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.camera.Freecam;
import me.retucio.sputnik.module.modules.misc.UnfocusedCpu;
import me.retucio.sputnik.module.modules.render.Lsd;
import me.retucio.sputnik.module.modules.render.NoRender;
import me.retucio.sputnik.module.modules.camera.Zoom;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.resource.CrossFrameResourcePool;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.FirstPersonHandsAndItemsRenderer;
import net.minecraft.client.renderer.state.level.CameraRenderState;
import net.minecraft.client.renderer.state.level.PlayerRenderState;
import net.minecraft.client.renderer.item.ItemModelResolver;
import com.mojang.renderpearl.api.textures.GpuTextureView;
import net.minecraft.client.resources.model.ModelManager;
import org.joml.Matrix4fc;
@@ -39,19 +43,32 @@ public abstract class GameRendererMixin {
@Unique Zoom zoom;
@Unique Freecam freecam;
@Unique NoRender noRender;
@Unique Lsd lsd;
@Shadow @Final
private Minecraft minecraft;
@Shadow @Final
private RenderTarget mainRenderTarget;
@Shadow @Final
private CrossFrameResourcePool resourcePool;
@Inject(method = "<init>", at = @At("TAIL"))
private void getModules(Minecraft minecraft, ItemInHandRenderer itemInHandRenderer, RenderBuffers renderBuffers, ModelManager modelManager, CallbackInfo ci) {
private void getModules(Minecraft minecraft, FirstPersonHandsAndItemsRenderer itemInHandRenderer, ModelManager modelManager, ItemModelResolver itemModelResolver, CallbackInfo ci) {
zoom = ModuleManager.INSTANCE.getModuleByClass(Zoom.class);;
freecam = ModuleManager.INSTANCE.getModuleByClass(Freecam.class);
noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.class);
lsd = ModuleManager.INSTANCE.getModuleByClass(Lsd.class);
}
@Inject(method = "render", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/client/renderer/LevelRenderer;blitEntityOutline()V"))
private void applyLsd(CallbackInfo ci) {
if (lsd.isEnabled()) lsd.apply(mainRenderTarget, resourcePool);
}
@Inject(method = "render", at = @At("HEAD"), cancellable = true)
private void avoidRenderingUnfocusedCpu(DeltaTracker dt, boolean tick, CallbackInfo ci) {
private void avoidRenderingUnfocusedCpu(CallbackInfo ci) {
if (ModuleManager.INSTANCE.getModuleByClass(UnfocusedCpu.class).isEnabled() && !mc.isWindowActive()) {
ci.cancel();
}
@@ -64,7 +81,7 @@ public abstract class GameRendererMixin {
@Inject(method = "renderItemInHand", at = @At("HEAD"), cancellable = true)
private void renderHand(CameraRenderState cameraState, float deltaPartialTick, Matrix4fc modelViewMatrix, CallbackInfo ci) {
private void renderHand(CameraRenderState cameraState, PlayerRenderState playerRenderState, GpuTextureView texture, CallbackInfo ci) {
if ((zoom.isEnabled() && !zoom.showHands.getValue())
|| (freecam.isEnabled() && !freecam.renderHands.getValue()))
ci.cancel();
@@ -3,11 +3,12 @@ package me.retucio.sputnik.mixin.mixins.render;
import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.render.GlintPlus;
import net.minecraft.client.renderer.feature.ItemFeatureRenderer;
import net.minecraft.client.resources.model.geometry.BakedQuad;
import net.minecraft.client.renderer.rendertype.RenderType;
import net.minecraft.client.renderer.rendertype.RenderTypes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
@Mixin(ItemFeatureRenderer.class)
@@ -15,24 +16,15 @@ public abstract class ItemFeatureRendererMixin {
// obtener módulo en cada redirect, ya que tiene un inicializador estático
@Redirect(method = "getFoilRenderType", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;glint()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private static RenderType redirectGlint() {
@ModifyExpressionValue(method = "prepareMainSubmit", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/geometry/BakedQuad$MaterialInfo;itemGlintRenderType()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private RenderType sputnik$colorGlint(RenderType original, @Local BakedQuad.MaterialInfo materialInfo) {
GlintPlus glintPlus = ModuleManager.INSTANCE.getModuleByClass(GlintPlus.class);
if (glintPlus == null) return RenderTypes.glint();
return glintPlus.getGlint();
return glintPlus == null ? original : glintPlus.getGlint(original, materialInfo);
}
@Redirect(method = "getFoilRenderType", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;entityGlint()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private static RenderType redirectEntityGlint() {
@ModifyExpressionValue(method = "prepareMainSubmit", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/geometry/BakedQuad$MaterialInfo;itemGlintSpecialRenderType()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private RenderType sputnik$colorSpecialGlint(RenderType original, @Local BakedQuad.MaterialInfo materialInfo) {
GlintPlus glintPlus = ModuleManager.INSTANCE.getModuleByClass(GlintPlus.class);
if (glintPlus == null) return RenderTypes.entityGlint();
return glintPlus.getEntityGlint();
}
@Redirect(method = "getFoilRenderType", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;glintTranslucent()Lnet/minecraft/client/renderer/rendertype/RenderType;"))
private static RenderType redirectGlintTranslucent() {
GlintPlus glintPlus = ModuleManager.INSTANCE.getModuleByClass(GlintPlus.class);
if (glintPlus == null) return RenderTypes.glintTranslucent();
return glintPlus.getGlintTranslucent();
return glintPlus == null ? original : glintPlus.getGlint(original, materialInfo);
}
}
Loaded 100 of 269 files, more files were not shown because too many files have changed in this diff. Show more