diff --git a/src/main/java/com/genyo/Genyo.java b/src/main/java/com/genyo/Genyo.java index e300fe5..15951a9 100644 --- a/src/main/java/com/genyo/Genyo.java +++ b/src/main/java/com/genyo/Genyo.java @@ -145,7 +145,12 @@ private void initSystems() { private void initModules(Modules modules) { modules.add(new GenyoAutoEZ()); - modules.add(new AutoPortal()); + // modules.add(new GenyoLogoutPoints()); + modules.add(new GenyoLogoutSpots()); + modules.add(new GenyoNoMineAnimation()); + modules.add(new GenyoAutoPortal()); + modules.add(new MineESP()); + modules.add(new GenyoAutoPortal()); modules.add(new MineESP()); modules.add(new GenyoNuker()); modules.add(new NoSwing()); diff --git a/src/main/java/com/genyo/core/Core.java b/src/main/java/com/genyo/core/Core.java index 2eb6dc9..81163fa 100644 --- a/src/main/java/com/genyo/core/Core.java +++ b/src/main/java/com/genyo/core/Core.java @@ -54,19 +54,26 @@ private float humanizeVolume(float min, float max) { @EventHandler public void onMessageReceive(ReceiveMessageEvent event) { String message = event.getMessage().getString().toLowerCase(); + GenyoConfig cfg = GenyoConfig.get(); + if (cfg == null) return; if (message.contains("genyo") && !message.startsWith("§")) { - Managers.SOUND.playSound(VINE); + if (cfg.vineSound.get()) { + Managers.SOUND.playSound(VINE, cfg.vineVolume.get()); + } } else if (message.contains("verstappen")) { - Managers.SOUND.playSound(VERSTAPPEN); - } else if ((message.contains("nigga") || message.contains("nigger")) && - (GenyoConfig.get().blackPerson.get()) && GenyoConfig.get() != null) { + if (cfg.verstappenSound.get()) { + Managers.SOUND.playSound(VERSTAPPEN, cfg.verstappenVolume.get()); + } + } else if ((message.contains("nigga") || message.contains("nigger")) && cfg.blackPerson.get()) { if (soundTimer.passed(6000)) { Managers.SOUND.playSound(BLACK, 10); soundTimer.reset(); } } else if (message.contains("kiwi")) { - Managers.SOUND.playSound(KIWI); + if (cfg.kiwiSound.get()) { + Managers.SOUND.playSound(KIWI, cfg.kiwiVolume.get()); + } } } @@ -75,7 +82,10 @@ public void onPlayerRender(EntityAddedEvent event) { if (!(event.entity instanceof PlayerEntity player)) return; if (!Enemies.get().isEnemy(player)) return; - Managers.SOUND.playSound(HAMBURGER); + GenyoConfig cfg = GenyoConfig.get(); + if (cfg != null && cfg.hamburgerSound.get()) { + Managers.SOUND.playSound(HAMBURGER, cfg.hamburgerVolume.get()); + } } } diff --git a/src/main/java/com/genyo/managers/combat/CombatManager.java b/src/main/java/com/genyo/managers/combat/CombatManager.java index 5fe767d..39bfeda 100644 --- a/src/main/java/com/genyo/managers/combat/CombatManager.java +++ b/src/main/java/com/genyo/managers/combat/CombatManager.java @@ -4,6 +4,7 @@ import com.genyo.events.UnderCombatEvent; import com.genyo.managers.Managers; import com.genyo.core.sound.SoundManager; +import com.genyo.systems.config.GenyoConfig; import com.genyo.utils.GenyoChatUtils; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.events.entity.player.AttackEntityEvent; @@ -61,7 +62,10 @@ public void onPacketReceive(PacketEvent.Receive event) { "\n\nReason: " + Formatting.GREEN + "ewrhjfkjerkjfhrejkgkregr" + Formatting.GRAY + "\nConclusion: " + Formatting.GREEN + "Skill issue. :("); - Managers.SOUND.playSound(SoundManager.SCREAM, 80); + GenyoConfig cfg = GenyoConfig.get(); + if (cfg != null && cfg.screamSound.get()) { + Managers.SOUND.playSound(SoundManager.SCREAM, cfg.screamVolume.get()); + } } } } diff --git a/src/main/java/com/genyo/mixin/accessor/AccessorCrystalAura.java b/src/main/java/com/genyo/mixin/accessor/AccessorCrystalAura.java new file mode 100644 index 0000000..07747bf --- /dev/null +++ b/src/main/java/com/genyo/mixin/accessor/AccessorCrystalAura.java @@ -0,0 +1,11 @@ +package com.genyo.mixin.accessor; + +import meteordevelopment.meteorclient.systems.modules.combat.CrystalAura; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CrystalAura.class) +public interface AccessorCrystalAura { + @Accessor("placing") + boolean isPlacing(); +} diff --git a/src/main/java/com/genyo/mixin/accessor/AccessorLogoutSpots.java b/src/main/java/com/genyo/mixin/accessor/AccessorLogoutSpots.java new file mode 100644 index 0000000..d4daab5 --- /dev/null +++ b/src/main/java/com/genyo/mixin/accessor/AccessorLogoutSpots.java @@ -0,0 +1,13 @@ +package com.genyo.mixin.accessor; + +import meteordevelopment.meteorclient.systems.modules.render.LogoutSpots; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(LogoutSpots.class) +public interface AccessorLogoutSpots { + @Accessor("players") + List getPlayers(); +} diff --git a/src/main/java/com/genyo/mixin/entity/player/MixinLivingEntityRenderer.java b/src/main/java/com/genyo/mixin/entity/player/MixinLivingEntityRenderer.java new file mode 100644 index 0000000..ee26ef7 --- /dev/null +++ b/src/main/java/com/genyo/mixin/entity/player/MixinLivingEntityRenderer.java @@ -0,0 +1,25 @@ +package com.genyo.mixin.entity.player; + + +import com.genyo.render.FrozenPlayerRenderState; +import com.genyo.render.RenderStateCache; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +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(LivingEntityRenderer.class) +public class MixinLivingEntityRenderer { + + @Inject(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", at = @At("TAIL")) + private void genyo$captureState(LivingEntity entity, LivingEntityRenderState state, float tickDelta, CallbackInfo ci) { + if (entity instanceof PlayerEntity player && state instanceof PlayerEntityRenderState playerState) { + RenderStateCache.put(player.getUuid(), FrozenPlayerRenderState.copyOf(playerState)); + } + } +} diff --git a/src/main/java/com/genyo/mixin/meteor/MixinWWidget.java b/src/main/java/com/genyo/mixin/meteor/MixinWWidget.java index 0a23237..d468849 100644 --- a/src/main/java/com/genyo/mixin/meteor/MixinWWidget.java +++ b/src/main/java/com/genyo/mixin/meteor/MixinWWidget.java @@ -28,7 +28,7 @@ private void onMouseMoved(double mouseX, double mouseY, if (mouseOver && !genyo_wasOver) { GenyoConfig cfg = GenyoConfig.get(); - if (cfg != null && cfg.guiSounds.get()) { + if (cfg != null && cfg.hoverSoundEnabled.get()) { float vol = cfg.hoverVolume.get() / 100f; Managers.SOUND.playUISound(SoundManager.GUI_HOVER, vol, 1f); } @@ -44,14 +44,18 @@ private void onMouseClicked(Click click, boolean doubled, if (!mouseOver) return; GenyoConfig cfg = GenyoConfig.get(); - if (cfg == null || !cfg.guiSounds.get()) return; - - float vol = cfg.clickVolume.get() / 100f; + if (cfg == null) return; if (click.button() == 0) { - Managers.SOUND.playUISound(SoundManager.GUI_CLICK_LEFT, vol, 1f); + if (cfg.clickLeftSoundEnabled.get()) { + float vol = cfg.clickLeftVolume.get() / 100f; + Managers.SOUND.playUISound(SoundManager.GUI_CLICK_LEFT, vol, 1f); + } } else if (click.button() == 1) { - Managers.SOUND.playUISound(SoundManager.GUI_CLICK_RIGHT, vol, 1f); + if (cfg.clickRightSoundEnabled.get()) { + float vol = cfg.clickRightVolume.get() / 100f; + Managers.SOUND.playUISound(SoundManager.GUI_CLICK_RIGHT, vol, 1f); + } } } } diff --git a/src/main/java/com/genyo/mixin/render/MixinWorldRendererBreaking.java b/src/main/java/com/genyo/mixin/render/MixinWorldRendererBreaking.java new file mode 100644 index 0000000..c6b5f15 --- /dev/null +++ b/src/main/java/com/genyo/mixin/render/MixinWorldRendererBreaking.java @@ -0,0 +1,22 @@ +package com.genyo.mixin.render; + +import com.genyo.systems.modules.visual.GenyoNoMineAnimation; +import meteordevelopment.meteorclient.systems.modules.Modules; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.util.math.BlockPos; +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(WorldRenderer.class) +public abstract class MixinWorldRendererBreaking { + + @Inject(method = "setBlockBreakingInfo", at = @At("HEAD"), cancellable = true) + private void genyo$hookSetBlockBreakingInfo(int breakerId, BlockPos pos, int stage, CallbackInfo ci) { + GenyoNoMineAnimation module = Modules.get().get(GenyoNoMineAnimation.class); + if (module != null && module.shouldCancel(breakerId)) { + ci.cancel(); + } + } +} diff --git a/src/main/java/com/genyo/render/FrozenPlayerRenderState.java b/src/main/java/com/genyo/render/FrozenPlayerRenderState.java new file mode 100644 index 0000000..e92ac76 --- /dev/null +++ b/src/main/java/com/genyo/render/FrozenPlayerRenderState.java @@ -0,0 +1,78 @@ +package com.genyo.render; + +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; + +/** A frozen, non-mutating copy of a PlayerEntityRenderState, captured at logout. */ +public class FrozenPlayerRenderState extends PlayerEntityRenderState { + + public static FrozenPlayerRenderState copyOf(PlayerEntityRenderState src) { + FrozenPlayerRenderState f = new FrozenPlayerRenderState(); + + // EntityRenderState + f.entityType = src.entityType; + f.x = src.x; f.y = src.y; f.z = src.z; + f.age = src.age; + f.width = src.width; f.height = src.height; + f.standingEyeHeight = src.standingEyeHeight; + f.invisible = src.invisible; + f.sneaking = src.sneaking; + f.onFire = src.onFire; + f.light = src.light; + f.outlineColor = src.outlineColor; + + // LivingEntityRenderState + f.bodyYaw = src.bodyYaw; + f.relativeHeadYaw = src.relativeHeadYaw; + f.pitch = src.pitch; + f.deathTime = src.deathTime; + f.limbSwingAnimationProgress = src.limbSwingAnimationProgress; + f.limbSwingAmplitude = src.limbSwingAmplitude; + f.baseScale = src.baseScale; + f.ageScale = src.ageScale; + f.flipUpsideDown = src.flipUpsideDown; + f.shaking = src.shaking; + f.baby = src.baby; + f.touchingWater = src.touchingWater; + f.usingRiptide = src.usingRiptide; + f.hurt = src.hurt; + f.pose = src.pose; + + // ArmedEntityRenderState + f.mainArm = src.mainArm; + f.rightArmPose = src.rightArmPose; + f.leftArmPose = src.leftArmPose; + f.rightHandItem = src.rightHandItem.copy(); + f.leftHandItem = src.leftHandItem.copy(); + f.swingAnimationType = src.swingAnimationType; + f.handSwingProgress = src.handSwingProgress; // frozen value; won't animate further, which is intended + + // BipedEntityRenderState + f.leaningPitch = src.leaningPitch; + f.limbAmplitudeInverse = src.limbAmplitudeInverse; + f.crossbowPullTime = src.crossbowPullTime; + f.itemUseTime = src.itemUseTime; + f.preferredArm = src.preferredArm; + f.activeHand = src.activeHand; + f.isInSneakingPose = src.isInSneakingPose; + f.isGliding = src.isGliding; + f.isSwimming = src.isSwimming; + f.hasVehicle = src.hasVehicle; + f.isUsingItem = src.isUsingItem; + f.equippedHeadStack = src.equippedHeadStack.copy(); + f.equippedChestStack = src.equippedChestStack.copy(); + f.equippedLegsStack = src.equippedLegsStack.copy(); + f.equippedFeetStack = src.equippedFeetStack.copy(); + + // PlayerEntityRenderState + f.skinTextures = src.skinTextures; + f.spectator = src.spectator; + f.hatVisible = src.hatVisible; + f.jacketVisible = src.jacketVisible; + f.leftPantsLegVisible = src.leftPantsLegVisible; + f.rightPantsLegVisible = src.rightPantsLegVisible; + f.leftSleeveVisible = src.leftSleeveVisible; + f.rightSleeveVisible = src.rightSleeveVisible; + + return f; + } +} diff --git a/src/main/java/com/genyo/render/PlayerWireframeRenderer.java b/src/main/java/com/genyo/render/PlayerWireframeRenderer.java new file mode 100644 index 0000000..d765368 --- /dev/null +++ b/src/main/java/com/genyo/render/PlayerWireframeRenderer.java @@ -0,0 +1,132 @@ +package com.genyo.render; + +import meteordevelopment.meteorclient.renderer.Renderer3D; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.utils.render.color.Color; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.RotationAxis; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +public class PlayerWireframeRenderer { + + private static final int[][] EDGES = { + {0, 1}, {1, 2}, {2, 3}, {3, 0}, // bottom face + {4, 5}, {5, 6}, {6, 7}, {7, 4}, // top face + {0, 4}, {1, 5}, {2, 6}, {3, 7} // verticals connecting bottom to top + }; + + // 4 corner indices (winding order) per face of the box + private static final int[][] FACES = { + {0, 1, 2, 3}, // bottom + {4, 5, 6, 7}, // top + {0, 1, 5, 4}, + {1, 2, 6, 5}, + {2, 3, 7, 6}, + {3, 0, 4, 7} + }; + + public static void render(Render3DEvent event, ModelPart root, + double originX, double originY, double originZ, + float bodyYaw, Color lineColor, Color sideColor, double lineThickness) { + Renderer3D renderer = event.renderer; + MatrixStack matrices = new MatrixStack(); + + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180.0F - bodyYaw)); + matrices.scale(-1.0F, -1.0F, 1.0F); + matrices.translate(0.0F, -1.501F, 0.0F); + + // Camera position, used to billboard thick-line quads so they face the viewer + var cam = MinecraftClient.getInstance().gameRenderer.getCamera().getCameraPos(); + + + root.forEachCuboid(matrices, (entry, path, index, cuboid) -> { + Matrix4f mat = entry.getPositionMatrix(); + + Vector3f[] corners = new Vector3f[] { + new Vector3f(cuboid.minX / 16f, cuboid.minY / 16f, cuboid.minZ / 16f), + new Vector3f(cuboid.maxX / 16f, cuboid.minY / 16f, cuboid.minZ / 16f), + new Vector3f(cuboid.maxX / 16f, cuboid.minY / 16f, cuboid.maxZ / 16f), + new Vector3f(cuboid.minX / 16f, cuboid.minY / 16f, cuboid.maxZ / 16f), + new Vector3f(cuboid.minX / 16f, cuboid.maxY / 16f, cuboid.minZ / 16f), + new Vector3f(cuboid.maxX / 16f, cuboid.maxY / 16f, cuboid.minZ / 16f), + new Vector3f(cuboid.maxX / 16f, cuboid.maxY / 16f, cuboid.maxZ / 16f), + new Vector3f(cuboid.minX / 16f, cuboid.maxY / 16f, cuboid.maxZ / 16f), + }; + + for (Vector3f corner : corners) mat.transformPosition(corner); + + // Filled side faces + if (sideColor.a > 0) { + for (int[] face : FACES) { + Vector3f a = corners[face[0]]; + Vector3f b = corners[face[1]]; + Vector3f c = corners[face[2]]; + Vector3f d = corners[face[3]]; + + renderer.quad( + originX + a.x, originY + a.y, originZ + a.z, + originX + b.x, originY + b.y, originZ + b.z, + originX + c.x, originY + c.y, originZ + c.z, + originX + d.x, originY + d.y, originZ + d.z, + sideColor + ); + } + } + + // Wireframe edges, thickened into billboarded quads + for (int[] edge : EDGES) { + Vector3f a = corners[edge[0]]; + Vector3f b = corners[edge[1]]; + + drawThickLine( + renderer, + originX + a.x, originY + a.y, originZ + a.z, + originX + b.x, originY + b.y, originZ + b.z, + lineColor, lineThickness, cam.x, cam.y, cam.z + ); + } + }); + } + + /** + * Draws a line as a camera-facing quad so it can be given a world-space thickness. + * Falls back visually to a hairline at very small thickness values. + */ + private static void drawThickLine(Renderer3D renderer, + double x1, double y1, double z1, + double x2, double y2, double z2, + Color color, double thickness, + double camX, double camY, double camZ) { + // Direction of the line segment + double dx = x2 - x1, dy = y2 - y1, dz = z2 - z1; + double len = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len < 1e-6) return; + dx /= len; dy /= len; dz /= len; + + // Vector from line midpoint to camera, used to find a perpendicular + double mx = (x1 + x2) / 2, my = (y1 + y2) / 2, mz = (z1 + z2) / 2; + double toCamX = camX - mx, toCamY = camY - my, toCamZ = camZ - mz; + + // perpendicular = normalize(dir x toCam) + double px = dy * toCamZ - dz * toCamY; + double py = dz * toCamX - dx * toCamZ; + double pz = dx * toCamY - dy * toCamX; + double plen = Math.sqrt(px * px + py * py + pz * pz); + if (plen < 1e-6) return; // line is pointing directly at camera, degenerate + double half = thickness / 32.0; // thickness setting is in "block/16ths"-ish units; tune to taste + px = px / plen * half; + py = py / plen * half; + pz = pz / plen * half; + + renderer.quad( + x1 - px, y1 - py, z1 - pz, + x1 + px, y1 + py, z1 + pz, + x2 + px, y2 + py, z2 + pz, + x2 - px, y2 - py, z2 - pz, + color + ); + } +} diff --git a/src/main/java/com/genyo/render/RenderStateCache.java b/src/main/java/com/genyo/render/RenderStateCache.java new file mode 100644 index 0000000..0e283b2 --- /dev/null +++ b/src/main/java/com/genyo/render/RenderStateCache.java @@ -0,0 +1,34 @@ +package com.genyo.render; + +import meteordevelopment.meteorclient.MeteorClient; +import meteordevelopment.meteorclient.events.game.GameLeftEvent; +import meteordevelopment.orbit.EventHandler; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class RenderStateCache { + private static final Map cache = new ConcurrentHashMap<>(); + + static { + MeteorClient.EVENT_BUS.subscribe(new Object() { + @EventHandler + private void onGameLeft(GameLeftEvent event) { + cache.clear(); + } + }); + } + + public static void put(UUID uuid, FrozenPlayerRenderState state) { + cache.put(uuid, state); + } + + public static FrozenPlayerRenderState freeze(UUID uuid) { + return cache.get(uuid); + } + + public static void clear(UUID uuid) { + cache.remove(uuid); + } +} diff --git a/src/main/java/com/genyo/systems/config/GenyoConfig.java b/src/main/java/com/genyo/systems/config/GenyoConfig.java index 4ce39ec..6fe3ca5 100644 --- a/src/main/java/com/genyo/systems/config/GenyoConfig.java +++ b/src/main/java/com/genyo/systems/config/GenyoConfig.java @@ -43,9 +43,9 @@ public class GenyoConfig extends System { .build() ); - public final Setting guiSounds = sgSounds.add(new BoolSetting.Builder() - .name("gui-sounds") - .description("Play sounds when hovering and clicking in Meteor's GUI.") + public final Setting hoverSoundEnabled = sgSounds.add(new BoolSetting.Builder() + .name("hover-sound") + .description("Play a sound when hovering over a module in Meteor's GUI.") .defaultValue(true) .build() ); @@ -55,16 +55,39 @@ public class GenyoConfig extends System { .description("Volume of the hover sound.") .min(1).defaultValue(60).max(100) .sliderRange(1, 100) - .visible(guiSounds::get) + .visible(hoverSoundEnabled::get) .build() ); - public final Setting clickVolume = sgSounds.add(new IntSetting.Builder() - .name("click-volume") - .description("Volume of left/right click sounds.") + public final Setting clickLeftSoundEnabled = sgSounds.add(new BoolSetting.Builder() + .name("click-left-sound") + .description("Play a sound when left-clicking a module in Meteor's GUI.") + .defaultValue(true) + .build() + ); + + public final Setting clickLeftVolume = sgSounds.add(new IntSetting.Builder() + .name("click-left-volume") + .description("Volume of the left-click sound.") + .min(1).defaultValue(80).max(100) + .sliderRange(1, 100) + .visible(clickLeftSoundEnabled::get) + .build() + ); + + public final Setting clickRightSoundEnabled = sgSounds.add(new BoolSetting.Builder() + .name("click-right-sound") + .description("Play a sound when right-clicking a module in Meteor's GUI.") + .defaultValue(true) + .build() + ); + + public final Setting clickRightVolume = sgSounds.add(new IntSetting.Builder() + .name("click-right-volume") + .description("Volume of the right-click sound.") .min(1).defaultValue(80).max(100) .sliderRange(1, 100) - .visible(guiSounds::get) + .visible(clickRightSoundEnabled::get) .build() ); @@ -91,6 +114,86 @@ public class GenyoConfig extends System { .build() ); + public final Setting vineSound = sgSounds.add(new BoolSetting.Builder() + .name("vine-sound") + .description("Play a sound when someone says 'genyo' in chat.") + .defaultValue(true) + .build() + ); + + public final Setting vineVolume = sgSounds.add(new IntSetting.Builder() + .name("vine-volume") + .description("Volume of the vine sound.") + .min(1).defaultValue(100).max(100) + .sliderRange(1, 100) + .visible(vineSound::get) + .build() + ); + + public final Setting verstappenSound = sgSounds.add(new BoolSetting.Builder() + .name("verstappen-sound") + .description("Play a sound when someone says 'verstappen' in chat.") + .defaultValue(true) + .build() + ); + + public final Setting verstappenVolume = sgSounds.add(new IntSetting.Builder() + .name("verstappen-volume") + .description("Volume of the verstappen sound.") + .min(1).defaultValue(100).max(100) + .sliderRange(1, 100) + .visible(verstappenSound::get) + .build() + ); + + public final Setting kiwiSound = sgSounds.add(new BoolSetting.Builder() + .name("kiwi-sound") + .description("Play a sound when someone says 'kiwi' in chat.") + .defaultValue(true) + .build() + ); + + public final Setting kiwiVolume = sgSounds.add(new IntSetting.Builder() + .name("kiwi-volume") + .description("Volume of the kiwi sound.") + .min(1).defaultValue(100).max(100) + .sliderRange(1, 100) + .visible(kiwiSound::get) + .build() + ); + + public final Setting hamburgerSound = sgSounds.add(new BoolSetting.Builder() + .name("hamburger-sound") + .description("Play a sound when an enemy player renders in.") + .defaultValue(true) + .build() + ); + + public final Setting hamburgerVolume = sgSounds.add(new IntSetting.Builder() + .name("hamburger-volume") + .description("Volume of the hamburger sound.") + .min(1).defaultValue(100).max(100) + .sliderRange(1, 100) + .visible(hamburgerSound::get) + .build() + ); + + public final Setting screamSound = sgSounds.add(new BoolSetting.Builder() + .name("scream-sound") + .description("Play a sound when you die.") + .defaultValue(true) + .build() + ); + + public final Setting screamVolume = sgSounds.add(new IntSetting.Builder() + .name("scream-volume") + .description("Volume of the scream sound.") + .min(1).defaultValue(80).max(100) + .sliderRange(1, 100) + .visible(screamSound::get) + .build() + ); + // Visual public final Setting useGenyoSplashes = sgVisual.add(new BoolSetting.Builder() diff --git a/src/main/java/com/genyo/systems/modules/combat/BasePlace.java b/src/main/java/com/genyo/systems/modules/combat/BasePlace.java index a5d5e65..97217cb 100644 --- a/src/main/java/com/genyo/systems/modules/combat/BasePlace.java +++ b/src/main/java/com/genyo/systems/modules/combat/BasePlace.java @@ -3,6 +3,7 @@ import com.genyo.Genyo; import com.genyo.events.network.PlayerTickEvent; import com.genyo.managers.Managers; +import com.genyo.mixin.accessor.AccessorCrystalAura; import com.genyo.render.animation.Animation; import com.genyo.systems.modules.PlacerModule; import com.genyo.systems.settings.FloatSetting; @@ -126,7 +127,22 @@ public void onDeactivate() { @EventHandler public void onTick(PlayerTickEvent event) { - if (!Modules.get().isActive(GenyoAutoCrystal.class) || !Modules.get().isActive(CrystalAura.class) || Modules.get().get(GenyoAutoCrystal.class).isPlacing()) + boolean genyoActive = Modules.get().isActive(GenyoAutoCrystal.class); + boolean caActive = Modules.get().isActive(CrystalAura.class); + + // Neither aura running -> nothing for base-place to support + if (!genyoActive && !caActive) + { + return; + } + + // Don't interfere while the active aura is itself mid-placement + if (genyoActive && Modules.get().get(GenyoAutoCrystal.class).isPlacing()) + { + return; + } + + if (caActive && ((AccessorCrystalAura) Modules.get().get(CrystalAura.class)).isPlacing()) { return; } @@ -216,7 +232,8 @@ private BlockPos getCrystalBase(PlayerEntity player) continue; } - if (!Modules.get().get(GenyoAutoCrystal.class).isCrystalHitboxClear(pos)) + if (Modules.get().isActive(GenyoAutoCrystal.class) + && !Modules.get().get(GenyoAutoCrystal.class).isCrystalHitboxClear(pos)) { continue; } diff --git a/src/main/java/com/genyo/systems/modules/combat/GenyoAutoTrap.java b/src/main/java/com/genyo/systems/modules/combat/GenyoAutoTrap.java index c8e701d..d6709d1 100644 --- a/src/main/java/com/genyo/systems/modules/combat/GenyoAutoTrap.java +++ b/src/main/java/com/genyo/systems/modules/combat/GenyoAutoTrap.java @@ -4,6 +4,7 @@ import com.genyo.events.network.PlayerTickEvent; import com.genyo.managers.Managers; import com.genyo.systems.modules.PlacerModule; +import com.genyo.systems.modules.visual.GenyoLogoutSpots; import com.genyo.render.animation.Animation; import com.genyo.systems.settings.FloatSetting; import com.genyo.utils.math.GPositionUtils; @@ -122,6 +123,13 @@ public GenyoAutoTrap() { .build() ); + private final Setting useLogoutSpotsConfig = sgGeneral.add(new BoolSetting.Builder() + .name("Use Logout Spots") + .description("Falls back to trapping a frozen logout spot if no live target is found") + .defaultValue(true) + .build() + ); + // Render private final Setting renderConfig = sgRender.add(new BoolSetting.Builder() @@ -182,16 +190,45 @@ public void onPlayerTick(PlayerTickEvent event) placements.clear(); return; } + PlayerEntity trapTarget = getTrapTarget(); - if (trapTarget == null) + BlockPos targetBlockPos; + + if (trapTarget != null) + { + targetBlockPos = GPositionUtils.getRoundedBlockPos(trapTarget.getX(), trapTarget.getY(), trapTarget.getZ()); + surround = getSurround(targetBlockPos, trapTarget.getBoundingBox()); + } + else if (useLogoutSpotsConfig.get()) + { + GenyoLogoutSpots.Spot logout = getNearestLogoutSpot(); + if (logout == null) + { + surround.clear(); + placements.clear(); + return; + } + + double x = logout.state.x; + double y = logout.state.y; + double z = logout.state.z; + double halfWidth = logout.state.width / 2.0; + + targetBlockPos = GPositionUtils.getRoundedBlockPos(x, y, z); + Box targetBox = new Box( + x - halfWidth, y, z - halfWidth, + x + halfWidth, y + logout.state.height, z + halfWidth + ); + + surround = getSurround(targetBlockPos, targetBox); + } + else { surround.clear(); placements.clear(); return; } - BlockPos targetBlockPos = GPositionUtils.getRoundedBlockPos(trapTarget.getX(), trapTarget.getY(), trapTarget.getZ()); - surround = getSurround(targetBlockPos, trapTarget); if (surround.isEmpty()) { return; @@ -314,6 +351,43 @@ private PlayerEntity getTrapTarget() .orElse(null); } + private GenyoLogoutSpots.Spot getNearestLogoutSpot() + { + if (!Modules.get().isActive(GenyoLogoutSpots.class)) + { + return null; + } + + GenyoLogoutSpots module = Modules.get().get(GenyoLogoutSpots.class); + List spots = module.getSpots(); + if (spots.isEmpty()) + { + return null; + } + + GenyoLogoutSpots.Spot best = null; + double bestDist = Double.MAX_VALUE; + + for (GenyoLogoutSpots.Spot spot : spots) + { + if (Managers.SOCIAL.isFriend(spot.name)) + { + continue; + } + + double dist = mc.player.squaredDistanceTo(spot.state.x, spot.state.y, spot.state.z); + if (dist > MathUtil.squared(placeRangeConfig.get()) || dist > bestDist) + { + continue; + } + + bestDist = dist; + best = spot; + } + + return best; + } + public void attackBlockingCrystals(List posList) { for (BlockPos pos : posList) @@ -359,9 +433,14 @@ public List getPlacementsFromSurround(List surround) } public List getSurround(BlockPos playerPos, PlayerEntity player) + { + return getSurround(playerPos, player.getBoundingBox()); + } + + public List getSurround(BlockPos playerPos, Box box) { List surroundBlocks = new ArrayList<>(); - List playerBlocks = getPlayerBlocks(playerPos, player); + List playerBlocks = getPlayerBlocks(playerPos, box); for (BlockPos pos : playerBlocks) { for (Direction dir : Direction.values()) @@ -428,11 +507,16 @@ public List getSurround(BlockPos playerPos, PlayerEntity player) } public List getPlayerBlocks(BlockPos playerPos, PlayerEntity entity) + { + return getPlayerBlocks(playerPos, entity.getBoundingBox()); + } + + public List getPlayerBlocks(BlockPos playerPos, Box box) { final List playerBlocks = new ArrayList<>(); if (extendConfig.get()) { - playerBlocks.addAll(GPositionUtils.getAllInBox(entity.getBoundingBox(), playerPos)); + playerBlocks.addAll(GPositionUtils.getAllInBox(box, playerPos)); } else { diff --git a/src/main/java/com/genyo/systems/modules/combat/MineESP.java b/src/main/java/com/genyo/systems/modules/combat/MineESP.java index 3416d7f..b0ce89d 100644 --- a/src/main/java/com/genyo/systems/modules/combat/MineESP.java +++ b/src/main/java/com/genyo/systems/modules/combat/MineESP.java @@ -17,14 +17,11 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.packet.s2c.play.BlockBreakingProgressS2CPacket; -import net.minecraft.network.packet.s2c.play.EntityAnimationS2CPacket; import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.BlockPos; import org.joml.Vector3d; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class MineESP extends GenyoModule { @@ -34,6 +31,7 @@ public enum RenderMode { } private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgColors = settings.createGroup("Colors"); private final SettingGroup sgRender = settings.createGroup("Render"); private final Setting radius = sgGeneral.add(new DoubleSetting.Builder() @@ -59,6 +57,13 @@ public enum RenderMode { .build() ); + private final Setting showOwnMining = sgGeneral.add(new BoolSetting.Builder() + .name("show-own-mining") + .description("Az ESP a saját bányászásunkat is mutassa.") + .defaultValue(false) + .build() + ); + private final Setting customFont = sgGeneral.add(new BoolSetting.Builder() .name("custom-font") .description("Using Meteor's unique typeface.") @@ -81,6 +86,19 @@ public enum RenderMode { .build() ); + private final Setting doubleMineSideColor = sgColors.add(new ColorSetting.Builder() + .name("double-mine-side-color") + .description("A doboz színe, ha a játékos egyszerre több blokkot bányászik.") + .defaultValue(new SettingColor(255, 165, 0, 100)) + .build() + ); + + private final Setting doubleMineLineColor = sgColors.add(new ColorSetting.Builder() + .name("double-mine-line-color") + .defaultValue(new SettingColor(255, 165, 0, 255)) + .build() + ); + private final Setting textColor = sgRender.add(new ColorSetting.Builder() .name("text-color") .defaultValue(new SettingColor(255, 255, 255, 255)) @@ -112,9 +130,8 @@ public enum RenderMode { ); private final Map blocks = new ConcurrentHashMap<>(); - private final Map suspectedMines = new ConcurrentHashMap<>(); private final Map lastBrokenBlocks = new ConcurrentHashMap<>(); - private final Set swingingEntities = ConcurrentHashMap.newKeySet(); + private BlockPos selfBreakingPos = null; public MineESP() { super(Genyo.COMBAT, "mine-esp", "Featuring ESP Liquid effect, with Double Mine and Rebreak synchronization."); @@ -123,19 +140,12 @@ public MineESP() { @Override public void onDeactivate() { blocks.clear(); - suspectedMines.clear(); lastBrokenBlocks.clear(); - swingingEntities.clear(); + selfBreakingPos = null; } @EventHandler private void onReceivePacket(PacketEvent.Receive event) { - if (event.packet instanceof EntityAnimationS2CPacket animPacket) { - if (animPacket.getAnimationId() == 0 || animPacket.getAnimationId() == 3) { - swingingEntities.add(animPacket.getEntityId()); - } - } - if (event.packet instanceof BlockBreakingProgressS2CPacket packet) { BlockPos pos = packet.getPos(); int stage = packet.getProgress(); @@ -159,23 +169,7 @@ private void onReceivePacket(PacketEvent.Receive event) { @EventHandler private void onTick(TickEvent.Pre event) { - if (syncDoubleMine.get()) { - for (int entityId : swingingEntities) { - Entity entity = mc.world.getEntityById(entityId); - if (entity instanceof PlayerEntity && entity != mc.player) { - HitResult hit = entity.raycast(6.0, 1.0f, false); - if (hit instanceof BlockHitResult blockHit) { - BlockPos hitPos = blockHit.getBlockPos(); - if (!mc.world.getBlockState(hitPos).isAir()) { - suspectedMines.put(entityId, hitPos); - } - } - } - } - } - swingingEntities.clear(); - - suspectedMines.entrySet().removeIf(entry -> mc.world.getBlockState(entry.getValue()).isAir()); + updateOwnMining(); blocks.entrySet().removeIf(entry -> { MineData data = entry.getValue(); @@ -185,6 +179,68 @@ private void onTick(TickEvent.Pre event) { data.renderProgress += (target - data.renderProgress) * 0.15; return false; }); + + if (syncDoubleMine.get()) { + Map activeCounts = new ConcurrentHashMap<>(); + for (MineData data : blocks.values()) { + activeCounts.merge(data.entityId, 1, Integer::sum); + } + for (MineData data : blocks.values()) { + data.isDoubleMine = activeCounts.getOrDefault(data.entityId, 0) > 1; + } + } else { + for (MineData data : blocks.values()) { + data.isDoubleMine = false; + } + } + } + + private void updateOwnMining() { + if (mc.player == null || mc.interactionManager == null || !showOwnMining.get()) { + if (selfBreakingPos != null) { + blocks.remove(selfBreakingPos); + selfBreakingPos = null; + } + return; + } + + boolean breakingNow = mc.interactionManager.isBreakingBlock() && mc.crosshairTarget instanceof BlockHitResult; + + if (!breakingNow) { + if (selfBreakingPos != null) { + // If the block wasn't actually mined (i.e. we aborted), remove it immediately + // instead of waiting for the timeout. A finished block is already air and gets + // cleaned up by the removeIf air-check right after this runs. + if (!mc.world.getBlockState(selfBreakingPos).isAir()) { + blocks.remove(selfBreakingPos); + } + selfBreakingPos = null; + } + return; + } + + BlockHitResult hit = (BlockHitResult) mc.crosshairTarget; + BlockPos pos = hit.getBlockPos(); + int entityId = mc.player.getId(); + + int stage = mc.interactionManager.getBlockBreakingProgress(); + if (stage < 0) { + stage = 0; + } + + if (selfBreakingPos != null && !selfBreakingPos.equals(pos)) { + blocks.remove(selfBreakingPos); + } + + MineData data = blocks.get(pos); + if (data == null) { + boolean isRebreak = pos.equals(lastBrokenBlocks.get(entityId)); + blocks.put(pos, new MineData(pos, entityId, stage, isRebreak)); + } else { + data.targetStage = stage; + data.timer = 0; + } + selfBreakingPos = pos; } @EventHandler @@ -193,12 +249,6 @@ private void onRender3D(Render3DEvent event) { for (MineData data : blocks.values()) { renderBox(event, data.pos, data); - if (syncDoubleMine.get()) { - BlockPos suspected = suspectedMines.get(data.entityId); - if (suspected != null && !suspected.equals(data.pos)) { - renderBox(event, suspected, data); - } - } } } @@ -208,20 +258,24 @@ private void onRender2D(Render2DEvent event) { for (MineData data : blocks.values()) { renderText(event, data.pos, data); - if (syncDoubleMine.get()) { - BlockPos suspected = suspectedMines.get(data.entityId); - if (suspected != null && !suspected.equals(data.pos)) { - renderText(event, suspected, data); - } - } } } private void renderBox(Render3DEvent event, BlockPos pos, MineData data) { if (mc.player.squaredDistanceTo(pos.toCenterPos()) > radius.get() * radius.get()) return; - Color currentSide = data.isRebreak ? rebreakSideColor.get() : lerpColor(sideColorStart.get(), sideColorEnd.get(), data.renderProgress); - Color currentLine = data.isRebreak ? rebreakLineColor.get() : lerpColor(lineColorStart.get(), lineColorEnd.get(), data.renderProgress); + Color currentSide; + Color currentLine; + if (data.isRebreak) { + currentSide = rebreakSideColor.get(); + currentLine = rebreakLineColor.get(); + } else if (data.isDoubleMine) { + currentSide = doubleMineSideColor.get(); + currentLine = doubleMineLineColor.get(); + } else { + currentSide = lerpColor(sideColorStart.get(), sideColorEnd.get(), data.renderProgress); + currentLine = lerpColor(lineColorStart.get(), lineColorEnd.get(), data.renderProgress); + } if (renderMode.get() == RenderMode.Liquid) { double time = System.currentTimeMillis() / 200.0; @@ -263,13 +317,22 @@ private void renderText(Render2DEvent event, BlockPos pos, MineData data) { Vector3d pos2d = new Vector3d(pos3d); if (NametagUtils.to2D(pos2d, 1.5)) { + String tag = null; + Color tagColor = null; + if (data.isRebreak) { + tag = "Rebreak"; + tagColor = rebreakLineColor.get(); + } else if (data.isDoubleMine) { + tag = "Double Mine"; + tagColor = doubleMineLineColor.get(); + } + if (customFont.get()) { NametagUtils.begin(pos3d); TextRenderer.get().begin(1.0, false, true); TextRenderer.get().render(text, -TextRenderer.get().getWidth(text) / 2.0, 0, textColor.get(), true); - if (data.isRebreak) { - String rb = "Rebreak"; - TextRenderer.get().render(rb, -TextRenderer.get().getWidth(rb) / 2.0, 12, rebreakLineColor.get(), true); + if (tag != null) { + TextRenderer.get().render(tag, -TextRenderer.get().getWidth(tag) / 2.0, 12, tagColor, true); } TextRenderer.get().end(); NametagUtils.end(); @@ -278,10 +341,9 @@ private void renderText(Render2DEvent event, BlockPos pos, MineData data) { int x = (int) (pos2d.x / scale) - (mc.textRenderer.getWidth(text) / 2); int y = (int) (pos2d.y / scale); event.drawContext.drawTextWithShadow(mc.textRenderer, text, x, y, textColor.get().getPacked()); - if (data.isRebreak) { - String rb = "Rebreak"; - int rbX = (int) (pos2d.x / scale) - (mc.textRenderer.getWidth(rb) / 2); - event.drawContext.drawTextWithShadow(mc.textRenderer, rb, rbX, y + 10, rebreakLineColor.get().getPacked()); + if (tag != null) { + int tagX = (int) (pos2d.x / scale) - (mc.textRenderer.getWidth(tag) / 2); + event.drawContext.drawTextWithShadow(mc.textRenderer, tag, tagX, y + 10, tagColor.getPacked()); } } } @@ -302,6 +364,7 @@ private static class MineData { public double renderProgress; public int timer; public boolean isRebreak; + public boolean isDoubleMine; public MineData(BlockPos pos, int entityId, int stage, boolean isRebreak) { this.pos = pos; diff --git a/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutPoints.java b/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutPoints.java new file mode 100644 index 0000000..139806e --- /dev/null +++ b/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutPoints.java @@ -0,0 +1,247 @@ +package com.genyo.systems.modules.visual; + +import com.genyo.Genyo; +import com.genyo.render.animation.Animation; +import meteordevelopment.meteorclient.events.packets.PacketEvent; +import meteordevelopment.meteorclient.events.render.Render2DEvent; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.renderer.ShapeMode; +import meteordevelopment.meteorclient.renderer.text.TextRenderer; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.render.NametagUtils; +import meteordevelopment.meteorclient.utils.render.color.Color; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerRemoveS2CPacket; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import org.joml.Vector3d; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class GenyoLogoutPoints extends Module { + + private static final DecimalFormat DECIMAL = new DecimalFormat("#.#"); + + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + + private final Setting showDistance = sgGeneral.add(new BoolSetting.Builder() + .name("distance") + .description("Shows distance from the logout.") + .defaultValue(false) + .build() + ); + + private final Setting showTimePassed = sgGeneral.add(new BoolSetting.Builder() + .name("time-passed") + .description("Shows time passed since logout.") + .defaultValue(false) + .build() + ); + + private final Setting fadeTime = sgGeneral.add(new IntSetting.Builder() + .name("fade-time") + .description("How long the fade in/out animation takes, in milliseconds.") + .min(0) + .defaultValue(500) + .max(2000) + .build() + ); + + private final Setting boxColor = sgGeneral.add(new ColorSetting.Builder() + .name("box-color") + .description("Color of the logout marker outline.") + .defaultValue(new SettingColor(255, 255, 255, 255)) + .build() + ); + + private final Setting textColor = sgGeneral.add(new ColorSetting.Builder() + .name("text-color") + .description("Color of the logout tag text.") + .defaultValue(new SettingColor(255, 255, 255, 255)) + .build() + ); + + private final Map onlinePlayers = new ConcurrentHashMap<>(); + private final Map offlinePlayers = new ConcurrentHashMap<>(); + + public GenyoLogoutPoints() { + super(Genyo.VISUAL, "genyo-logout-points", "Marks nearby logouts."); + } + + @Override + public void onDeactivate() { + onlinePlayers.clear(); + offlinePlayers.clear(); + } + + @EventHandler + public void onTick(TickEvent.Post event) { + if (mc.player == null || mc.world == null) { + return; + } + + for (PlayerEntity player : mc.world.getPlayers()) { + if (player != null && player.getGameProfile() != null && !player.equals(mc.player)) { + onlinePlayers.put(player.getGameProfile().id(), new LogoutData(player, fadeTime.get())); + } + } + } + + @EventHandler + public void onPacketReceive(PacketEvent.Receive event) { + if (mc.player == null || mc.world == null) { + return; + } + + if (event.packet instanceof PlayerListS2CPacket packet + && packet.getActions().contains(PlayerListS2CPacket.Action.ADD_PLAYER)) { + for (PlayerListS2CPacket.Entry entry : packet.getPlayerAdditionEntries()) { + LogoutData data = offlinePlayers.get(entry.profile().id()); + if (data != null) { + data.setState(false); + } + } + + onlinePlayers.clear(); + } else if (event.packet instanceof PlayerRemoveS2CPacket packet) { + List profileIds = packet.profileIds(); + for (UUID uuid : profileIds) { + LogoutData data = onlinePlayers.get(uuid); + if (data == null) { + continue; + } + + if (!offlinePlayers.containsKey(uuid)) { + offlinePlayers.put(uuid, data); + data.setState(true); + } + } + + onlinePlayers.clear(); + } + } + + @EventHandler + public void onRender3D(Render3DEvent event) { + if (mc.player == null || mc.world == null) { + return; + } + + for (Map.Entry set : offlinePlayers.entrySet()) { + LogoutData data = set.getValue(); + if (data == null) { + continue; + } + + double factor = data.getAnimation().getFactor(); + if (!data.getAnimation().getState() && factor <= 0.01) { + offlinePlayers.remove(set.getKey()); + continue; + } + + PlayerEntity offlinePlayer = data.getOfflinePlayer(); + Box box = offlinePlayer.getBoundingBox(); + + Color lineColor = boxColor.get().a((int) (boxColor.get().a * factor)); + Color sideColor = lineColor.a((int) (lineColor.a * 0.4)); + + event.renderer.box( + box.minX, box.minY, box.minZ, + box.maxX, box.maxY, box.maxZ, + sideColor, lineColor, ShapeMode.Lines, 0 + ); + } + } + + @EventHandler + public void onRender2D(Render2DEvent event) { + if (mc.player == null || mc.world == null) { + return; + } + + for (LogoutData data : offlinePlayers.values()) { + double factor = data.getAnimation().getFactor(); + if (factor <= 0.01) { + continue; + } + + PlayerEntity offlinePlayer = data.getOfflinePlayer(); + Box box = offlinePlayer.getBoundingBox(); + + StringBuilder logoutTag = new StringBuilder(offlinePlayer.getName().getString() + "'s Logout"); + + boolean hasExtraInfo = false; + if (showDistance.get()) { + hasExtraInfo = true; + double dist = Math.sqrt(mc.player.squaredDistanceTo(offlinePlayer.getEntityPos())); + logoutTag.append(" - ").append(DECIMAL.format(dist)).append("m"); + } + + if (showTimePassed.get()) { + if (!hasExtraInfo) { + logoutTag.append(" -"); + } + + long seconds = (System.currentTimeMillis() - data.getLogoutTime()) / 1000; + if (seconds < 60) { + logoutTag.append(" ").append(seconds).append("s"); + } else { + long minutes = seconds / 60; + logoutTag.append(" ").append(minutes).append("min"); + } + } + + Vec3d center = box.getCenter(); + Vector3d pos3d = new Vector3d(center.x, box.maxY + 0.2, center.z); + Vector3d pos2d = new Vector3d(pos3d); + + if (NametagUtils.to2D(pos2d, 1.5)) { + Color currentTextColor = textColor.get().a((int) (textColor.get().a * factor)); + String tag = logoutTag.toString(); + + NametagUtils.begin(pos3d); + TextRenderer.get().begin(1.0, false, true); + TextRenderer.get().render(tag, -TextRenderer.get().getWidth(tag) / 2.0, 0, currentTextColor, true); + TextRenderer.get().end(); + NametagUtils.end(); + } + } + } + + private static class LogoutData { + private final PlayerEntity offlinePlayer; + private final long logoutTime; + private final Animation animation; + + public LogoutData(PlayerEntity offlinePlayer, float fadeLength) { + this.offlinePlayer = offlinePlayer; + this.logoutTime = System.currentTimeMillis(); + this.animation = new Animation(false, fadeLength); + } + + public PlayerEntity getOfflinePlayer() { + return offlinePlayer; + } + + public long getLogoutTime() { + return logoutTime; + } + + public Animation getAnimation() { + return animation; + } + + public void setState(boolean state) { + this.animation.setState(state); + } + } +} diff --git a/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutSpots.java b/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutSpots.java new file mode 100644 index 0000000..9aec24c --- /dev/null +++ b/src/main/java/com/genyo/systems/modules/visual/GenyoLogoutSpots.java @@ -0,0 +1,262 @@ +package com.genyo.systems.modules.visual; + + +import com.genyo.render.FrozenPlayerRenderState; +import com.genyo.render.PlayerWireframeRenderer; +import com.genyo.render.RenderStateCache; +import net.minecraft.client.render.entity.model.EntityModelLayer; +import net.minecraft.entity.player.PlayerSkinType; +import meteordevelopment.meteorclient.events.render.Render2DEvent; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.systems.modules.Categories; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.utils.render.color.Color; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.render.entity.model.EntityModelLayers; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class GenyoLogoutSpots extends Module { + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgRender = settings.createGroup("Render"); + + private final Setting lineColor = sgRender.add(new ColorSetting.Builder() + .name("line-color") + .description("Wireframe line color.") + .defaultValue(new SettingColor(85, 220, 255)) + .build() + ); + + private final Setting sideColor = sgRender.add(new ColorSetting.Builder() + .name("side-color") + .description("Fill color for the wireframe's side faces.") + .defaultValue(new SettingColor(255, 0, 255)) + .build() + ); + + private final Setting sideOpacity = sgRender.add(new IntSetting.Builder() + .name("side-opacity") + .description("Opacity of the side face fill (0 = invisible, 255 = solid).") + .defaultValue(30) + .min(0) + .max(255) + .sliderMin(0) + .sliderMax(255) + .build() + ); + + private final Setting lineThickness = sgRender.add(new DoubleSetting.Builder() + .name("line-thickness") + .description("Thickness of the wireframe lines, in blocks.") + .defaultValue(0.150) + .min(0.005) + .sliderMin(0.005) + .sliderMax(0.15) + .build() + ); + + private final Setting showNames = sgRender.add(new BoolSetting.Builder() + .name("show-names") + .description("Renders the player's name above their logout spot.") + .defaultValue(true) + .build() + ); + + private final Setting nameColor = sgRender.add(new ColorSetting.Builder() + .name("name-color") + .description("Color of the player name text.") + .defaultValue(new SettingColor(255, 255, 255)) + .visible(showNames::get) + .build() + ); + + private final Setting maxSpots = sgGeneral.add(new IntSetting.Builder() + .name("max-spots") + .description("Maximum number of logout spots to keep at once.") + .defaultValue(20) + .min(1) + .sliderMin(1) + .build() + ); + + private final List spots = new ArrayList<>(); + + private final List lastTabList = new ArrayList<>(); + private final List lastLoadedPlayers = new ArrayList<>(); + + private int timer; + private DimensionType lastDimension; + + public GenyoLogoutSpots() { + super(Categories.Render, "genyo-logout-spots", "Freezes an exact wireframe of a player at the moment they log out."); + } + + @Override + public void onActivate() { + spots.clear(); + lastTabList.clear(); + lastTabList.addAll(mc.getNetworkHandler().getPlayerList()); + refreshLoadedPlayers(); + + timer = 10; + lastDimension = mc.world.getDimension(); + } + + @Override + public void onDeactivate() { + spots.clear(); + lastTabList.clear(); + } + + private void refreshLoadedPlayers() { + lastLoadedPlayers.clear(); + for (Entity entity : mc.world.getEntities()) { + if (entity instanceof PlayerEntity player && player != mc.player) { + lastLoadedPlayers.add(player); + } + } + } + + @EventHandler + private void onTick(TickEvent.Post event) { + var currentTab = mc.getNetworkHandler().getPlayerList(); + + if (currentTab.size() != lastTabList.size()) { + for (PlayerListEntry oldEntry : lastTabList) { + UUID uuid = oldEntry.getProfile().id(); + + boolean stillInTab = currentTab.stream() + .anyMatch(e -> e.getProfile().id().equals(uuid)); + + if (!stillInTab) { + for (PlayerEntity real : lastLoadedPlayers) { + if (real.getUuid().equals(uuid)) { + addSpot(real); + break; + } + } + } + } + + lastTabList.clear(); + lastTabList.addAll(currentTab); + } + + if (timer <= 0) { + refreshLoadedPlayers(); + timer = 10; + } else { + timer--; + } + + DimensionType dimension = mc.world.getDimension(); + if (dimension != lastDimension) spots.clear(); + lastDimension = dimension; + } + + @EventHandler + private void onEntityAdded(meteordevelopment.meteorclient.events.entity.EntityAddedEvent event) { + // Player reconnected/came back into range — drop the stale spot for them + if (event.entity instanceof PlayerEntity real) { + spots.removeIf(spot -> spot.uuid.equals(real.getUuid())); + } + } + + private void addSpot(PlayerEntity real) { + FrozenPlayerRenderState frozen = RenderStateCache.freeze(real.getUuid()); + if (frozen == null) return; // no captured frame yet (e.g. player never rendered on screen), nothing to freeze + + spots.removeIf(s -> s.uuid.equals(real.getUuid())); + spots.add(new Spot(real.getUuid(), real.getName().getString(), frozen)); + + while (spots.size() > maxSpots.get()) { + spots.remove(0); + } + } + + @EventHandler + private void onRender3D(Render3DEvent event) { + SettingColor side = sideColor.get(); + Color sideWithOpacity = new SettingColor(side.r, side.g, side.b, sideOpacity.get()); + Color line = lineColor.get(); + double thickness = lineThickness.get(); + + for (Spot spot : spots) { + spot.render(event, line, sideWithOpacity, thickness); + } + } + + @EventHandler + private void onRender2D(Render2DEvent event) { + if (!showNames.get() || spots.isEmpty()) return; + + Color color = nameColor.get(); + + for (Spot spot : spots) { + // Head height roughly matches the frozen state's standing eye height + Vec3d worldPos = new Vec3d(spot.state.x, spot.state.y + spot.state.standingEyeHeight + 0.3, spot.state.z); + + Vec3d screen = mc.gameRenderer.project(worldPos); + // project() returns NDC-like coords; z outside [0,1] roughly means behind/outside camera + if (screen.z < 0 || screen.z > 1) continue; + + int x = (int) ((screen.x * 0.5 + 0.5) * mc.getWindow().getScaledWidth()); + int y = (int) ((1.0 - (screen.y * 0.5 + 0.5)) * mc.getWindow().getScaledHeight()); + + DrawContext ctx = event.drawContext; + int textWidth = mc.textRenderer.getWidth(spot.name); + ctx.drawText(mc.textRenderer, spot.name, x - textWidth / 2, y, color.getPacked(), true); + } + } + + @Override + public String getInfoString() { + return Integer.toString(spots.size()); + } + + public List getSpots() { + return spots; + } + + public static class Spot { + public final UUID uuid; + public final String name; + public final FrozenPlayerRenderState state; + + private final ModelPart modelPart; + private final BipedEntityModel model; + + @SuppressWarnings("unchecked") + public Spot(UUID uuid, String name, FrozenPlayerRenderState state) { + this.uuid = uuid; + this.name = name; + this.state = state; + + this.modelPart = net.minecraft.client.MinecraftClient.getInstance() + .getLoadedEntityModels() + .getModelPart(EntityModelLayers.PLAYER); + + this.model = new BipedEntityModel<>(modelPart); + this.model.setAngles(state); + } + + public void render(Render3DEvent event, Color lineColor, Color sideColor, double lineThickness) { + PlayerWireframeRenderer.render(event, modelPart, state.x, state.y, state.z, state.bodyYaw, + lineColor, sideColor, lineThickness); + } + } +} diff --git a/src/main/java/com/genyo/systems/modules/visual/GenyoNoMineAnimation.java b/src/main/java/com/genyo/systems/modules/visual/GenyoNoMineAnimation.java new file mode 100644 index 0000000..f56d5d4 --- /dev/null +++ b/src/main/java/com/genyo/systems/modules/visual/GenyoNoMineAnimation.java @@ -0,0 +1,51 @@ +package com.genyo.systems.modules.visual; + + + +import com.genyo.Genyo; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.systems.modules.Module; + +public class GenyoNoMineAnimation extends Module { + + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + + public final Setting mode = sgGeneral.add(new EnumSetting.Builder() + .name("mode") + .description("Whose block-breaking animation to hide.") + .defaultValue(Mode.Everyone) + .build() + ); + + private final Setting hideParticles = sgGeneral.add(new BoolSetting.Builder() + .name("hide-particles") + .description("Also hides the block-breaking particles.") + .defaultValue(true) + .build() + ); + + public GenyoNoMineAnimation() { + super(Genyo.VISUAL, "genyo-no-mine-animation", "Hides the block-breaking (crack) animation."); + } + + // Used by the mixin to decide whether to cancel a given breaking-info update. + public boolean shouldCancel(int breakerId) { + if (!isActive()) return false; + + boolean isSelf = mc.player != null && breakerId == mc.player.getId(); + + return switch (mode.get()) { + case Everyone -> true; + case OthersOnly -> !isSelf; + case SelfOnly -> isSelf; + }; + } + + public boolean hideParticles() { + return isActive() && hideParticles.get(); + } + + public enum Mode { + Everyone, OthersOnly, SelfOnly + } +} diff --git a/src/main/java/com/genyo/systems/modules/world/AutoPortal.java b/src/main/java/com/genyo/systems/modules/world/GenyoAutoPortal.java similarity index 99% rename from src/main/java/com/genyo/systems/modules/world/AutoPortal.java rename to src/main/java/com/genyo/systems/modules/world/GenyoAutoPortal.java index f069438..b4c0d14 100644 --- a/src/main/java/com/genyo/systems/modules/world/AutoPortal.java +++ b/src/main/java/com/genyo/systems/modules/world/GenyoAutoPortal.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; -public class AutoPortal extends GenyoModule { +public class GenyoAutoPortal extends GenyoModule { private final SettingGroup sgGeneral = settings.getDefaultGroup(); private final SettingGroup sgRender = settings.createGroup("Render"); @@ -81,7 +81,7 @@ public class AutoPortal extends GenyoModule { .build() ); - public AutoPortal() { + public GenyoAutoPortal() { super(Genyo.MISC, "auto-portal", "Builds a Nether portal with Delay, Auto-Refill, and Hotbar Cleanup."); } diff --git a/src/main/java/com/genyo/systems/modules/world/GenyoSelfTrap.java b/src/main/java/com/genyo/systems/modules/world/GenyoSelfTrap.java index 15ab32a..05aba73 100644 --- a/src/main/java/com/genyo/systems/modules/world/GenyoSelfTrap.java +++ b/src/main/java/com/genyo/systems/modules/world/GenyoSelfTrap.java @@ -15,6 +15,7 @@ import meteordevelopment.meteorclient.renderer.ShapeMode; import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.player.AirPlace; import meteordevelopment.meteorclient.utils.player.InvUtils; import meteordevelopment.meteorclient.utils.render.color.Color; import meteordevelopment.meteorclient.utils.render.color.SettingColor; @@ -105,7 +106,14 @@ public GenyoSelfTrap() { private final Setting head = sgGeneral.add(new BoolSetting.Builder() .name("Cover Head") - .description("Doesn't work rn") + .description("Places a block above the player's head.") + .defaultValue(false) + .build() + ); + + private final Setting antiStep = sgGeneral.add(new BoolSetting.Builder() + .name("Prevent Step") + .description("Prevents the player from stepping out of the trap") .defaultValue(false) .build() ); @@ -487,10 +495,50 @@ public List getSurround(PlayerEntity player) } } - /*if (AirPlaceModule.getInstance().isEnabled() && head.get()) + if (head.get()) { - surroundBlocks.add(mc.player.getBlockPos().up(2)); - }*/ + boolean supported = false; + final List headBlocks = new ArrayList<>(); + for (BlockPos pos : playerBlocks) + { + BlockPos headPos = pos.offset(Direction.UP, 2); + if (!mc.world.getBlockState(headPos).isReplaceable()) + { + supported = true; + } + headBlocks.add(headPos); + if (antiStep.get()) + { + BlockPos antiStepPos = pos.offset(Direction.UP, 3); + headBlocks.add(antiStepPos); + } + } + if (!Modules.get().isActive(AirPlace.class)) + { + BlockPos supportingPos = null; + double min = Double.MAX_VALUE; + for (BlockPos pos : surroundBlocks) + { + BlockPos pos1 = pos.offset(Direction.UP, 2); + if (!mc.world.getBlockState(pos1).isReplaceable()) + { + supported = true; + break; + } + double dist = mc.player.squaredDistanceTo(pos1.toCenterPos()); + if (dist < min) + { + supportingPos = pos1; + min = dist; + } + } + if (supportingPos != null && !supported) + { + surroundBlocks.add(supportingPos); + } + } + surroundBlocks.addAll(headBlocks); + } return surroundBlocks; } diff --git a/src/main/resources/genyo.mixins.json b/src/main/resources/genyo.mixins.json index 2a506b6..ebbe14a 100644 --- a/src/main/resources/genyo.mixins.json +++ b/src/main/resources/genyo.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_21", "client": [ "ClientPlayerInteractionManagerMixin", + "entity.player.MixinLivingEntityRenderer", "FriendsInjector", "MixinConnectScreen", "MixinItemUsageContext", @@ -11,6 +12,7 @@ "PlayerUtilsMixin", "accessor.AccessorAnvilScreen", "accessor.AccessorBundlePacket", + "accessor.AccessorCrystalAura", "accessor.AccessorClientPlayerInteractionManager", "accessor.AccessorClientWorld", "accessor.AccessorGameOptions", @@ -24,7 +26,8 @@ "render.MixinEntityRenderer", "render.MixinRenderTickCounter", "render.MixinWorldRenderer", - "world.MixinClientWorld" + "world.MixinClientWorld", + "render.MixinWorldRendererBreaking" ], "injectors": { "defaultRequire": 1