diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AttributeSwap.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AttributeSwap.java index ebef608eb0..2091f1b973 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AttributeSwap.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AttributeSwap.java @@ -29,12 +29,15 @@ import net.minecraft.registry.tag.EntityTypeTags; import net.minecraft.registry.tag.ItemTags; import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; public class AttributeSwap extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); private final SettingGroup sgSwappingOptions = settings.createGroup("Swapping Options"); private final SettingGroup sgSwordEnchants = settings.createGroup("Sword Enchants"); private final SettingGroup sgMaceEnchants = settings.createGroup("Mace Enchants"); + private final SettingGroup sgSpearEnchants = settings.createGroup("Spear Enchants"); private final SettingGroup sgOtherEnchants = settings.createGroup("Other Enchants"); private final SettingGroup sgWeapon = settings.createGroup("Weapon Options"); @@ -112,6 +115,14 @@ public class AttributeSwap extends Module { .build() ); + private final Setting spearSwapping = sgSwappingOptions.add(new BoolSetting.Builder() + .name("spear-swapping") + .description("Enables smart swapping for spear enchantments.") + .defaultValue(true) + .visible(() -> mode.get() == Mode.Smart) + .build() + ); + private final Setting otherSwapping = sgSwappingOptions.add(new BoolSetting.Builder() .name("other-swapping") .description("Enables smart swapping for other enchantments like Impaling.") @@ -208,6 +219,30 @@ public class AttributeSwap extends Module { .build() ); + private final Setting enchantLunge = sgSpearEnchants.add(new BoolSetting.Builder() + .name("lunge") + .description("Swaps to a spear with Lunge for traveling.") + .defaultValue(true) + .visible(() -> mode.get() == Mode.Smart && spearSwapping.get()) + .build() + ); + + private final Setting spearHitbox = sgSpearEnchants.add(new BoolSetting.Builder() + .name("hitbox") + .description("Swaps to a spear for extended reach when target is far.") + .defaultValue(true) + .visible(() -> mode.get() == Mode.Smart && spearSwapping.get()) + .build() + ); + + private final Setting excludeLungeFromHitbox = sgSpearEnchants.add(new BoolSetting.Builder() + .name("exclude-lunge-from-hitbox") + .description("Don't use lunge-enchanted spears for hitbox extension.") + .defaultValue(true) + .visible(() -> mode.get() == Mode.Smart && spearSwapping.get() && spearHitbox.get()) + .build() + ); + private final Setting onlyOnWeapon = sgWeapon.add(new BoolSetting.Builder() .name("only-on-weapon") .description("Only swaps when holding a selected weapon in hand.") @@ -286,9 +321,32 @@ public void onDeactivate() { @EventHandler private void onAttack(DoAttackEvent event) { - if (!canSwapByWeapon() || mode.get() == Mode.Smart || !swapOnMiss.get()) return; - if (mc.crosshairTarget.getType() == HitResult.Type.BLOCK) return; + if (mc.crosshairTarget.getType() == HitResult.Type.BLOCK || !canSwapByWeapon()) return; + + if (mode.get() == Mode.Smart && spearSwapping.get()) { + if (spearHitbox.get()) { + Entity target = getTargetEntity(); + if (target != null) { + if (mc.player.distanceTo(target) <= mc.player.getEntityInteractionRange() + 0.5) return; + int spearSlot = getSmartSpearSlot(false); + if (spearSlot != -1) { + doSwap(spearSlot); + return; + } + } + } + // lunge spear for travelling (or when enemy isn't in spear range) + if (enchantLunge.get()) { + int lungeSlot = getSmartSpearSlot(true); + if (lungeSlot != -1) { + doSwap(lungeSlot); + return; + } + } + } + + if (mode.get() == Mode.Smart || !swapOnMiss.get()) return; doSwap(targetSlot.get() - 1); } @@ -382,6 +440,49 @@ private int getSmartSlot(Entity target) { return bestSlot; } + private int getSmartSpearSlot(boolean requireLunge) { + for (int i = 0; i < 9; i++) { + if (i == mc.player.getInventory().getSelectedSlot()) continue; + ItemStack stack = mc.player.getInventory().getStack(i); + if (!stack.isIn(ItemTags.SPEARS)) continue; + + boolean hasLunge = Utils.getEnchantmentLevel(stack, Enchantments.LUNGE) > 0; + if (requireLunge && !hasLunge) continue; + if (!requireLunge && excludeLungeFromHitbox.get() && hasLunge) continue; + + return i; + } + + return -1; + } + + private Entity getTargetEntity() { + double maxDistance = 7; + Vec3d start = mc.player.getCameraPosVec(1.0f); + Vec3d look = mc.player.getRotationVec(1.0f); + Vec3d end = start.add(look.multiply(maxDistance)); + + Box box = mc.player.getBoundingBox().stretch(look.multiply(maxDistance)).expand(1.0); + + Entity target = null; + double closestDistance = maxDistance * maxDistance; + + for (Entity entity : mc.world.getOtherEntities(mc.player, box, e -> !e.isSpectator() && e.canHit())) { + // expanding entity's hitbox by 0.15 to simulate spear's actual hitbox margin + Box expandedBox = entity.getBoundingBox().expand(0.150); + + if (expandedBox.raycast(start, end).isPresent()) { + double distSq = start.squaredDistanceTo(entity.getX(), entity.getY(), entity.getZ()); + if (distSq < closestDistance) { + closestDistance = distSq; + target = entity; + } + } + } + + return target; + } + private double getItemScore(ItemStack stack, boolean isFalling, boolean durability, boolean isLiving, boolean isPlayer, boolean isOnFire, boolean hasFireResistance, boolean isUndead, boolean isArthropod, boolean isAquatic, double armor, float health) { double score = 0;