Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ plugins {
}


rootProject.name = "myau-injection-template"
rootProject.name = "SUIM"
92 changes: 70 additions & 22 deletions src/main/java/coffee/axle/suim/feature/combat/Displace.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,46 @@
import coffee.axle.suim.util.MyauLogger;
import coffee.axle.suim.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChatComponentText;

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChatComponentText is imported but never used in this class. Please remove the unused import to keep the file clean and avoid warnings.

Suggested change
import net.minecraft.util.ChatComponentText;

Copilot uses AI. Check for mistakes.
import net.minecraft.util.Vec3;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.client.settings.KeyBinding;
import org.lwjgl.input.Mouse;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemSword;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@SuppressWarnings({ "unused" })
public class Displace extends Feature {

private static final Minecraft mc = Minecraft.getMinecraft();
private static final int DISPLACE_WINDOW_TICKS = 10;
private static final String[] DIRECTIONS = {"Left", "Right"};
private static final String[] DIRECTIONS = { "Left", "Right" };

private Object moduleInstance;
private Object yawOffsetProperty;
private Object delayProperty;
private Object directionProperty;
private Object findVoidProperty;
private Object weaponOnly, knockbackOnly;

private boolean displaceThisTick = false;
private boolean active = false;
private boolean compensateNextTick = false;
private boolean displaceLeft = false;
private boolean wasDisplacingLastTick = false;
private boolean hasKB = false;
private Entity attackedLivingEntity = null;
private int tickCounter = 0;
private final Map<Integer, Integer> targetWindowStartTicks = new HashMap<>();

Expand All @@ -61,9 +70,11 @@ public boolean initialize() {
delayProperty = creator.createIntegerProperty("delay-ms", 0, 0, 500);
directionProperty = creator.createEnumProperty("direction", 0, DIRECTIONS);
findVoidProperty = creator.createBooleanProperty("find-void", false);
weaponOnly = creator.createBooleanProperty("weapon-only", true);
knockbackOnly = creator.createBooleanProperty("knockback-only", false);

creator.registerProperties(moduleInstance, yawOffsetProperty, delayProperty,
directionProperty, findVoidProperty);
directionProperty, findVoidProperty, weaponOnly, knockbackOnly);
manager.reloadModuleCommand();

MinecraftForge.EVENT_BUS.register(this);
Expand All @@ -83,6 +94,7 @@ private void onEnable() {
hasKB = false;
compensateNextTick = false;
wasDisplacingLastTick = false;
attackedLivingEntity = null;
tickCounter = 0;
targetWindowStartTicks.clear();
}
Expand All @@ -92,11 +104,13 @@ private void onDisable() {
hasKB = false;
compensateNextTick = false;
wasDisplacingLastTick = false;
attackedLivingEntity = null;
targetWindowStartTicks.clear();
}

private int msToTicks(double ms) {
if (ms <= 0.0) return 0;
if (ms <= 0.0)
return 0;
return (int) Math.ceil(ms / 50.0);
}

Expand All @@ -107,11 +121,19 @@ private boolean anyMovementKey() {
|| mc.gameSettings.keyBindRight.isKeyDown();
}

private boolean tryFindVoidDirection(EntityPlayer target) {
private boolean isHoldingAllowedWeapon() {
ItemStack heldItem = mc.thePlayer.getHeldItem();
if (heldItem == null)
return false;
return heldItem.getItem() instanceof ItemSword || heldItem.getItem() == Items.stick;
}

private boolean tryFindVoidDirection(Entity target) {
double dx = target.posX - mc.thePlayer.posX;
double dz = target.posZ - mc.thePlayer.posZ;
double dist = Math.sqrt(dx * dx + dz * dz);
if (dist < 0.001) return false;
if (dist < 0.001)
return false;
dx /= dist;
dz /= dist;
double rightX = -dz;
Expand All @@ -130,8 +152,10 @@ private boolean tryFindVoidDirection(EntityPlayer target) {
if (mc.theWorld.rayTraceBlocks(new Vec3(lx, eyeY, lz), new Vec3(lx, eyeY - 10, lz)) == null)
leftVoidCount++;
}
if (leftVoidCount == 0 && rightVoidCount == 0) return false;
if (leftVoidCount != rightVoidCount) displaceLeft = leftVoidCount > rightVoidCount;
if (leftVoidCount == 0 && rightVoidCount == 0)
return false;
if (leftVoidCount != rightVoidCount)
displaceLeft = leftVoidCount > rightVoidCount;
return true;
}

Expand All @@ -149,24 +173,27 @@ private void pruneTargetDelayStates() {
}
}

private boolean shouldDisplaceInCurrentWindow(EntityPlayer target, int currentTick) {
if (target == null) return true;
private boolean shouldDisplaceInCurrentWindow(Entity target, int currentTick) {
if (target == null)
return true;
int targetId = target.getEntityId();
Integer windowStartTick = targetWindowStartTicks.get(targetId);
if (windowStartTick == null || currentTick - windowStartTick >= DISPLACE_WINDOW_TICKS) {
targetWindowStartTicks.put(targetId, currentTick);
return true;
}
int delayTicks = msToTicks(properties.getInt(delayProperty, 0));
if (delayTicks <= 0) return true;
if (delayTicks <= 0)
return true;
return currentTick - windowStartTick >= delayTicks;
}

private EntityPlayer findClosestTarget(double rangeSq) {
EntityPlayer closest = null;
double closestDist = rangeSq;
for (EntityPlayer player : mc.theWorld.playerEntities) {
if (player == mc.thePlayer || player.isDead || player.deathTime != 0) continue;
if (player == mc.thePlayer || player.isDead || player.deathTime != 0)
continue;
double dist = mc.thePlayer.getDistanceSqToEntity(player);
if (dist < closestDist) {
closestDist = dist;
Expand All @@ -178,7 +205,8 @@ private EntityPlayer findClosestTarget(double rangeSq) {

@SubscribeEvent(priority = EventPriority.HIGH)
public void onPrePlayerInput(PrePlayerInputEvent e) {
if (!manager.isModuleEnabled(moduleInstance)) return;
if (!manager.isModuleEnabled(moduleInstance))
return;
if (!active) {
compensateNextTick = false;
return;
Expand All @@ -190,16 +218,28 @@ public void onPrePlayerInput(PrePlayerInputEvent e) {
return;
}

if (!displaceThisTick || hasKB) return;
if (!anyMovementKey()) return;
if (!displaceThisTick || hasKB)
return;
if (!anyMovementKey())
return;

e.setForward(1);
compensateNextTick = true;
}

@SubscribeEvent(priority = EventPriority.HIGHEST)
public void onAttack(AttackEntityEvent e) {
if (e.entityPlayer != mc.thePlayer)
return;
if (!(e.target instanceof EntityLivingBase)) return;
// if (!(e.target instanceof EntityPlayer)) return;
Comment on lines +234 to +235

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AttackEntityEvent stores any EntityLivingBase as the target, but the delay-window tracking/pruning logic (targetWindowStartTicks + pruneTargetDelayStates) only retains EntityPlayer entries. For non-player targets the entry will be pruned immediately, making delay/window behavior inconsistent. Either restrict onAttack to EntityPlayer (as the commented line suggests) or update the pruning/window logic to support non-player EntityLivingBase targets.

Suggested change
if (!(e.target instanceof EntityLivingBase)) return;
// if (!(e.target instanceof EntityPlayer)) return;
if (!(e.target instanceof EntityPlayer))
return;

Copilot uses AI. Check for mistakes.
attackedLivingEntity = e.target;
}
Comment on lines +231 to +237

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onAttack updates module state (attackedLivingEntity) even when the module is disabled or when the client/player context is invalid. Other modules (e.g. HitSelect/MoreKB) guard AttackEntityEvent handlers with manager.isModuleEnabled(...) and Utils.nullCheck(). Add those guards here to avoid stale target state carrying over when toggling the module.

Copilot uses AI. Check for mistakes.

@SubscribeEvent(priority = EventPriority.LOW)
public void onPreMotion(PreMotionEvent e) {
if (!manager.isModuleEnabled(moduleInstance)) return;
if (!manager.isModuleEnabled(moduleInstance))
return;
if (!Utils.nullCheck()) {
active = false;
compensateNextTick = false;
Expand All @@ -210,13 +250,17 @@ public void onPreMotion(PreMotionEvent e) {
tickCounter++;
int currentTick = tickCounter;
pruneTargetDelayStates();
boolean knockbackOnlyEnabled = properties.getBoolean(knockbackOnly, false);
boolean hasKBEnchant = EnchantmentHelper.getKnockbackModifier(mc.thePlayer) > 0;
boolean weaponOnlyEnabled = properties.getBoolean(weaponOnly, true);

boolean attacking = Mouse.isButtonDown(0);
EntityPlayer target = attacking ? findClosestTarget(9.0) : null;
Entity target = attackedLivingEntity;
attackedLivingEntity = null;

active = target != null && (hasKBEnchant || anyMovementKey());
hasKB = hasKBEnchant;
boolean weaponAllowed = !weaponOnlyEnabled || isHoldingAllowedWeapon();
boolean knockbackFlag = !knockbackOnlyEnabled || hasKB;
active = target != null && weaponAllowed && (hasKB || anyMovementKey());
Comment thread
Ghosty920 marked this conversation as resolved.
if (!active) {
displaceThisTick = false;
compensateNextTick = false;
Comment on lines +257 to 266

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compensateNextTick compensation can't trigger because it depends on active being true in onPrePlayerInput, but active requires target != null and attackedLivingEntity is always cleared in onPreMotion. Unless AttackEntityEvent fires every tick, the next tick will have active == false and compensateNextTick gets reset, skipping the strafe compensation. Consider keeping the last target for a short window (ticks) or deriving active from keyBindAttack.isKeyDown() while a recent target exists, rather than clearing the target immediately.

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -246,12 +290,16 @@ public void onPreMotion(PreMotionEvent e) {
}
wasDisplacingLastTick = displaceThisTick;

if (!displaceThisTick) return;
if (!displaceThisTick)
return;

float baseYaw = mc.thePlayer.rotationYaw;
float offset = properties.getInt(yawOffsetProperty, 90);
if (displaceLeft) baseYaw -= offset;
else baseYaw += offset;
if (displaceLeft)
baseYaw -= offset;
else
baseYaw += offset;
e.setYaw(baseYaw);
}

}
Loading