/* * This file is part of the Combatant Client distribution. * Copyright (c) 2026 pivosos2007. * * Licensed under the GNU General Public License v3.0. */ package dev.loki.lovisual.mixins; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import net.minecraft.client.player.LocalPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import dev.loki.lovisual.events.Events; import dev.loki.lovisual.events.impl.PlayerMoveEvent; import dev.loki.lovisual.events.impl.PlayerStepEvent; import dev.loki.lovisual.events.impl.PlayerStepSuccessEvent; import dev.loki.lovisual.events.impl.PlayerVelocityStrafe; import dev.loki.lovisual.features.module.Modules; import dev.loki.lovisual.features.module.modules.movement.NoPush; import dev.loki.lovisual.features.module.modules.movement.NoStun; import dev.loki.lovisual.mixininterface.IEntity; import dev.loki.lovisual.mixins.accessors.EntityAccessor; import dev.loki.lovisual.mixins.accessors.EntityInvoker; import dev.loki.lovisual.render.helpers.TrailPoint; import dev.loki.lovisual.util.aiming.RotationManager; import java.util.ArrayList; import java.util.List; @Mixin(Entity.class) public abstract class EntityMixin implements IEntity { @Unique private final List trails = new ArrayList<>(); @Shadow public double xOld; @Shadow public double yOld; @Shadow public double zOld; @Unique private MoverType combatant$lastMoveType = MoverType.SELF; @Unique private Vec3 combatant$stepBeforePos = Vec3.ZERO; @Unique private Vec3 combatant$stepRequestedMovement = Vec3.ZERO; @Unique @Override public Vec3 get$InstantRenderPos() { return new Vec3(xOld, yOld, zOld); } @Unique @Override public List combatant$getTrails() { return trails; } @Inject(method = "move", at = @At("HEAD")) private void combatant$cacheMoveType(MoverType type, Vec3 movement, CallbackInfo ci) { combatant$lastMoveType = type; Entity self = (Entity) (Object) this; if (self instanceof LocalPlayer) { combatant$stepBeforePos = self.position(); combatant$stepRequestedMovement = movement; } } @ModifyVariable(method = "move", at = @At("HEAD"), argsOnly = true, index = 2) private Vec3 combatant$onMove(Vec3 movement) { Entity self = (Entity) (Object) this; if (!(self instanceof LocalPlayer)) return movement; if (!Events.BUS.hasListeners(PlayerMoveEvent.class)) return movement; PlayerMoveEvent event = new PlayerMoveEvent(combatant$lastMoveType, movement); Events.BUS.post(event); Vec3 out = event.getMovement(); return out != null ? out : movement; } @Inject(method = "move", at = @At("RETURN")) private void combatant$onMoveReturn(MoverType type, Vec3 movement, CallbackInfo ci) { Entity self = (Entity) (Object) this; if (!(self instanceof LocalPlayer)) return; if (!Events.BUS.hasListeners(PlayerStepSuccessEvent.class)) return; Vec3 before = combatant$stepBeforePos; Vec3 after = self.position(); Vec3 requested = combatant$stepRequestedMovement; double yDelta = after.y - before.y; double horizontalRequested = Math.hypot(requested.x, requested.z); if (yDelta <= 0.5 || horizontalRequested <= 1.0E-5) return; Events.BUS.post(new PlayerStepSuccessEvent(after.subtract(before))); } @ModifyReturnValue(method = "maxUpStep", at = @At("RETURN")) private float combatant$modifyStepHeight(float original) { Entity self = (Entity) (Object) this; if (!(self instanceof LocalPlayer)) return original; if (!Events.BUS.hasListeners(PlayerStepEvent.class)) return original; PlayerStepEvent event = new PlayerStepEvent(original); Events.BUS.post(event); return event.getHeight(); } @Redirect( method = "moveRelative", at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getInputVector(Lnet/minecraft/world/phys/Vec3;FF)Lnet/minecraft/world/phys/Vec3;" ) ) private Vec3 combatant$velocityStrafe(Vec3 movementInput, float speed, float yaw) { Entity self = (Entity) (Object) this; Vec3 vanilla = EntityInvoker.combatant$movementInputToVelocity(movementInput, speed, yaw); if (!(self instanceof LocalPlayer)) return vanilla; if (!Events.BUS.hasListeners(PlayerVelocityStrafe.class)) return vanilla; PlayerVelocityStrafe event = new PlayerVelocityStrafe(movementInput, speed, yaw, vanilla); Events.BUS.post(event); return event.getVelocity(); } @ModifyVariable( method = "calculateViewVector(FF)Lnet/minecraft/world/phys/Vec3;", at = @At("HEAD"), ordinal = 0, argsOnly = true ) private float combatant$rotationVectorPitch(float pitch) { if (!((Object) this instanceof LocalPlayer)) { return pitch; } return RotationManager.INSTANCE.getCurrentRotation() != null ? RotationManager.INSTANCE.getCurrentRotation().pitch() : pitch; } @Redirect( method = "push(Lnet/minecraft/world/entity/Entity;)V", at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V" ) ) private void combatant$cancelEntityPush(Entity instance, double x, double y, double z) { NoPush noPush = Modules.get(NoPush.class); if (noPush == null || !noPush.off("players")) { instance.push(x, y, z); } } @Inject(method = "makeStuckInBlock", at = @At("HEAD"), cancellable = true) private void nostun$cancelSlowMovement(BlockState state, Vec3 multiplier, CallbackInfo ci) { Entity self = (Entity) (Object) this; if (!(self instanceof LocalPlayer)) return; NoStun ns = Modules.get(NoStun.class); if (ns == null || !ns.isEnabled()) return; Vec3 override = ns.getSlowMovementOverride(state, multiplier); if (override != null && self instanceof EntityAccessor accessor) { self.resetFallDistance(); accessor.combatant$setMovementMultiplier(override); ci.cancel(); return; } if (ns.shouldCancelSlowMovement(state)) { ci.cancel(); } } }