From 44494830be9e9d1b1470196efe8de1ddf6fe02c7 Mon Sep 17 00:00:00 2001 From: retucio Date: Fri, 4 Sep 2026 12:58:41 +0200 Subject: [PATCH] v6.3 --- cambios.md | 14 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 5 +- .../mixins/entity/EntityRendererMixin.java | 2 + .../mixin/mixins/misc/ReferenceMixin.java | 33 ++ .../render/LivingEntityRendererMixin.java | 18 +- .../me/retucio/sputnik/module/Module.java | 2 - .../retucio/sputnik/module/ModuleManager.java | 2 + .../module/modules/camera/Freecam.java | 3 +- .../module/modules/misc/ScreenshotPlus.java | 6 +- .../modules/network/PacketCanceller.java | 4 +- .../module/modules/network/PacketMine.java | 2 - .../module/modules/player/AutoEat.java | 47 +- .../modules/render/BreakingProgress.java | 8 +- .../module/modules/render/Nametags.java | 2 + .../module/modules/render/Stylish.java | 255 +++++++++ .../module/modules/render/Tracers.java | 5 +- .../module/modules/world/AutoMine.java | 484 ++++++++++++++++++ .../module/setting/settings/ListSetting.java | 87 +++- .../java/me/retucio/sputnik/util/Lists.java | 25 +- .../resources/assets/sputnik/lang/en_us.json | 84 +++ .../resources/assets/sputnik/lang/es_es.json | 84 +++ src/main/resources/sputnik.mixins.json | 1 + 23 files changed, 1132 insertions(+), 43 deletions(-) create mode 100644 src/main/java/me/retucio/sputnik/mixin/mixins/misc/ReferenceMixin.java create mode 100644 src/main/java/me/retucio/sputnik/module/modules/render/Stylish.java create mode 100644 src/main/java/me/retucio/sputnik/module/modules/world/AutoMine.java diff --git a/cambios.md b/cambios.md index c62eab0..552ee21 100644 --- a/cambios.md +++ b/cambios.md @@ -1,17 +1,21 @@ -# v6.2 +# v6.3 ## novedades +- actualizado a Gradle 9.7.1 + ### adiciones -#### elementos del HUD nuevos: +#### módulos nuevos: -- arraylist: lista de los módulos activados +- autominado: mina automáticamente los bloques seleccionados, con distintos modos de selección +- estiloso: haz truquitos con el ítem que tienes en la mano ### arreglos / mejoras -- ahora puedes hacer puentes de varios bloques de ancho con `andamios` -- unidades distintas para el elemento del HUD `velocidad` +- mejoras varias a `puto gordo` +- ahora `trazos` con `uno mismo` solo te apunta en cámara libre +- nuevo ajuste de distancia máxima de renderizado para `nametags` ## problemas reconocidos diff --git a/gradle.properties b/gradle.properties index e8aec5b..1e4c1af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ discord_rpc_version=1.0.3 neutrino_version=1.1.0 jdk_version=25 -mod_version=6.2 +mod_version=6.3 maven_group=me.retucio archives_base_name=sputnik \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e922f4..2df7990 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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.7.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java index c4e3546..3a15f24 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java @@ -8,8 +8,10 @@ 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; diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/misc/ReferenceMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/misc/ReferenceMixin.java new file mode 100644 index 0000000..8cc0877 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/misc/ReferenceMixin.java @@ -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 { + + @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()); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/render/LivingEntityRendererMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/render/LivingEntityRendererMixin.java index a5971ef..ce9c634 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/render/LivingEntityRendererMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/render/LivingEntityRendererMixin.java @@ -8,7 +8,6 @@ import me.retucio.sputnik.module.modules.camera.Freecam; import me.retucio.sputnik.module.modules.render.Nametags; import net.minecraft.client.renderer.entity.LivingEntityRenderer; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; @@ -16,6 +15,7 @@ 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.ModifyArg; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -49,19 +49,27 @@ public abstract class LivingEntityRendererMixin { } @ModifyReturnValue(method = "shouldShowName(Lnet/minecraft/world/entity/LivingEntity;D)Z", at = @At("RETURN")) - private boolean shouldRenderPlayerNametag(boolean original, @Local(argsOnly = true) T livingEntity) { - if (nametags.isEnabled() && livingEntity instanceof Player) + private boolean shouldRenderPlayerNametag(boolean original, @Local(argsOnly = true, name = "entity") T entity) { + if (nametags.isEnabled() && entity instanceof Player) return nametags.entities.isEnabled(EntityTypes.PLAYER) && original; return original; } @Inject(method = "shouldShowName(Lnet/minecraft/world/entity/LivingEntity;D)Z", at = @At("RETURN"), cancellable = true) - private void renderSelfNametag(T livingEntity, double d, CallbackInfoReturnable cir) { - if (livingEntity instanceof Player player) { + private void renderSelfNametag(T entity, double distanceToCameraSq, CallbackInfoReturnable cir) { + if (entity instanceof Player player) { if (player == mc.player && nametags.showSelf.getValue()) { cir.setReturnValue(true); cir.cancel(); } } } + + @ModifyArg(method = "extractNameTags(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderer;extractNameTags(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/entity/state/EntityRenderState;FDD)V"), index = 3) + private double modifyNametagRenderDistance(double original) { + if (nametags.isEnabled()) { + return nametags.distance.getValue(); + } + return original; + } } \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/Module.java b/src/main/java/me/retucio/sputnik/module/Module.java index 3dc4933..cd5bc32 100644 --- a/src/main/java/me/retucio/sputnik/module/Module.java +++ b/src/main/java/me/retucio/sputnik/module/Module.java @@ -185,8 +185,6 @@ public class Module { // getters y setters - // OJO: nombre interno. es la clave de la config. (moduleStates, getSettingKey, settingsPanels) - // y no se traduce nunca. para mostrarlo al usuario usar getDisplayName() public String getName() { return name; } diff --git a/src/main/java/me/retucio/sputnik/module/ModuleManager.java b/src/main/java/me/retucio/sputnik/module/ModuleManager.java index 73f17ab..12297db 100644 --- a/src/main/java/me/retucio/sputnik/module/ModuleManager.java +++ b/src/main/java/me/retucio/sputnik/module/ModuleManager.java @@ -182,10 +182,12 @@ public class ModuleManager { add(new Nametags()); add(new NoRender()); add(new Particles()); + add(new Stylish()); add(new Tracers()); } private void addWorld() { + add(new AutoMine()); add(new AutoSign()); add(new AutoTool()); add(new BlockInfo()); diff --git a/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java b/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java index f931140..4e095e3 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java +++ b/src/main/java/me/retucio/sputnik/module/modules/camera/Freecam.java @@ -358,8 +358,9 @@ public class Freecam extends Module { if (mc.player == null) return; // el jugador probablemente nunca sea nulo bajo estas circunstancias pero quién sabe if (event.getPacket() instanceof ClientboundPlayerCombatKillPacket packet) { - if (mc.player.getId() == packet.playerId()) + if (mc.player.getId() == packet.playerId()) { toggle(); + } } else if (event.getPacket() instanceof ClientboundSetHealthPacket packet) { if (mc.player.getHealth() - packet.getHealth() > 0 && toggleOnDamage.getValue()) { ChatUtil.info(Component.translatable("sputnik.module.freecam.damagedisable")); diff --git a/src/main/java/me/retucio/sputnik/module/modules/misc/ScreenshotPlus.java b/src/main/java/me/retucio/sputnik/module/modules/misc/ScreenshotPlus.java index 62481b9..66cb350 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/misc/ScreenshotPlus.java +++ b/src/main/java/me/retucio/sputnik/module/modules/misc/ScreenshotPlus.java @@ -34,8 +34,10 @@ public class ScreenshotPlus extends Module { private final SettingGroup sgButtons = addSg(new SettingGroup("botones", true)); - public final EnumSetting defaultAction = sgGeneral.add(new EnumSetting<>("por defecto", "qué acción tomar por defecto", - ScreenshotActions.class, ScreenshotActions.NONE)); + public final EnumSetting defaultAction = sgGeneral.add(new EnumSetting<>( + "por defecto", "qué acción tomar por defecto", + ScreenshotActions.class, ScreenshotActions.NONE) + ); private final BooleanSetting saveButton = sgButtons.add(new BooleanSetting("botón de guardar", "mostrar botón para guardar la captura localmente", true)); private final BooleanSetting copyButton = sgButtons.add(new BooleanSetting("botón de copiar", "mostrar botón para copiar la captura al portapapeles", true)); diff --git a/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java b/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java index 3a18496..0d3865b 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java +++ b/src/main/java/me/retucio/sputnik/module/modules/network/PacketCanceller.java @@ -13,12 +13,12 @@ import org.jspecify.annotations.NonNull; public class PacketCanceller extends Module { - private final ListSetting>> c2s = sgGeneral.add(new ListSetting( + private final ListSetting>> c2s = sgGeneral.add(new ListSetting<>( "C2S", "paquetes a cancelar, dirección servidor", Lists.c2sPacketList, Lists.allFalse(Lists.c2sPacketList), Lists.c2sPacketNames )); - private final ListSetting>> s2c = sgGeneral.add(new ListSetting( + private final ListSetting>> s2c = sgGeneral.add(new ListSetting<>( "S2C", "paquetes a cancelar, dirección cliente", Lists.s2cPacketList, Lists.allFalse(Lists.s2cPacketList), Lists.s2cPacketNames )); diff --git a/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java b/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java index 2a5ad05..c9214a7 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java +++ b/src/main/java/me/retucio/sputnik/module/modules/network/PacketMine.java @@ -54,8 +54,6 @@ public class PacketMine extends Module { ClickAction.ADD_PRIORITY )); - // el espacio del final es a propósito: compartía clave de config. con retryTime ("delay"), - // así que se pisaban el valor. se muestra igual porque getName() hace strip() private final NumberSetting selectionDelay = sgGeneral.add(new NumberSetting( "delay ", "delay entre selecciones de bloques", diff --git a/src/main/java/me/retucio/sputnik/module/modules/player/AutoEat.java b/src/main/java/me/retucio/sputnik/module/modules/player/AutoEat.java index f5b484c..5f5c0f0 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/player/AutoEat.java +++ b/src/main/java/me/retucio/sputnik/module/modules/player/AutoEat.java @@ -2,12 +2,18 @@ package me.retucio.sputnik.module.modules.player; import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.setting.settings.BooleanSetting; +import me.retucio.sputnik.module.setting.settings.ListSetting; import me.retucio.sputnik.module.setting.settings.NumberSetting; import me.retucio.sputnik.util.InventoryUtil; +import me.retucio.sputnik.util.Lists; import net.minecraft.core.component.DataComponents; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Consumable; +import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect; import java.util.Comparator; import java.util.List; @@ -20,10 +26,28 @@ public class AutoEat extends Module { 6, 0, 19, 1 )); + private final NumberSetting whenToStop = sgGeneral.add(new NumberSetting( + "cuándo parar", "cuántos puntos de comida hace falta tener para parar de comer", + 20, 1, 20, 1 + )); + + private final ListSetting blacklist = sgGeneral.add(new ListSetting<>( + "lista negra", "alimentos a NO comer", + () -> Lists.foodList, + () -> Lists.allFalseExcept(Lists.foodList, this::isBad), + () -> Lists.foodNames + )); + private boolean eating; public AutoEat() { - super("puto gordo", "come automáticamente al tener hambre", Category.PLAYER); + super("puto gordo", "come aut/omáticamente al tener hambre", Category.PLAYER); + } + + @Override + public void onDisable() { + if (eating) stopEating(); + super.onDisable(); } @Override @@ -34,7 +58,9 @@ public class AutoEat extends Module { if (mc.player.getUseItemRemainingTicks() > 0) { return; } else { - stopEating(); + if (mc.player.getFoodData().getFoodLevel() >= whenToStop.getIntValue()) { + stopEating(); + } } } @@ -42,15 +68,21 @@ public class AutoEat extends Module { List sorted = InventoryUtil.findAllStacks( stack -> stack.getItem().components().get(DataComponents.FOOD) != null ); - sorted.sort(Comparator.comparingInt(stack -> stack.getItem().components().get(DataComponents.FOOD).nutrition())); + sorted.sort(Comparator.comparingInt( + (ItemStack s) -> s.getItem().components().get(DataComponents.FOOD).nutrition() + ).reversed()); + if (sorted.isEmpty()) return; + sorted.removeIf(stack -> blacklist.isEnabled(stack.getItem())); + boolean inHotbar = false; for (ItemStack stack : sorted) { int slot = InventoryUtil.findSlot(s -> stack == s); if (Inventory.isHotbarSlot(slot)) { mc.player.getInventory().setSelectedSlot(slot); inHotbar = true; + break; } } @@ -79,4 +111,13 @@ public class AutoEat extends Module { eating = false; mc.options.keyUse.setDown(false); } + + private boolean isBad(Item item) { + Consumable consumable = item.components().get(DataComponents.CONSUMABLE); + if (consumable == null) return false; + return consumable.onConsumeEffects().stream() + .anyMatch(effect -> effect instanceof ApplyStatusEffectsConsumeEffect applyEffects + && applyEffects.effects().stream().anyMatch(mobEffectInstance -> + !mobEffectInstance.getEffect().value().isBeneficial())); + } } diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/BreakingProgress.java b/src/main/java/me/retucio/sputnik/module/modules/render/BreakingProgress.java index b237930..8be9aea 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/BreakingProgress.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/BreakingProgress.java @@ -20,16 +20,16 @@ import java.awt.*; public class BreakingProgress extends Module { - private final SettingGroup sgOulines = addSg(new SettingGroup("contorno", true)); + private final SettingGroup sgOutlines = addSg(new SettingGroup("contorno", true)); private final SettingGroup sgFilling = addSg(new SettingGroup("relleno", true)); private final EnumSetting breakMode = sgGeneral.add(new EnumSetting<>("modo de minado", "elige si el cubo se encoge o se dilata", BreakMode.class, BreakMode.INWARDS)); - private final BooleanSetting outlines = sgOulines.add(new BooleanSetting("contorno", "renderizar contorno", true)); - private final ColorSetting outlineColor = sgOulines.add(new ColorSetting("color del contorno", "color del contorno", + private final BooleanSetting outlines = sgOutlines.add(new BooleanSetting("contorno", "renderizar contorno", true)); + private final ColorSetting outlineColor = sgOutlines.add(new ColorSetting("color del contorno", "color del contorno", new Color(0, 255, 0, 200), false)); - private final NumberSetting lineWidth = sgOulines.add(new NumberSetting("grosor de línea", "grosor de las líneas del contorno", 1, 1, 5, 0.1)); + private final NumberSetting lineWidth = sgOutlines.add(new NumberSetting("grosor de línea", "grosor de las líneas del contorno", 1, 1, 5, 0.1)); private final BooleanSetting fillings = sgFilling.add(new BooleanSetting("relleno", "renderizar relleno", true)); private final ColorSetting fillingColor = sgFilling.add(new ColorSetting("color del relleno", "color del relleno", diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/Nametags.java b/src/main/java/me/retucio/sputnik/module/modules/render/Nametags.java index 84f3ddd..4408006 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/Nametags.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/Nametags.java @@ -13,6 +13,7 @@ import me.retucio.sputnik.module.setting.SettingGroup; import me.retucio.sputnik.module.setting.settings.BooleanSetting; import me.retucio.sputnik.module.setting.settings.EnumSetting; import me.retucio.sputnik.module.setting.settings.ListSetting; +import me.retucio.sputnik.module.setting.settings.NumberSetting; import me.retucio.sputnik.util.Lists; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; @@ -64,6 +65,7 @@ public class Nametags extends Module { public final BooleanSetting alwaysVisible = sgGeneral.add(new BooleanSetting("siempre mostrar nametags", "mostrar nametag cuando el jugador está agachado o es invisible", false)); public final BooleanSetting showSelf = sgGeneral.add(new BooleanSetting("mostrar propio", "mostrar nametag propio", false)); public final BooleanSetting friendNick = sgGeneral.add(new BooleanSetting("motes", "usar los motes de amigos", true)); + public final NumberSetting distance = sgGeneral.add(new NumberSetting("distancia", "distancia máxima para renderizar nametags", 1024, 1, 1024, 1)); public final BooleanSetting countItems = sgInfo.add(new BooleanSetting("contar items", "muestra cuánto de un item hay en un stack dropeado", true)); public final BooleanSetting showProjectileDamage = sgInfo.add(new BooleanSetting("daño del proyectil", "muestra cuánto daño hace un proyectil en su nametag", true)); diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/Stylish.java b/src/main/java/me/retucio/sputnik/module/modules/render/Stylish.java new file mode 100644 index 0000000..bc9b7a7 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/module/modules/render/Stylish.java @@ -0,0 +1,255 @@ +package me.retucio.sputnik.module.modules.render; + +import com.github.retucio.neutrino.EventListener; +import com.mojang.blaze3d.vertex.PoseStack; +import me.retucio.sputnik.event.input.KeyEvent; +import me.retucio.sputnik.event.render.RenderHeldItemEvent; +import me.retucio.sputnik.module.Category; +import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.setting.settings.BooleanSetting; +import me.retucio.sputnik.module.setting.settings.EnumSetting; +import me.retucio.sputnik.module.setting.settings.KeySetting; +import me.retucio.sputnik.module.setting.settings.NumberSetting; +import net.minecraft.world.InteractionHand; +import org.joml.Quaternionf; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Stylish extends Module { + + private final KeySetting key = sgGeneral.add(new KeySetting( + "tecla", "tecla a pulsar para hacer el truco", + GLFW.GLFW_KEY_Y + )); + + private final EnumSetting trick = sgGeneral.add(new EnumSetting<>( + "truco", "qué truco hacer al pulsar la tecla", + TrickType.class, TrickType.SPIN + )); + + private final BooleanSetting comboMode = sgGeneral.add(new BooleanSetting( + "combo", "encadena varios trucos distintos en una sola pulsación", + false + )); + + private final NumberSetting comboLength = sgGeneral.add(new NumberSetting( + "trucos por combo", "cuántos trucos encadenar si el combo está activado", + 3, 2, 6, 1 + )).visibility(comboMode::getValue); + + private final NumberSetting speed = sgGeneral.add(new NumberSetting( + "velocidad", "velocidad de giro (grados/seg)", + 360, 30, 2000, 1 + )); + + private final NumberSetting spins = sgGeneral.add(new NumberSetting( + "vueltas", "cuantas vueltas completas da por truco", + 1, 1, 5, 1 + )); + + private final EnumSetting dir = sgGeneral.add(new EnumSetting<>( + "dirección", "dirección a rotar el ítem", + RotateDirection.class, RotateDirection.COUNTER_CLOCKWISE + )); + + private final EnumSetting hand = sgGeneral.add(new EnumSetting<>( + "mano", "en qué mano(s) hacerlo", + Hand.class, Hand.MAIN_HAND + )); + + private final BooleanSetting easing = sgGeneral.add(new BooleanSetting( + "suavizado", "usa una curva de aceleración/desaceleración en vez de velocidad lineal", + true + )); + + private final NumberSetting cooldown = sgGeneral.add(new NumberSetting( + "cooldown", "tiempo mínimo en ms entre activaciones", + 150, 0, 2000, 10 + )); + + // estado de anim. + private boolean spinning = false; + private float progressDegrees = 0f; + private long lastTimeNanos = 0L; + private long lastTriggerMillis = 0L; + + private final List queue = new ArrayList<>(); + private int queueIndex = 0; + private final Random random = new Random(); + + public Stylish() { + super("estiloso", "haz truquitos con items en las manos", Category.RENDER); + } + + @EventListener + private void onKey(KeyEvent event) { + if (event.getKey() != key.getValue()) return; + if (event.getAction() != GLFW.GLFW_PRESS) return; + if (spinning) return; + + long now = System.currentTimeMillis(); + if (now - lastTriggerMillis < cooldown.getIntValue()) return; + lastTriggerMillis = now; + + buildQueue(); + startNextTrick(); + } + + private void buildQueue() { + queue.clear(); + queueIndex = 0; + + if (!comboMode.getValue()) { + queue.add(resolveTrick(trick.getValue())); + return; + } + + int amount = comboLength.getIntValue(); + for (int i = 0; i < amount; i++) { + queue.add(resolveTrick(TrickType.RANDOM)); + } + } + + private TrickType resolveTrick(TrickType requested) { + if (requested != TrickType.RANDOM) return requested; + TrickType[] real = { + TrickType.SPIN, TrickType.FLIP, TrickType.ROLL, + TrickType.TWIRL, TrickType.WAVE, TrickType.POP, TrickType.THROW_CATCH + }; + return real[random.nextInt(real.length)]; + } + + private void startNextTrick() { + if (queueIndex >= queue.size()) { + spinning = false; + return; + } + spinning = true; + progressDegrees = 0f; + lastTimeNanos = System.nanoTime(); + } + + @EventListener + private void onRenderHeldItem(RenderHeldItemEvent event) { + if (!spinning) return; + if ((event.getHand() == InteractionHand.MAIN_HAND && hand.is(Hand.OFF_HAND)) + || (event.getHand() == InteractionHand.OFF_HAND && hand.is(Hand.MAIN_HAND)) + ) { + return; + } + + PoseStack matrices = event.getMatrices(); + + long now = System.nanoTime(); + float delta = (now - lastTimeNanos) / 1_000_000_000f; + lastTimeNanos = now; + + float target = 360.0f * spins.getIntValue(); + progressDegrees += speed.getFloatValue() * delta; + + boolean finished = progressDegrees >= target; + if (finished) progressDegrees = target; + + float rawFraction = progressDegrees / target; // 0..1 + float fraction = easing.getValue() ? easeInOutCubic(rawFraction) : rawFraction; + + TrickType current = queue.get(queueIndex); + applyTrick(current, matrices, fraction); + + if (finished) { + queueIndex++; + if (queueIndex < queue.size()) { + startNextTrick(); + } else { + spinning = false; + } + } + } + + private void applyTrick(TrickType type, PoseStack matrices, float fraction) { + float directionSign = dir.getValue() == RotateDirection.CLOCKWISE ? -1f : 1f; + float degrees = fraction * 360.0f * spins.getIntValue(); + float angleRad = (float) Math.toRadians(degrees * directionSign); + + switch (type) { + case SPIN -> matrices.mulPose(new Quaternionf().rotationZ(angleRad)); + + case FLIP -> matrices.mulPose(new Quaternionf().rotationX(angleRad)); + + case ROLL -> matrices.mulPose(new Quaternionf().rotationY(angleRad)); + + case TWIRL -> { + // combina spin + flip para un giro más aparatoso + matrices.mulPose(new Quaternionf().rotationZ(angleRad)); + matrices.mulPose(new Quaternionf().rotationX(angleRad * 0.5f)); + } + + case WAVE -> { + // bamboleo tipo saludo, sin vuelta completa + float waveAngle = (float) Math.sin(fraction * Math.PI * spins.getIntValue()) * 25f; + matrices.mulPose(new Quaternionf().rotationZ((float) Math.toRadians(waveAngle * directionSign))); + } + + case POP -> { + // pequeño pulso de escala, como si el ítem "respirara" + float scalePulse = 1f + (float) Math.sin(fraction * Math.PI) * 0.25f; + matrices.scale(scalePulse, scalePulse, scalePulse); + matrices.mulPose(new Quaternionf().rotationZ(angleRad * 0.3f)); + } + + case THROW_CATCH -> { + // el ítem "sube", gira en el aire y "cae" de vuelta + float height = (float) Math.sin(fraction * Math.PI) * 0.35f; + matrices.translate(0f, height, 0f); + matrices.mulPose(new Quaternionf().rotationX(angleRad * 2f)); + } + + case RANDOM -> { + // no debería llegar aquí, resolveTrick lo reemplaza antes + matrices.mulPose(new Quaternionf().rotationZ(angleRad)); + } + } + } + + private float easeInOutCubic(float t) { + return t < 0.5f + ? 4f * t * t * t + : 1f - (float) Math.pow(-2f * t + 2f, 3) / 2f; + } + + private enum TrickType { + SPIN("giro"), + FLIP("voltereta"), + ROLL("balanceo"), + TWIRL("giro doble"), + WAVE("saludo"), + POP("pulso"), + THROW_CATCH("lanzar y coger"), + RANDOM("aleatorio"); + + private final String name; + TrickType(String name) { this.name = name; } + @Override public String toString() { return name; } + } + + private enum RotateDirection { + CLOCKWISE("sentido horario"), + COUNTER_CLOCKWISE("sentido antihorario"); + + private final String name; + RotateDirection(String name) { this.name = name; } + @Override public String toString() { return name; } + } + + private enum Hand { + MAIN_HAND("mano principal"), + OFF_HAND("mano secundaria"); + + private final String name; + Hand(String name) { this.name = name; } + @Override public String toString() { return name; } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/Tracers.java b/src/main/java/me/retucio/sputnik/module/modules/render/Tracers.java index 25a8db8..1dfa2ef 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/Tracers.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/Tracers.java @@ -4,6 +4,8 @@ import com.github.retucio.neutrino.EventListener; import me.retucio.sputnik.event.render.Render3DEvent; import me.retucio.sputnik.module.Category; import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.ModuleManager; +import me.retucio.sputnik.module.modules.camera.Freecam; import me.retucio.sputnik.module.setting.SettingGroup; import me.retucio.sputnik.module.setting.settings.BooleanSetting; import me.retucio.sputnik.module.setting.settings.ColorSetting; @@ -46,7 +48,7 @@ public class Tracers extends Module { private final BooleanSetting self = sgGeneral.add(new BooleanSetting( "uno mismo", - "apuntarse a uno mismo en cámara libre o así", + "apuntarse a uno mismo en cámara libre", true )); @@ -105,6 +107,7 @@ public class Tracers extends Module { private Color getColor(Entity entity) { if (!entities.isEnabled(entity.getType())) return null; if (!self.getValue() && mc.player == entity) return null; + if (self.getValue() && mc.player == entity && !ModuleManager.INSTANCE.getModuleByClass(Freecam.class).isEnabled()) return null; if (entity instanceof Player) return playerColor.getValue(); return switch (entity.getType().getCategory()) { diff --git a/src/main/java/me/retucio/sputnik/module/modules/world/AutoMine.java b/src/main/java/me/retucio/sputnik/module/modules/world/AutoMine.java new file mode 100644 index 0000000..888e6a2 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/module/modules/world/AutoMine.java @@ -0,0 +1,484 @@ +package me.retucio.sputnik.module.modules.world; + +import com.github.retucio.neutrino.EventListener; +import me.retucio.sputnik.event.input.MouseClickEvent; +import me.retucio.sputnik.event.interact.AttackBlockEvent; +import me.retucio.sputnik.event.network.PacketEvent; +import me.retucio.sputnik.event.render.Render3DEvent; +import me.retucio.sputnik.module.Category; +import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.setting.SettingGroup; +import me.retucio.sputnik.module.setting.settings.BooleanSetting; +import me.retucio.sputnik.module.setting.settings.ColorSetting; +import me.retucio.sputnik.module.setting.settings.EnumSetting; +import me.retucio.sputnik.module.setting.settings.ListSetting; +import me.retucio.sputnik.module.setting.settings.NumberSetting; +import me.retucio.sputnik.util.ChatUtil; +import me.retucio.sputnik.util.InventoryUtil; +import me.retucio.sputnik.util.Lists; +import me.retucio.sputnik.util.MiscUtil; +import me.retucio.sputnik.util.RotationUtil; +import me.retucio.sputnik.util.render.RenderUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Tool; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.lwjgl.glfw.GLFW; + +import java.awt.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +public class AutoMine extends Module { + + private final SettingGroup sgBlocks = addSg(new SettingGroup("bloques", true)); + private final SettingGroup sgRender = addSg(new SettingGroup("renderizado", true)); + + private final EnumSetting mode = sgGeneral.add(new EnumSetting<>( + "modo de selección", + "cómo seleccionar los bloques a minar", + SelectionMode.class, + SelectionMode.INDIVIDUAL + )); + + private final NumberSetting maxBlocks = sgGeneral.add(new NumberSetting( + "límite de bloques", + "cantidad máxima de bloques a seleccionar de una vez (caja / flood)", + 256, + 1, + 4096, + 1 + )); + + private final NumberSetting retryTime = sgGeneral.add(new NumberSetting( + "delay", + "delay entre reintentos de minado", + 30, + 0, + 1000, + 1 + )); + + private final BooleanSetting cancelOutOfReach = sgGeneral.add(new BooleanSetting( + "cancelar fuera de alcance", + "cancelar aquellos paquetes que intenten romper bloques fuera del alcance del jugador", + true + )); + + private final BooleanSetting autoTool = sgGeneral.add(new BooleanSetting( + "auto herramienta", + "cambiar automáticamente a la herramienta más rápida antes de minar cada bloque", + true + )); + + private final BooleanSetting dontBreak = sgGeneral.add(new BooleanSetting( + "no romper pico", + "evita romper la herramienta seleccionada", + true + )); + + private final EnumSetting rotate = sgGeneral.add(new EnumSetting<>( + "rotar", + "rotar al minar bloques mediante el módulo", + RotateMode.class, RotateMode.NONE + )); + + private final NumberSetting packetLimiter = sgGeneral.add(new NumberSetting( + "limitar paquetes", + "limita la cantidad de paquetes enviados en un segundo (0 para desactivar)", + 15000, 0, 30000, 100 + )); + + private final ListSetting blacklist = sgBlocks.add(new ListSetting<>( + "lista negra", + "bloques a excluir de la selección", + Lists.blockList, + Lists.allFalse(Lists.blockList), + Lists.blockNames + )); + + private final BooleanSetting render = sgRender.add(new BooleanSetting( + "renderizar", + "renderizar un contorno alrededor de los bloques que están siendo minados", + true + )); + + private final ColorSetting color = sgRender.add(new ColorSetting( + "color", + "color del contorno renderizado", + new Color(0, 255, 0, 100), + false + )); + + private final ColorSetting pendingColor = sgRender.add(new ColorSetting( + "color de selección pendiente", + "color del contorno de la primera esquina, en modo caja, mientras se espera la segunda", + new Color(255, 255, 0, 100), + false + )); + + private final NumberSetting lineWidth = sgRender.add(new NumberSetting( + "grosor", + "grosor de las líneas del contorno", + 2, + 0.1, + 10, + 0.1 + )); + + private final List miningBlocks = new ArrayList<>(); + private BlockPos pendingCorner = null; + private int packetCount = 0; + private int tickCount = 0; + + public AutoMine() { + super( + "auto minado", + "selecciona un área o bloques individuales para minarlos automáticamente", + Category.WORLD + ); + + render.onUpdate(v -> { + color.visibility(v); + pendingColor.visibility(v); + lineWidth.visibility(v); + }); + } + + @Override + public void onDisable() { + miningBlocks.clear(); + pendingCorner = null; + packetCount = 0; + tickCount = 0; + super.onDisable(); + } + + @Override + public void onTick() { + if (mc.player == null || mc.level == null || mc.gameMode == null) return; + + tickCount++; + if (tickCount % 20 == 0) { + tickCount = 0; + packetCount = 0; + } + + if (miningBlocks.isEmpty()) return; + + // se limpian bloques ya rotos, o que fueron blacklisteados después de seleccionarse + Iterator iterator = miningBlocks.iterator(); + while (iterator.hasNext()) { + MiningBlock block = iterator.next(); + BlockState state = mc.level.getBlockState(block.pos); + if (state.isAir() || blacklist.isEnabled(state.getBlock())) { + iterator.remove(); + } + } + + for (MiningBlock block : miningBlocks) { + processBlock(block); + } + } + + @EventListener + private void onClick(MouseClickEvent event) { + if (mc.gui.screen() != null) return; + if (mc.level == null || mc.hitResult == null) return; + if (event.getAction() != GLFW.GLFW_PRESS) return; + if (event.getButton() != GLFW.GLFW_MOUSE_BUTTON_1) return; + BlockHitResult bhr; + if (mc.hitResult.getType() == HitResult.Type.BLOCK) { + bhr = (BlockHitResult) mc.hitResult; + } else { + return; + } + + event.cancel(); + + BlockPos pos = bhr.getBlockPos(); + Direction dir = bhr.getDirection(); + Block block = mc.level.getBlockState(pos).getBlock(); + + // clicar un bloque ya seleccionado lo quita de la selección, sea cual sea el modo + MiningBlock existing = getMiningBlock(pos); + if (existing != null) { + miningBlocks.remove(existing); + return; + } + + if (blacklist.isEnabled(block)) { + ChatUtil.warn(Component.translatable("sputnik.module.autominado.blacklisted")); + return; + } + + switch (mode.getValue()) { + case INDIVIDUAL -> tryAddBlock(pos, dir); + case BOX -> handleBoxSelection(pos, dir); + case FLOOD_FILL -> handleFloodFill(pos, dir); + } + } + + @EventListener + private void onRenderWorld(Render3DEvent event) { + if (!render.getValue()) return; + + for (MiningBlock block : miningBlocks) { + block.render(event); + } + + if (pendingCorner != null) { + renderOutline(event, pendingCorner, pendingColor.getValue()); + } + } + + @EventListener + private void onPacketSend(PacketEvent.Send event) { + if (packetLimiter.getValue() <= 0) return; + if (packetCount > packetLimiter.getIntValue()) { + event.cancel(); + return; + } + packetCount++; + } + + private void handleBoxSelection(BlockPos pos, Direction dir) { + if (pendingCorner == null) { + pendingCorner = pos; + ChatUtil.info(Component.translatable("sputnik.module.autominado.cornerselected")); + return; + } + + BlockPos corner1 = pendingCorner; + BlockPos corner2 = pos; + pendingCorner = null; + + int minX = Math.min(corner1.getX(), corner2.getX()); + int maxX = Math.max(corner1.getX(), corner2.getX()); + int minY = Math.min(corner1.getY(), corner2.getY()); + int maxY = Math.max(corner1.getY(), corner2.getY()); + int minZ = Math.min(corner1.getZ(), corner2.getZ()); + int maxZ = Math.max(corner1.getZ(), corner2.getZ()); + + int added = 0; + int limit = maxBlocks.getIntValue(); + + outer: + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + if (added >= limit) break outer; + if (tryAddBlock(new BlockPos(x, y, z), dir)) added++; + } + } + } + + ChatUtil.info(Component.translatable("sputnik.module.autominado.boxselected", added)); + } + + private void handleFloodFill(BlockPos origin, Direction dir) { + if (mc.level == null) return; + Block target = mc.level.getBlockState(origin).getBlock(); + + Set visited = new HashSet<>(); + Deque queue = new ArrayDeque<>(); + queue.add(origin); + visited.add(origin); + + int added = 0; + int limit = maxBlocks.getIntValue(); + + while (!queue.isEmpty() && added < limit) { + BlockPos current = queue.poll(); + if (mc.level.getBlockState(current).getBlock() != target) continue; + + if (tryAddBlock(current, dir)) added++; + + for (Direction d : Direction.values()) { + BlockPos neighbor = current.relative(d); + if (visited.contains(neighbor)) continue; + visited.add(neighbor); + if (mc.level.getBlockState(neighbor).getBlock() == target) { + queue.add(neighbor); + } + } + } + + ChatUtil.info(Component.translatable("sputnik.module.autominado.floodfilled", added)); + } + + // false si el bloque no se pudo (o no debía) agregar: aire, ya seleccionado o en la lista negra + private boolean tryAddBlock(BlockPos pos, Direction dir) { + if (mc.level == null) return false; + if (mc.level.getBlockState(pos).isAir()) return false; + if (getMiningBlock(pos) != null) return false; + if (blacklist.isEnabled(mc.level.getBlockState(pos).getBlock())) return false; + + miningBlocks.add(new MiningBlock(pos, dir)); + return true; + } + + private boolean processBlock(MiningBlock block) { + if (mc.level.getBlockState(block.pos).isAir()) return false; + + if (!block.mining) { + return block.mine(); + } else if (block.shouldRetry()) { + block.startTime = System.currentTimeMillis(); + block.mining = false; + return block.mine(); + } + + return false; + } + + private MiningBlock getMiningBlock(BlockPos pos) { + for (MiningBlock block : miningBlocks) { + if (block.pos.equals(pos)) return block; + } + return null; + } + + // básicamente AutoTool::selectFastestTool, pero apuntado a un bloque puntual en vez del raycast bajo la mira + private void switchToBestTool(BlockPos pos) { + if (mc.player == null || mc.level == null) return; + BlockState state = mc.level.getBlockState(pos); + + List tools = InventoryUtil.findAllStacks(stack -> stack.has(DataComponents.TOOL)); + + Map speeds = new HashMap<>(); + for (ItemStack tool : tools) { + int slot = mc.player.getInventory().findSlotMatchingItem(tool); + if (!Inventory.isHotbarSlot(slot)) continue; + + Tool toolComponent = tool.get(DataComponents.TOOL); + if (toolComponent != null) { + speeds.put(toolComponent.getMiningSpeed(state), tool); + } + } + + if (speeds.isEmpty()) return; + + Float fastestSpeed = MiscUtil.getHighest(speeds.keySet().stream().toList()); + ItemStack fastestTool = speeds.get(fastestSpeed); + int slot = mc.player.getInventory().findSlotMatchingItem(fastestTool); + + if (slot != -1) mc.player.getInventory().setSelectedSlot(slot); + } + + private void renderOutline(Render3DEvent event, BlockPos pos, Color renderColor) { + VoxelShape shape = mc.level.getBlockState(pos).getShape(mc.level, pos); + if (shape.isEmpty()) return; + + RenderUtil.drawVoxelShapeOutline( + event.getMatrices(), + shape, + pos, + renderColor, + lineWidth.getFloatValue(), + false + ); + } + + private RotationUtil.Mode getRotationMode() { + return switch (rotate.getValue()) { + case CLIENT, BOTH -> RotationUtil.Mode.CLIENT; + case SERVER -> RotationUtil.Mode.SILENT; + case NONE -> null; + }; + } + + private class MiningBlock { + BlockPos pos; + Direction dir; + boolean mining = false; + long startTime; + + public MiningBlock(BlockPos pos, Direction dir) { + this.pos = pos; + this.dir = dir; + this.startTime = System.currentTimeMillis(); + } + + public boolean mine() { + if (cancelOutOfReach.getValue() + && mc.player.position().distanceToSqr(Vec3.atLowerCornerOf(pos)) > Math.pow(mc.player.blockInteractionRange(), 2)) { + return false; + } + + if (dontBreak.getValue() + && mc.player.isHolding(ItemStack::isDamageableItem) + && mc.player.getMainHandItem().getMaxDamage() - mc.player.getMainHandItem().getDamageValue() <= 1) { + ChatUtil.warn(Component.translatable("sputnik.module.autominado.pickbreaking")); + toggle(); + return false; + } + + if (autoTool.getValue()) { + switchToBestTool(pos); + } + + Runnable mine = () -> { + mc.gameMode.startPrediction(mc.level, sequence -> + new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, pos, dir, sequence)); + mc.gameMode.startPrediction(mc.level, sequence -> + new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, pos, dir, sequence)); + }; + + RotationUtil.Mode rotation = getRotationMode(); + if (rotation == null) mine.run(); + else RotationUtil.lookAt(RotationUtil.NORMAL, Vec3.atLowerCornerOf(pos), 0, rotation, mine); + + mining = true; + return true; + } + + public void render(Render3DEvent event) { + renderOutline(event, pos, color.getValue()); + } + + public boolean shouldRetry() { + return mining && (System.currentTimeMillis() - startTime >= retryTime.getValue()); + } + } + + private enum SelectionMode { + INDIVIDUAL("individual"), + BOX("caja"), + FLOOD_FILL("inundación"); + + private final String name; + SelectionMode(String name) { this.name = name; } + @Override public String toString() { return name; } + } + + private enum RotateMode { + CLIENT("cámara"), + SERVER("mediante paquetes"), + BOTH("ambos"), + NONE("no"); + + private final String name; + RotateMode(String name) { this.name = name; } + @Override public String toString() { return name; } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java b/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java index 152b002..3dfe248 100644 --- a/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java +++ b/src/main/java/me/retucio/sputnik/module/setting/settings/ListSetting.java @@ -4,22 +4,31 @@ import me.retucio.sputnik.module.setting.Setting; import me.retucio.sputnik.util.I18nUtil; import java.util.*; +import java.util.function.Supplier; public class ListSetting extends Setting> { - private final List options; + private List options; private Map displayNames = null; + private final Supplier> optionsSupplier; + private final Supplier> initialValuesSupplier; + private final Supplier> displayNamesSupplier; + private boolean resolved; + public ListSetting(String name, String description, List options, Map initialValues) { - super(name, description, initialValues); + super(name, description, new HashMap<>()); + this.optionsSupplier = null; + this.initialValuesSupplier = null; + this.displayNamesSupplier = null; + this.resolved = true; + this.options = new ArrayList<>(options); this.defaultValue = new HashMap<>(); - for (T option : options) { boolean enabled = initialValues != null && initialValues.getOrDefault(option, false); defaultValue.put(option, enabled); } - this.value = new HashMap<>(defaultValue); } @@ -28,11 +37,65 @@ public class ListSetting extends Setting> { this.displayNames = displayNames; } + // constructor "lazy": para listas que no están listas (no pun intended) al momento de construirlas + + public ListSetting(String name, String description, + Supplier> optionsSupplier, + Supplier> initialValuesSupplier, + Supplier> displayNamesSupplier + ) { + super(name, description, new HashMap<>()); + this.optionsSupplier = optionsSupplier; + this.initialValuesSupplier = initialValuesSupplier; + this.displayNamesSupplier = displayNamesSupplier; + this.resolved = false; + + this.options = new ArrayList<>(); + this.defaultValue = new HashMap<>(); + this.value = new HashMap<>(); + } + + // resuelve la lista real la primera vez que se necesita + private void resolveIfNeeded() { + if (resolved || optionsSupplier == null) return; + resolved = true; + + List resolvedOptions = optionsSupplier.get(); + if (resolvedOptions == null) resolvedOptions = new ArrayList<>(); + + Map resolvedInitial = initialValuesSupplier != null ? initialValuesSupplier.get() : null; + + Map previousValue = this.value; + + this.options = new ArrayList<>(resolvedOptions); + this.defaultValue = new HashMap<>(); + for (T option : resolvedOptions) { + boolean enabled = resolvedInitial != null && resolvedInitial.getOrDefault(option, false); + defaultValue.put(option, enabled); + } + + this.value = new HashMap<>(defaultValue); + // preservar cualquier valor ya aplicado (p. ej. por ConfigManager) antes del resolve + if (previousValue != null) { + for (Map.Entry entry : previousValue.entrySet()) { + if (this.value.containsKey(entry.getKey())) { + this.value.put(entry.getKey(), entry.getValue()); + } + } + } + + if (displayNamesSupplier != null) { + this.displayNames = displayNamesSupplier.get(); + } + } + public boolean isEnabled(T option) { + resolveIfNeeded(); return value.getOrDefault(option, false); } public void setEnabled(T option, boolean enabled) { + resolveIfNeeded(); if (value.containsKey(option) && value.get(option) != enabled) { value.put(option, enabled); fireUpdateEvent(); @@ -45,22 +108,26 @@ public class ListSetting extends Setting> { } public Map getValues() { + resolveIfNeeded(); return new HashMap<>(value); } @Override public void setValue(Map values) { + resolveIfNeeded(); if (!this.value.equals(values)) { super.setValue(values); } } public void setDefaultValue(Map values) { + resolveIfNeeded(); defaultValue.clear(); defaultValue.putAll(values); } public Map getConfigValues() { + resolveIfNeeded(); Map configValues = new HashMap<>(); for (T option : options) { configValues.put(getConfigName(option), value.getOrDefault(option, false)); @@ -69,16 +136,17 @@ public class ListSetting extends Setting> { } public void addOption(T option, boolean isDefault, String displayName) { + resolveIfNeeded(); options.add(option); defaultValue.put(option, isDefault); value.put(option, isDefault); - if (this.displayNames != null) { this.displayNames.put(option, displayName); } } public void setAll(boolean enabled) { + resolveIfNeeded(); for (T option : options) { value.put(option, enabled); } @@ -87,10 +155,12 @@ public class ListSetting extends Setting> { } public List getOptions() { + resolveIfNeeded(); return Collections.unmodifiableList(options); } public List getEnabledOptions() { + resolveIfNeeded(); List enabledOptions = new ArrayList<>(); for (T option : options) { if (isEnabled(option)) { @@ -101,21 +171,18 @@ public class ListSetting extends Setting> { } public Map getDisplayNames() { + resolveIfNeeded(); return displayNames != null ? new HashMap<>(displayNames) : null; } - // OJO: nombre interno de la opción. es la clave de la config. (ver - // ConfigManager#applyListSetting) y para las listas del juego es la clave de - // traducción ("entity.minecraft.zombie"), que no depende del idioma. no traducir public String getConfigName(T key) { + resolveIfNeeded(); if (displayNames != null && displayNames.containsKey(key)) { return displayNames.get(key); } return key != null ? key.toString() : "?"; } - // nombre visible: se traduce aquí. antes se mostraba el nombre interno tal cual, o sea - // los nombres de vanilla en inglés aunque el juego estuviera en otro idioma public String getDisplayName(T key) { String name = getConfigName(key); return I18nUtil.orElse(name, name); diff --git a/src/main/java/me/retucio/sputnik/util/Lists.java b/src/main/java/me/retucio/sputnik/util/Lists.java index 3a6f645..10c1d15 100644 --- a/src/main/java/me/retucio/sputnik/util/Lists.java +++ b/src/main/java/me/retucio/sputnik/util/Lists.java @@ -1,6 +1,8 @@ package me.retucio.sputnik.util; +import me.retucio.sputnik.Sputnik; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleType; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.ProtocolInfo; @@ -16,12 +18,15 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.EntityType; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import org.jspecify.annotations.NonNull; import java.awt.*; import java.util.*; import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; import java.util.stream.Stream; import static me.retucio.sputnik.util.Colors.*; @@ -46,6 +51,9 @@ public class Lists { public static final List> screenList = BuiltInRegistries.MENU.stream().toList(); public static Map, String> screenNames; + public static List foodList = new ArrayList<>(); + public static Map foodNames = new HashMap<>(); + public static List>> packetList = new ArrayList<>(); public static Map>, String> packetNames = new HashMap<>(); public static List>> c2sPacketList = new ArrayList<>(); @@ -133,11 +141,17 @@ public class Lists { @SafeVarargs public static Map allTrueExcept(List options, T... exceptions) { - Map map = allFalse(options); + Map map = allTrue(options); for (T exception : exceptions) map.replace(exception, false); return map; } + public static Map allTrueExcept(List options, Predicate not) { + Map map = allTrue(options); + map.keySet().forEach(t -> { if (not.test(t)) map.put(t, false); }); + return map; + } + public static Map allFalse(List options) { Map map = new HashMap<>(); for (T option : options) map.put(option, false); @@ -151,14 +165,21 @@ public class Lists { return map; } + public static Map allFalseExcept(List options, Predicate not) { + Map map = allFalse(options); + map.keySet().forEach(t -> { if (not.test(t)) map.put(t, true); }); + return map; + } + public static Map getMapOfLists(List options, List names) { Map result = new HashMap<>(); // no exceder los límites de ninguna de las dos listas int size = Math.min(options.size(), names.size()); - for (int i = 0; i < size; i++) + for (int i = 0; i < size; i++) { result.put(options.get(i), names.get(i)); + } return result; } diff --git a/src/main/resources/assets/sputnik/lang/en_us.json b/src/main/resources/assets/sputnik/lang/en_us.json index fcf3d6a..3f788c3 100644 --- a/src/main/resources/assets/sputnik/lang/en_us.json +++ b/src/main/resources/assets/sputnik/lang/en_us.json @@ -377,7 +377,91 @@ "sputnik.module.autofish.rodbreaking": "the rod is about to break", "sputnik.module.autoherramienta.desc": "automatically switches to the right tool", "sputnik.module.autoherramienta.name": "auto tool", + "sputnik.module.autominado.name": "auto mine", + "sputnik.module.autominado.desc": "select blocks to mine them automatically", + "sputnik.module.autominado.blacklisted": "that block is blacklisted!", + "sputnik.module.autominado.cornerselected": "corner selected", + "sputnik.module.autominado.boxselected": "box selected", + "sputnik.module.autominado.floodfilled": "flood filled", + "sputnik.module.autominado.pickbreaking": "pickaxe's about to break!", + "sputnik.setting.autominado.general.mododeseleccion.name": "selection mode", + "sputnik.setting.autominado.general.mododeseleccion.desc": "in which way to select blocks", + "sputnik.enum.module.modules.world.automine.selectionmode.individual": "individual", + "sputnik.enum.module.modules.world.automine.selectionmode.box": "box", + "sputnik.enum.module.modules.world.automine.selectionmode.flood_fill": "flood fill", + "sputnik.setting.autominado.general.limitedebloques.name": "block limit", + "sputnik.setting.autominado.general.limitedebloques.desc": "max number of blocks to select", + "sputnik.setting.autominado.general.delay.name": "delay", + "sputnik.setting.autominado.general.delay.desc": "delay between attempts to mine", + "sputnik.setting.autominado.general.cancelarfueradealcance.name": "cancel out of reach", + "sputnik.setting.autominado.general.cancelarfueradealcance.desc": "do not try to mine blocks that are out of reach", + "sputnik.setting.autominado.general.autoherramienta.name": "autotool", + "sputnik.setting.autominado.general.autoherramienta.desc": "select the best tool automatically", + "sputnik.setting.autominado.general.noromperpico.name": "don't break", + "sputnik.setting.autominado.general.noromperpico.desc": "avoid breaking the current tool", + "sputnik.setting.autominado.general.rotar.name": "rotate", + "sputnik.setting.autominado.general.rotar.desc": "whether to rotate to face the block being mined, and in which way to do so", + "sputnik.enum.module.modules.world.automine.rotatemode.client": "client-sided", + "sputnik.enum.module.modules.world.automine.rotatemode.server": "server-sided", + "sputnik.enum.module.modules.world.automine.rotatemode.both": "client- and server-sided", + "sputnik.enum.module.modules.world.automine.rotatemode.none": "none", + "sputnik.setting.autominado.general.limitarpaquetes.name": "limit packets", + "sputnik.setting.autominado.general.limitarpaquetes.desc": "max number of packets to send per second (0 to disable)", + "sputnik.setting.autominado.bloques.listanegra.name": "blacklist", + "sputnik.setting.autominado.bloques.listanegra.desc": "blocks NOT to mine", + "sputnik.setting.autominado.renderizado.renderizar.name": "render", + "sputnik.setting.autominado.renderizado.renderizar.desc": "whether to render outlines of blocks being mined, or in the queue", + "sputnik.setting.autominado.renderizado.color.name": "color", + "sputnik.setting.autominado.renderizado.color.desc": "color of blocks being mined", + "sputnik.setting.autominado.renderizado.colordeseleccionpendiente.name": "color of pending selection", + "sputnik.setting.autominado.renderizado.colordeseleccionpendiente.desc": "color of the first corner in box mode", + "sputnik.setting.autominado.renderizado.grosor.name": "thickness", + "sputnik.setting.autominado.renderizado.grosor.desc": "line thickness", + "sputnik.group.autominado.bloques": "blocks", + "sputnik.group.autominado.renderizado": "render", + "sputnik.module.estiloso.name": "stylish", + "sputnik.module.estiloso.desc": "be stylish", + "sputnik.setting.estiloso.general.truco.name": "trick", + "sputnik.setting.estiloso.general.truco.desc": "which trick to do", + "sputnik.enum.module.modules.render.stylish.tricktype.spin": "spin", + "sputnik.enum.module.modules.render.stylish.tricktype.flip": "flip", + "sputnik.enum.module.modules.render.stylish.tricktype.roll": "roll", + "sputnik.enum.module.modules.render.stylish.tricktype.twirl": "twirl", + "sputnik.enum.module.modules.render.stylish.tricktype.wave": "wave", + "sputnik.enum.module.modules.render.stylish.tricktype.pop": "pop", + "sputnik.enum.module.modules.render.stylish.tricktype.throw_catch": "throw & catch", + "sputnik.enum.module.modules.render.stylish.tricktype.random": "random", + "sputnik.setting.estiloso.general.combo.name": "combo", + "sputnik.setting.estiloso.general.combo.desc": "concatenate tricks to do a combo", + "sputnik.setting.estiloso.general.trucosporcombo.name": "combo tricks", + "sputnik.setting.estiloso.general.trucosporcombo.desc": "how many tricks to do in a combo", + "sputnik.setting.estiloso.general.mano.name": "hand", + "sputnik.setting.estiloso.general.mano.desc": "in which hand(s) to do it", + "sputnik.setting.estiloso.general.tecla.name": "key", + "sputnik.setting.estiloso.general.tecla.desc": "key to use to do the thing", + "sputnik.setting.estiloso.general.velocidad.name": "speed", + "sputnik.setting.estiloso.general.velocidad.desc": "how quickly to do it", + "sputnik.setting.estiloso.general.vueltas.name": "times", + "sputnik.setting.estiloso.general.vueltas.desc": "how many times to do it", + "sputnik.setting.estiloso.general.direccion.name": "direction", + "sputnik.setting.estiloso.general.direccion.desc": "in which direction to do it", + "sputnik.enum.module.modules.render.stylish.hand.main_hand": "main hand", + "sputnik.enum.module.modules.render.stylish.hand.off_hand": "off hand", + "sputnik.setting.estiloso.general.suavizado.name": "easing", + "sputnik.setting.estiloso.general.suavizado.desc": "use non-linear velocity instead of linear", + "sputnik.setting.estiloso.general.cooldown.name": "cooldown", + "sputnik.setting.estiloso.general.cooldown.desc": "cooldown between tricks", + "sputnik.enum.module.modules.render.stylish.rotatedirection.clockwise": "clockwise", + "sputnik.enum.module.modules.render.stylish.rotatedirection.counter_clockwise": "counter-clockwise", + "sputnik.setting.hud.elementos.velocidad.name": "velocity", + "sputnik.setting.hud.elementos.velocidad.desc": "unit to use when showing current speed", "sputnik.module.autopechera.desc": "switches to a chestplate before hitting with the mace", + "sputnik.setting.nametags.general.distancia.name": "render distance", + "sputnik.setting.nametags.general.distancia.desc": "max distance to render nametags", + "sputnik.setting.putogordo.general.cuandoparar.name": "when to stop", + "sputnik.setting.putogordo.general.cuandoparar.desc": "how many food points you need to have to stop eating", + "sputnik.setting.putogordo.general.listanegra.name": "blacklist", + "sputnik.setting.putogordo.general.listanegra.desc": "food items NOT to eat", "sputnik.module.autopechera.name": "auto chestplate", "sputnik.module.autopesca.desc": "fishes for you", "sputnik.module.autopesca.name": "auto fish", diff --git a/src/main/resources/assets/sputnik/lang/es_es.json b/src/main/resources/assets/sputnik/lang/es_es.json index f8bf8c9..86a07c8 100644 --- a/src/main/resources/assets/sputnik/lang/es_es.json +++ b/src/main/resources/assets/sputnik/lang/es_es.json @@ -377,7 +377,91 @@ "sputnik.module.autofish.rodbreaking": "caña a punto de romperse", "sputnik.module.autoherramienta.desc": "cambia a la herramienta adecuada automáticamente", "sputnik.module.autoherramienta.name": "auto herramienta", + "sputnik.module.autominado.name": "auto minado", + "sputnik.module.autominado.desc": "selecciona un área o bloques individuales para minarlos automáticamente", + "sputnik.module.autominado.blacklisted": "ese bloque está en la lista negra!", + "sputnik.module.autominado.cornerselected": "esquina seleccionada", + "sputnik.module.autominado.boxselected": "área seleccionada", + "sputnik.module.autominado.floodfilled": "inundación seleccionada", + "sputnik.module.autominado.pickbreaking": "el pico está por romperse!", + "sputnik.setting.autominado.general.mododeseleccion.name": "modo de selección", + "sputnik.setting.autominado.general.mododeseleccion.desc": "cómo seleccionar los bloques a minar", + "sputnik.enum.module.modules.world.automine.selectionmode.individual": "individual", + "sputnik.enum.module.modules.world.automine.selectionmode.box": "caja", + "sputnik.enum.module.modules.world.automine.selectionmode.flood_fill": "inundación", + "sputnik.setting.autominado.general.limitedebloques.name": "límite de bloques", + "sputnik.setting.autominado.general.limitedebloques.desc": "cantidad máxima de bloques a seleccionar de una vez (caja / flood)", + "sputnik.setting.autominado.general.delay.name": "delay", + "sputnik.setting.autominado.general.delay.desc": "delay entre reintentos de minado", + "sputnik.setting.autominado.general.cancelarfueradealcance.name": "cancelar fuera de alcance", + "sputnik.setting.autominado.general.cancelarfueradealcance.desc": "cancelar aquellos paquetes que intenten romper bloques fuera del alcance del jugador", + "sputnik.setting.autominado.general.autoherramienta.name": "auto herramienta", + "sputnik.setting.autominado.general.autoherramienta.desc": "cambiar automáticamente a la herramienta más rápida antes de minar cada bloque", + "sputnik.setting.autominado.general.noromperpico.name": "no romper pico", + "sputnik.setting.autominado.general.noromperpico.desc": "evita romper la herramienta seleccionada", + "sputnik.setting.autominado.general.rotar.name": "rotar", + "sputnik.setting.autominado.general.rotar.desc": "rotar al minar bloques mediante el módulo", + "sputnik.enum.module.modules.world.automine.rotatemode.client": "cámara", + "sputnik.enum.module.modules.world.automine.rotatemode.server": "mediante paquetes", + "sputnik.enum.module.modules.world.automine.rotatemode.both": "ambos", + "sputnik.enum.module.modules.world.automine.rotatemode.none": "no", + "sputnik.setting.autominado.general.limitarpaquetes.name": "limitar paquetes", + "sputnik.setting.autominado.general.limitarpaquetes.desc": "limita la cantidad de paquetes enviados en un segundo (0 para desactivar)", + "sputnik.setting.autominado.bloques.listanegra.name": "lista negra", + "sputnik.setting.autominado.bloques.listanegra.desc": "bloques a excluir de la selección", + "sputnik.setting.autominado.renderizado.renderizar.name": "renderizar", + "sputnik.setting.autominado.renderizado.renderizar.desc": "renderizar un contorno alrededor de los bloques que están siendo minados", + "sputnik.setting.autominado.renderizado.color.name": "color", + "sputnik.setting.autominado.renderizado.color.desc": "color del contorno renderizado", + "sputnik.setting.autominado.renderizado.colordeseleccionpendiente.name": "color de selección pendiente", + "sputnik.setting.autominado.renderizado.colordeseleccionpendiente.desc": "color del contorno de la primera esquina, en modo caja, mientras se espera la segunda", + "sputnik.setting.autominado.renderizado.grosor.name": "grosor", + "sputnik.setting.autominado.renderizado.grosor.desc": "grosor de las líneas del contorno", + "sputnik.group.autominado.bloques": "bloques", + "sputnik.group.autominado.renderizado": "renderizado", + "sputnik.module.estiloso.name": "estiloso", + "sputnik.module.estiloso.desc": "haz truquitos con items en las manos", + "sputnik.setting.estiloso.general.truco.name": "truco", + "sputnik.setting.estiloso.general.truco.desc": "qué truco hacer al pulsar la tecla", + "sputnik.enum.module.modules.render.stylish.tricktype.spin": "giro", + "sputnik.enum.module.modules.render.stylish.tricktype.flip": "voltereta", + "sputnik.enum.module.modules.render.stylish.tricktype.roll": "balanceo", + "sputnik.enum.module.modules.render.stylish.tricktype.twirl": "giro doble", + "sputnik.enum.module.modules.render.stylish.tricktype.wave": "saludo", + "sputnik.enum.module.modules.render.stylish.tricktype.pop": "pulso", + "sputnik.enum.module.modules.render.stylish.tricktype.throw_catch": "lanzar y coger", + "sputnik.enum.module.modules.render.stylish.tricktype.random": "aleatorio", + "sputnik.setting.estiloso.general.combo.name": "combo", + "sputnik.setting.estiloso.general.combo.desc": "encadena varios trucos distintos en una sola pulsación", + "sputnik.setting.estiloso.general.trucosporcombo.name": "combo tricks", + "sputnik.setting.estiloso.general.trucosporcombo.desc": "cuántos trucos encadenar si el combo está activado", + "sputnik.setting.estiloso.general.mano.name": "mano", + "sputnik.setting.estiloso.general.mano.desc": "en qué mano(s) hacerlo", + "sputnik.setting.estiloso.general.tecla.name": "tecla", + "sputnik.setting.estiloso.general.tecla.desc": "tecla a pulsar para hacer el truco", + "sputnik.setting.estiloso.general.velocidad.name": "velocidad", + "sputnik.setting.estiloso.general.velocidad.desc": "velocidad de giro (grados/seg)", + "sputnik.setting.estiloso.general.vueltas.name": "vueltas", + "sputnik.setting.estiloso.general.vueltas.desc": "cuantas vueltas completas da", + "sputnik.setting.estiloso.general.direccion.name": "dirección", + "sputnik.setting.estiloso.general.direccion.desc": "dirección a rotar el ítem", + "sputnik.enum.module.modules.render.stylish.hand.main_hand": "mano principal", + "sputnik.enum.module.modules.render.stylish.hand.off_hand": "mano secundaria", + "sputnik.setting.estiloso.general.suavizado.name": "suavizado", + "sputnik.setting.estiloso.general.suavizado.desc": "usa una curva de aceleración/desaceleración en vez de velocidad lineal", + "sputnik.setting.estiloso.general.cooldown.name": "cooldown", + "sputnik.setting.estiloso.general.cooldown.desc": "tiempo mínimo en ms entre activaciones", + "sputnik.enum.module.modules.render.stylish.rotatedirection.clockwise": "sentido horario", + "sputnik.enum.module.modules.render.stylish.rotatedirection.counter_clockwise": "sentido antihorario", + "sputnik.setting.hud.elementos.velocidad.name": "velocidad", + "sputnik.setting.hud.elementos.velocidad.desc": "unidad a usar para la velocidad", "sputnik.module.autopechera.desc": "cambia a pechera antes de pegar con el mazo", + "sputnik.setting.nametags.general.distancia.name": "distancia", + "sputnik.setting.nametags.general.distancia.desc": "distancia máxima para renderizar nametags", + "sputnik.setting.putogordo.general.cuandoparar.name": "cuándo parar", + "sputnik.setting.putogordo.general.cuandoparar.desc": "cuántos puntos de comida hace falta tener para parar de comer", + "sputnik.setting.putogordo.general.listanegra.name": "lista negra", + "sputnik.setting.putogordo.general.listanegra.desc": "alimentos a NO comer", "sputnik.module.autopechera.name": "autopechera", "sputnik.module.autopesca.desc": "pesca por ti", "sputnik.module.autopesca.name": "auto pesca", diff --git a/src/main/resources/sputnik.mixins.json b/src/main/resources/sputnik.mixins.json index dcafa58..1978773 100644 --- a/src/main/resources/sputnik.mixins.json +++ b/src/main/resources/sputnik.mixins.json @@ -18,6 +18,7 @@ "mixins.item.ItemMixin", "mixins.item.ItemStackMixin", "mixins.item.TridentItemMixin", + "mixins.misc.ReferenceMixin", "mixins.misc.StringUtilMixin", "mixins.misc.Vec3Mixin", "mixins.network.ClientIntentionPacketMixin",