add CrystalAura combat module
Places and breaks end crystals around the nearest non-friend player, ported from telaviv's CatAura: damage-sorted target calc with min/self damage and anti-suicide gates, place modes (Normal/AirPlace/Support/ AutoSurroundAnchor), break delays with inhibit and anti-weakness, silent or normal auto-switch, rotation via the rotation manager, and a box render of the current placement. Dropped the dead AntiFeetPlace toggle (never read upstream) and the unused Sequence setting.
This commit is contained in:
@@ -0,0 +1,929 @@
|
||||
package cc.fros.features.modules.combat;
|
||||
|
||||
import cc.fros.Fros;
|
||||
import cc.fros.event.impl.entity.player.TickEvent;
|
||||
import cc.fros.event.impl.render.Render3DEvent;
|
||||
import cc.fros.event.system.Subscribe;
|
||||
import cc.fros.features.modules.Module;
|
||||
import cc.fros.features.settings.Setting;
|
||||
import cc.fros.util.inventory.InventoryUtil;
|
||||
import cc.fros.util.inventory.ResultType;
|
||||
import cc.fros.util.models.Angles;
|
||||
import cc.fros.util.models.Timer;
|
||||
import cc.fros.util.render.RenderUtil;
|
||||
import cc.fros.util.world.BlockUtil;
|
||||
import cc.fros.util.world.CrystalUtil;
|
||||
import cc.fros.util.world.TargetUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket;
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RespawnAnchorBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* CrystalAura -- places and breaks end crystals to kill players.
|
||||
* Ported from telaviv's {@code CatAura}.
|
||||
*/
|
||||
public class CrystalAuraModule extends Module {
|
||||
// ---- Calc ----
|
||||
private final Setting<Page> page = mode("Page", Page.Calc);
|
||||
private final Setting<Sort> sort = mode("Sort", Sort.Damage);
|
||||
private final Setting<Double> targetRange = num("TargetRange", 7.0, 3.0, 20.0);
|
||||
private final Setting<Double> minDmg = num("MinDamage", 2.0, 0.0, 36.0);
|
||||
private final Setting<Double> maxSelfDmg = num("MaxSelfDamage", 4.0, 0.0, 36.0);
|
||||
private final Setting<Boolean> noSuicide = bool("AntiSuicide", false);
|
||||
private final Setting<Integer> lethalCrystals = num("LethalCrystals", 0, 0, 5);
|
||||
private final Setting<Boolean> terrain = bool("Terrain", false);
|
||||
private final Setting<MultiTask> multiTask = mode("MultiTask", MultiTask.None);
|
||||
|
||||
// ---- Place ----
|
||||
private final Setting<PlaceMode> placeMode = mode("PlaceMode", PlaceMode.Normal);
|
||||
private final Setting<Double> placeRange = num("PlaceRange", 6.0, 1.0, 6.0);
|
||||
private final Setting<Double> placeWallsRange = num("PlaceWallsRange", 3.0, 1.0, 6.0);
|
||||
private final Setting<Integer> placeDelay = num("PlaceDelay", 1, 0, 1000);
|
||||
private final Setting<AutoSwitch> autoSwitch = mode("AutoSwitch", AutoSwitch.Normal);
|
||||
private final Setting<Boolean> pause = bool("Pause", false);
|
||||
private final Setting<Boolean> strictDirection = bool("StrictDirection", false);
|
||||
private final Setting<Boolean> onePointTwelve = bool("1.12", false);
|
||||
|
||||
// ---- AutoSurroundAnchor ----
|
||||
private final Setting<Integer> chargeDelay = num("ChargeDelay", 2, 0, 20);
|
||||
private final Setting<Integer> explodeDelay = num("ExplodeDelay", 1, 0, 20);
|
||||
|
||||
// ---- Break ----
|
||||
private final Setting<Double> breakRange = num("BreakRange", 6.0, 1.0, 6.0);
|
||||
private final Setting<Double> breakWallsRange = num("BreakWallsRange", 3.0, 1.0, 6.0);
|
||||
private final Setting<Integer> breakDelay = num("BreakDelay", 1, 0, 1000);
|
||||
private final Setting<Integer> ticksExisted = num("TicksExisted", 0, 0, 10);
|
||||
private final Setting<Boolean> inhibit = bool("Inhibit", false);
|
||||
private final Setting<AntiWeakness> antiWeakness = mode("AntiWeakness", AntiWeakness.None);
|
||||
|
||||
// ---- Misc ----
|
||||
private final Setting<Boolean> rotate = bool("Rotate", false);
|
||||
private final Setting<RotationType> rotationType = mode("RotationType", RotationType.Simple);
|
||||
|
||||
// ---- Render ----
|
||||
private final Setting<Color> fillColor = color("FillColor", 0, 0, 0, 100);
|
||||
private final Setting<Color> lineColor = color("OutlineColor", 255, 255, 255, 255);
|
||||
|
||||
private final Timer placeTimer = new Timer();
|
||||
private final Timer breakTimer = new Timer();
|
||||
|
||||
public final Map<Integer, Long> hitCrystals = new ConcurrentHashMap<>();
|
||||
|
||||
private Player target;
|
||||
private double displayDmg;
|
||||
private boolean offhand;
|
||||
private boolean placeIsSupport;
|
||||
|
||||
// AutoSurroundAnchor state
|
||||
private enum AnchorState { IDLE, BREAKING, PLACING_ANCHOR, CHARGING, EXPLODING }
|
||||
private AnchorState anchorState = AnchorState.IDLE;
|
||||
private BlockPos anchorPos;
|
||||
private int anchorTimer;
|
||||
private int lastSlot;
|
||||
|
||||
public BlockPos renderPos;
|
||||
private BlockPos calcPos;
|
||||
private BlockPos lastPos;
|
||||
private Angles placeRots;
|
||||
private Angles breakRots;
|
||||
private Entity calcCrystal;
|
||||
private double renderDmg;
|
||||
|
||||
public CrystalAuraModule() {
|
||||
super("CrystalAura", "Places and breaks crystals to kill players.", Category.COMBAT);
|
||||
refreshVisibility();
|
||||
}
|
||||
|
||||
private void refreshVisibility() {
|
||||
sort.setVisibility(v -> on(Page.Calc));
|
||||
targetRange.setVisibility(v -> on(Page.Calc));
|
||||
minDmg.setVisibility(v -> on(Page.Calc));
|
||||
maxSelfDmg.setVisibility(v -> on(Page.Calc));
|
||||
noSuicide.setVisibility(v -> on(Page.Calc));
|
||||
lethalCrystals.setVisibility(v -> on(Page.Calc));
|
||||
terrain.setVisibility(v -> on(Page.Calc));
|
||||
multiTask.setVisibility(v -> on(Page.Calc));
|
||||
|
||||
placeMode.setVisibility(v -> on(Page.Place));
|
||||
placeRange.setVisibility(v -> on(Page.Place));
|
||||
placeWallsRange.setVisibility(v -> on(Page.Place));
|
||||
placeDelay.setVisibility(v -> on(Page.Place));
|
||||
autoSwitch.setVisibility(v -> on(Page.Place));
|
||||
pause.setVisibility(v -> on(Page.Place) && autoSwitch.getValue() == AutoSwitch.Normal);
|
||||
strictDirection.setVisibility(v -> on(Page.Place));
|
||||
onePointTwelve.setVisibility(v -> on(Page.Place));
|
||||
|
||||
chargeDelay.setVisibility(v -> on(Page.Place) && placeMode.getValue() == PlaceMode.AutoSurroundAnchor);
|
||||
explodeDelay.setVisibility(v -> on(Page.Place) && placeMode.getValue() == PlaceMode.AutoSurroundAnchor);
|
||||
|
||||
breakRange.setVisibility(v -> on(Page.Break));
|
||||
breakWallsRange.setVisibility(v -> on(Page.Break));
|
||||
breakDelay.setVisibility(v -> on(Page.Break));
|
||||
ticksExisted.setVisibility(v -> on(Page.Break));
|
||||
inhibit.setVisibility(v -> on(Page.Break));
|
||||
antiWeakness.setVisibility(v -> on(Page.Break));
|
||||
|
||||
rotate.setVisibility(v -> on(Page.Misc));
|
||||
rotationType.setVisibility(v -> on(Page.Misc) && rotate.getValue());
|
||||
|
||||
fillColor.setVisibility(v -> on(Page.Render));
|
||||
lineColor.setVisibility(v -> on(Page.Render));
|
||||
}
|
||||
|
||||
private boolean on(Page p) {
|
||||
return page.getValue() == p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
resetState();
|
||||
placeTimer.reset();
|
||||
breakTimer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
resetState();
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
renderPos = null;
|
||||
target = null;
|
||||
displayDmg = 0;
|
||||
calcPos = null;
|
||||
calcCrystal = null;
|
||||
placeRots = null;
|
||||
breakRots = null;
|
||||
lastPos = null;
|
||||
placeIsSupport = false;
|
||||
|
||||
anchorState = AnchorState.IDLE;
|
||||
anchorPos = null;
|
||||
anchorTimer = 0;
|
||||
lastSlot = -1;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPreTick(TickEvent.Pre event) {
|
||||
if (nullCheck() || !mc.player.isAlive()) return;
|
||||
|
||||
if (isMultiTask()) {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare();
|
||||
|
||||
if (rotate.getValue() && actionRots() != null) {
|
||||
if (rotationType.getValue() == RotationType.Silent) {
|
||||
Fros.rotationManager.silent(actionRots(), this::interact);
|
||||
} else {
|
||||
Fros.rotationManager.motion(actionRots(), 0, this::interact);
|
||||
}
|
||||
} else {
|
||||
interact();
|
||||
}
|
||||
}
|
||||
|
||||
private Angles actionRots() {
|
||||
if (calcCrystal != null && breakRots != null) return breakRots;
|
||||
return placeRots;
|
||||
}
|
||||
|
||||
private boolean isMultiTask() {
|
||||
if (!mc.player.isUsingItem()) return false;
|
||||
if (multiTask.getValue() == MultiTask.Strong) return true;
|
||||
if (multiTask.getValue() == MultiTask.Soft) {
|
||||
return mc.player.getUsedItemHand() != InteractionHand.OFF_HAND && !offhand;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Target / calculation
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private void prepare() {
|
||||
offhand = mc.player.getOffhandItem().getItem() == Items.END_CRYSTAL;
|
||||
|
||||
calcPos = null;
|
||||
calcCrystal = null;
|
||||
placeRots = null;
|
||||
breakRots = null;
|
||||
|
||||
// Handle AutoSurroundAnchor mode
|
||||
if (placeMode.getValue() == PlaceMode.AutoSurroundAnchor) {
|
||||
handleAutoSurroundAnchor();
|
||||
return;
|
||||
}
|
||||
|
||||
Result result = getTargetResult();
|
||||
if (result == null) return;
|
||||
|
||||
load(result);
|
||||
|
||||
if (inhibit.getValue() && calcCrystal != null && hitCrystals.containsKey(calcCrystal.getId())) {
|
||||
breakTimer.setMs(breakDelay.getValue().longValue() + 100L);
|
||||
}
|
||||
}
|
||||
|
||||
private void load(Result result) {
|
||||
target = result.target;
|
||||
displayDmg = result.getDamage();
|
||||
if (crystalSlot() != -1 || offhand) {
|
||||
calcPos = result.calcPos;
|
||||
placeRots = result.placeRots;
|
||||
renderDmg = result.bestPlaceDmg;
|
||||
placeIsSupport = result.placeIsSupport;
|
||||
if (calcPos == null && renderDmg == 0) renderPos = null;
|
||||
} else {
|
||||
calcPos = null;
|
||||
placeRots = null;
|
||||
renderDmg = 0;
|
||||
placeIsSupport = false;
|
||||
}
|
||||
calcCrystal = result.calcCrystal;
|
||||
breakRots = result.breakRots;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// AutoSurroundAnchor - Break surround, place anchor, charge, explode
|
||||
// ------------------------------------------------------------------
|
||||
private void handleAutoSurroundAnchor() {
|
||||
if (target == null || !target.isAlive() || target.isRemoved()) {
|
||||
LivingEntity t = TargetUtil.getTarget(targetRange.getValue());
|
||||
if (t instanceof Player p) target = p;
|
||||
else {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// State machine for anchor sequence
|
||||
switch (anchorState) {
|
||||
case IDLE -> {
|
||||
// Find the block below target (their feet block)
|
||||
BlockPos footPos = target.blockPosition().below();
|
||||
if (!canBreakSurroundBlock(footPos)) {
|
||||
// Try to find a breakable surround block around them
|
||||
footPos = findBreakableSurroundBlock(target);
|
||||
if (footPos == null) return;
|
||||
}
|
||||
anchorPos = footPos;
|
||||
anchorState = AnchorState.BREAKING;
|
||||
anchorTimer = 0;
|
||||
// Re-find target each cycle
|
||||
LivingEntity t = TargetUtil.getTarget(targetRange.getValue());
|
||||
if (t instanceof Player p) target = p;
|
||||
else return;
|
||||
}
|
||||
case BREAKING -> {
|
||||
if (!breakSurroundBlock(anchorPos)) return;
|
||||
// Block broken, place anchor next
|
||||
anchorState = AnchorState.PLACING_ANCHOR;
|
||||
anchorTimer = 0;
|
||||
}
|
||||
case PLACING_ANCHOR -> {
|
||||
if (!placeAnchor(anchorPos)) return;
|
||||
anchorState = AnchorState.CHARGING;
|
||||
anchorTimer = 0;
|
||||
}
|
||||
case CHARGING -> {
|
||||
if (!chargeAnchor(anchorPos)) return;
|
||||
anchorState = AnchorState.EXPLODING;
|
||||
anchorTimer = 0;
|
||||
}
|
||||
case EXPLODING -> {
|
||||
if (!explodeAnchor(anchorPos)) return;
|
||||
// Sequence complete
|
||||
anchorState = AnchorState.IDLE;
|
||||
anchorPos = null;
|
||||
anchorTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canBreakSurroundBlock(BlockPos pos) {
|
||||
if (mc.level.getBlockState(pos).isAir()) return false;
|
||||
// Check if we can reach and break this block
|
||||
double distance = mc.player.getEyePosition().distanceTo(Vec3.atCenterOf(pos));
|
||||
if (distance > breakRange.getValue()) return false;
|
||||
// Can't break bedrock
|
||||
if (mc.level.getBlockState(pos).getBlock() == Blocks.BEDROCK) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private BlockPos findBreakableSurroundBlock(Player target) {
|
||||
// Check blocks around the target (surround positions)
|
||||
for (Direction dir : new Direction[]{Direction.DOWN, Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST}) {
|
||||
BlockPos pos = target.blockPosition().relative(dir);
|
||||
if (canBreakSurroundBlock(pos)) return pos;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean breakSurroundBlock(BlockPos pos) {
|
||||
if (anchorTimer++ < 1) return false; // Wait 1 tick
|
||||
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
if (state.isAir()) {
|
||||
// Already broken
|
||||
return true;
|
||||
}
|
||||
|
||||
// Break the block
|
||||
Direction side = BlockUtil.getDirection(pos, false);
|
||||
if (side == null) return false;
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
int pickaxeSlot = InventoryUtil.find(stack -> stack.is(ItemTags.PICKAXES), InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
if (pickaxeSlot == -1) pickaxeSlot = oldSlot;
|
||||
|
||||
InventoryUtil.swap(pickaxeSlot);
|
||||
mc.gameMode.destroyBlock(pos);
|
||||
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||
InventoryUtil.swap(oldSlot);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean placeAnchor(BlockPos pos) {
|
||||
if (anchorTimer++ < 1) return false; // Wait 1 tick
|
||||
|
||||
// Check if block is now air
|
||||
if (!mc.level.getBlockState(pos).isAir()) return false;
|
||||
|
||||
// Find respawn anchor in hotbar
|
||||
int anchorSlot = InventoryUtil.find(Items.RESPAWN_ANCHOR, InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
if (anchorSlot == -1) return false;
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
lastSlot = oldSlot;
|
||||
|
||||
InventoryUtil.swap(anchorSlot);
|
||||
Direction side = BlockUtil.getPlaceableSideCrystal(pos, strictDirection.getValue());
|
||||
if (side == null) {
|
||||
InventoryUtil.swap(oldSlot);
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec3 vec = pos.getCenter().add(side.getStepX() * 0.5, side.getStepY() * 0.5, side.getStepZ() * 0.5);
|
||||
if (side.getAxis().isHorizontal()) vec = vec.add(0, 0.45, 0);
|
||||
|
||||
BlockHitResult hit = new BlockHitResult(vec, side, pos, false);
|
||||
mc.gameMode.useItemOn(mc.player, InteractionHand.MAIN_HAND, hit);
|
||||
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||
|
||||
// Swap back silently
|
||||
InventoryUtil.swap(oldSlot);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean chargeAnchor(BlockPos pos) {
|
||||
if (anchorTimer++ < chargeDelay.getValue()) return false; // Configurable delay
|
||||
|
||||
// Check anchor exists and get charges
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
if (state.getBlock() != Blocks.RESPAWN_ANCHOR) return false;
|
||||
|
||||
int charges = state.getValue(RespawnAnchorBlock.CHARGE);
|
||||
if (charges > 0) {
|
||||
// Already charged, ready to explode
|
||||
anchorTimer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Need glowstone
|
||||
int glowstoneSlot = InventoryUtil.find(Items.GLOWSTONE, InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
if (glowstoneSlot == -1) return false;
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
InventoryUtil.swap(glowstoneSlot);
|
||||
|
||||
// Right-click the anchor to charge
|
||||
Vec3 center = Vec3.atCenterOf(pos).add(0, 1.0, 0); // Anchor hitbox center
|
||||
Direction side = BlockUtil.getDirection(pos, false);
|
||||
if (side == null) side = Direction.UP;
|
||||
BlockHitResult hit = new BlockHitResult(center, side, pos, true);
|
||||
|
||||
mc.gameMode.useItemOn(mc.player, InteractionHand.MAIN_HAND, hit);
|
||||
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||
|
||||
InventoryUtil.swap(oldSlot);
|
||||
anchorTimer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean explodeAnchor(BlockPos pos) {
|
||||
if (anchorTimer++ < explodeDelay.getValue()) return false; // Configurable delay
|
||||
|
||||
// Check anchor exists and is charged
|
||||
BlockState state = mc.level.getBlockState(pos);
|
||||
if (state.getBlock() != Blocks.RESPAWN_ANCHOR) return false;
|
||||
|
||||
int charges = state.getValue(RespawnAnchorBlock.CHARGE);
|
||||
if (charges == 0) return false;
|
||||
|
||||
// Find any non-glowstone item
|
||||
int itemSlot = InventoryUtil.find(stack -> !stack.is(Items.GLOWSTONE), InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
if (itemSlot == -1) itemSlot = lastSlot;
|
||||
if (itemSlot == -1) return false;
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
InventoryUtil.swap(itemSlot);
|
||||
|
||||
// Right-click the anchor to explode
|
||||
Vec3 center = Vec3.atCenterOf(pos).add(0, 1.0, 0);
|
||||
Direction side = BlockUtil.getDirection(pos, false);
|
||||
if (side == null) side = Direction.UP;
|
||||
BlockHitResult hit = new BlockHitResult(center, side, pos, true);
|
||||
|
||||
mc.gameMode.useItemOn(mc.player, InteractionHand.MAIN_HAND, hit);
|
||||
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||
|
||||
InventoryUtil.swap(oldSlot);
|
||||
anchorTimer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
private Result getTargetResult() {
|
||||
if (sort.getValue() == Sort.Range) {
|
||||
LivingEntity t = TargetUtil.getTarget(targetRange.getValue());
|
||||
if (t == null) {
|
||||
resetState();
|
||||
return null;
|
||||
}
|
||||
return getResult((Player) t);
|
||||
}
|
||||
|
||||
List<LivingEntity> targets = TargetUtil.getTargets(targetRange.getValue());
|
||||
Result best = null;
|
||||
for (Entity entity : targets) {
|
||||
Result current = getResult((Player) entity);
|
||||
if (best == null || current.getDamage() > best.getDamage()) best = current;
|
||||
}
|
||||
if (best == null) {
|
||||
resetState();
|
||||
return null;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private Result getResult(Player player) {
|
||||
BreakResult breakResult = getBestBreakResult(player);
|
||||
return new Result(player, breakResult, getBestPlaceResult(player, breakResult.calcCrystal));
|
||||
}
|
||||
|
||||
private BreakResult getBestBreakResult(Player targetPlayer) {
|
||||
Entity bestCrystal = null;
|
||||
double bestDamage = 0.5;
|
||||
|
||||
for (Entity entity : mc.level.entitiesForRendering()) {
|
||||
if (!(entity instanceof EndCrystal crystal)) continue;
|
||||
if (!canBreakCrystal(crystal)) continue;
|
||||
|
||||
float targetDamage = CrystalUtil.calculateDamage(targetPlayer, entity.position(), terrain.getValue());
|
||||
boolean lethal = lethalCrystals.getValue() != 0
|
||||
&& targetDamage * lethalCrystals.getValue() >= targetPlayer.getHealth() + targetPlayer.getAbsorptionAmount();
|
||||
|
||||
if (!(bestDamage < targetDamage && targetDamage > minDmg.getValue()) && !lethal) continue;
|
||||
|
||||
float selfDamage = CrystalUtil.calculateDamage(mc.player, entity.position(), terrain.getValue());
|
||||
if (selfDamage > maxSelfDmg.getValue()) continue;
|
||||
if (selfDamage + 2 > mc.player.getHealth() + mc.player.getAbsorptionAmount() && noSuicide.getValue()) continue;
|
||||
|
||||
bestDamage = targetDamage;
|
||||
bestCrystal = entity;
|
||||
}
|
||||
|
||||
Angles rots = null;
|
||||
if (bestCrystal != null && rotate.getValue()) {
|
||||
rots = Angles.pos(new Vec3(bestCrystal.getX(), bestCrystal.getEyeY(), bestCrystal.getZ()));
|
||||
}
|
||||
return new BreakResult(bestDamage, bestCrystal, rots);
|
||||
}
|
||||
|
||||
private PlaceResult getBestPlaceResult(Player targetPlayer, Entity currentCalcCrystal) {
|
||||
BlockPos bestPos = null;
|
||||
double bestDamage = 0.5;
|
||||
boolean isSupport = false;
|
||||
|
||||
List<BlockPos> sphere = BlockUtil.sphere(placeRange.getValue() + 1.0, mc.player.blockPosition(), true, false);
|
||||
for (BlockPos pos : sphere) {
|
||||
boolean hasBlock = CrystalUtil.canPlaceCrystal(pos, onePointTwelve.getValue());
|
||||
boolean canAirPlace = placeMode.getValue() != PlaceMode.Normal && CrystalUtil.canPlaceCrystalAir(pos);
|
||||
boolean canSupport = placeMode.getValue() == PlaceMode.Support && mc.level.getBlockState(pos).canBeReplaced();
|
||||
|
||||
if (!hasBlock && !canAirPlace && !canSupport) continue;
|
||||
if (!handleBBCrystal(pos, lastPos, currentCalcCrystal)) continue;
|
||||
if (!checkPlace(pos)) continue;
|
||||
|
||||
// For support placements, we need air above the support block
|
||||
if (canSupport) {
|
||||
BlockPos up = pos.above();
|
||||
if (!mc.level.getBlockState(up).isAir()) continue;
|
||||
if (onePointTwelve.getValue() && !mc.level.getBlockState(up.above()).isAir()) continue;
|
||||
}
|
||||
|
||||
float targetDamage = CrystalUtil.calculateDamage(targetPlayer, crystalDamageVec(pos), terrain.getValue());
|
||||
if (!(bestDamage < targetDamage && targetDamage > minDmg.getValue())) continue;
|
||||
|
||||
float selfDamage = CrystalUtil.calculateDamage(mc.player, crystalDamageVec(pos), terrain.getValue());
|
||||
if (selfDamage > maxSelfDmg.getValue()) continue;
|
||||
if (selfDamage + 2 > mc.player.getHealth() + mc.player.getAbsorptionAmount() && noSuicide.getValue()) continue;
|
||||
|
||||
boolean isSupportCandidate = canSupport || (canAirPlace && !hasBlock);
|
||||
if (targetDamage > bestDamage || (isSupportCandidate && !isSupport)) {
|
||||
bestDamage = targetDamage;
|
||||
bestPos = pos;
|
||||
isSupport = isSupportCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestDamage == 0.5) bestDamage = 0;
|
||||
return new PlaceResult(bestDamage, bestPos, getPlaceRot(bestPos), isSupport);
|
||||
}
|
||||
|
||||
private boolean handleBBCrystal(BlockPos pos, BlockPos last, Entity currentCalcCrystal) {
|
||||
AABB bb = new AABB(pos.above());
|
||||
bb = new AABB(bb.minX, bb.minY, bb.minZ, bb.minX + 1.0, bb.minY + 2.0, bb.minZ + 1.0);
|
||||
|
||||
for (Entity entity : mc.level.getEntitiesOfClass(Entity.class, bb, e -> !e.isSpectator())) {
|
||||
if (entity instanceof EndCrystal) {
|
||||
if (!entity.isAlive() || entity.blockPosition().below().equals(pos) || entity.equals(currentCalcCrystal)) continue;
|
||||
}
|
||||
if (!entity.isAlive() || entity.equals(currentCalcCrystal)) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkPlace(BlockPos pos) {
|
||||
Direction side = BlockUtil.getPlaceableSideCrystal(pos, strictDirection.getValue());
|
||||
if (side == null) return false;
|
||||
|
||||
Vec3 vec = pos.getCenter().add(side.getStepX() * 0.5, side.getStepY() * 0.5, side.getStepZ() * 0.5);
|
||||
double distanceSq = vec.distanceToSqr(mc.player.getX(), mc.player.getY(), mc.player.getZ());
|
||||
if (distanceSq > square(placeRange.getValue())) return false;
|
||||
|
||||
boolean trace = BlockUtil.placeTrace(pos);
|
||||
if (trace && distanceSq > square(placeWallsRange.getValue())) return false;
|
||||
|
||||
double crystalDistance = mc.player.getEyePosition().distanceTo(pos.getCenter().add(0, 2.20000004768, 0));
|
||||
if (crystalDistance > breakRange.getValue()) return false;
|
||||
|
||||
Vec3 raytrace = pos.getCenter().add(0, 2.20000004768, 0);
|
||||
if (crystalDistance > breakWallsRange.getValue()) {
|
||||
BlockHitResult result = mc.level.clip(new ClipContext(
|
||||
mc.player.getEyePosition(), raytrace,
|
||||
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mc.player));
|
||||
return result.getType() == HitResult.Type.MISS;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Angles getPlaceRot(BlockPos pos) {
|
||||
if (pos == null || !rotate.getValue()) return null;
|
||||
Direction side = BlockUtil.getPlaceableSideCrystal(pos, strictDirection.getValue());
|
||||
if (side == null) return null;
|
||||
Vec3 vec = pos.getCenter().add(side.getStepX() * 0.5, side.getStepY() * 0.5, side.getStepZ() * 0.5);
|
||||
if (side.getAxis().isHorizontal()) vec = vec.add(0, 0.45, 0);
|
||||
return Angles.pos(vec);
|
||||
}
|
||||
|
||||
private Vec3 crystalDamageVec(BlockPos pos) {
|
||||
return Vec3.atLowerCornerOf(pos).add(0.5, 1.0, 0.5);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Actions
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
public void interact() {
|
||||
breakCrystal();
|
||||
if (crystalSlot() != -1 || offhand) {
|
||||
if (placeIsSupport) {
|
||||
placeSupportBlock();
|
||||
} else {
|
||||
placeCrystal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean breakCrystal() {
|
||||
if (calcCrystal == null) return false;
|
||||
if (rotate.getValue() && breakRots == null) return false;
|
||||
if (!breakTimer.passedMs(breakDelay.getValue().longValue())) return false;
|
||||
|
||||
int currentSlot = InventoryUtil.selected();
|
||||
int weaponSlot = -1;
|
||||
boolean silentWeapon = false;
|
||||
|
||||
if (antiWeakness.getValue() != AntiWeakness.None && isWeakened()) {
|
||||
weaponSlot = swordSlot();
|
||||
if (weaponSlot != -1 && currentSlot != weaponSlot) {
|
||||
if (antiWeakness.getValue() == AntiWeakness.Silent) {
|
||||
// Packet-only: the server sees the sword for the attack while the client keeps
|
||||
// mining with whatever is actually selected.
|
||||
silentSelect(weaponSlot);
|
||||
silentWeapon = true;
|
||||
} else {
|
||||
// Normal: real switch, stays on the weapon.
|
||||
InventoryUtil.swap(weaponSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mc.gameMode.attack(mc.player, calcCrystal);
|
||||
mc.player.swing(offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND);
|
||||
mc.player.resetAttackStrengthTicker();
|
||||
hitCrystals.put(calcCrystal.getId(), System.currentTimeMillis());
|
||||
|
||||
if (silentWeapon) silentRestore();
|
||||
|
||||
breakTimer.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void placeCrystal() {
|
||||
if (calcPos == null) return;
|
||||
if (rotate.getValue() && placeRots == null) return;
|
||||
if (!placeTimer.passedMs(placeDelay.getValue().longValue())) return;
|
||||
|
||||
int crystalSlot = crystalSlot();
|
||||
if (crystalSlot == -1 && !offhand) {
|
||||
renderPos = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
boolean silent = false;
|
||||
|
||||
if (!offhand && autoSwitch.getValue() == AutoSwitch.Normal) {
|
||||
if (!pause.getValue() || !isEatingGap()) InventoryUtil.swap(crystalSlot);
|
||||
} else if (!offhand && autoSwitch.getValue() == AutoSwitch.Silent) {
|
||||
// Packet-only switch: the server is told we hold crystals while the client keeps its
|
||||
// own selected slot, so mining with the tool in hand is never interrupted.
|
||||
if (oldSlot != crystalSlot) {
|
||||
silentSelect(crystalSlot);
|
||||
silent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (autoSwitch.getValue() == AutoSwitch.None
|
||||
&& mc.player.getMainHandItem().getItem() != Items.END_CRYSTAL && !offhand) {
|
||||
renderPos = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Direction side = BlockUtil.getPlaceableSideCrystal(calcPos, strictDirection.getValue());
|
||||
if (side != null) {
|
||||
Vec3 vec = calcPos.getCenter().add(side.getStepX() * 0.5, side.getStepY() * 0.5, side.getStepZ() * 0.5);
|
||||
if (side.getAxis().isHorizontal()) vec = vec.add(0, 0.45, 0);
|
||||
|
||||
BlockHitResult hit = new BlockHitResult(vec, side, calcPos, false);
|
||||
mc.gameMode.useItemOn(mc.player, offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND, hit);
|
||||
mc.player.swing(offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND);
|
||||
|
||||
lastPos = calcPos;
|
||||
placeTimer.reset();
|
||||
}
|
||||
|
||||
// Restore on every path: previously the swap-back lived inside the side != null block, so a
|
||||
// failed placement left the server stuck on the crystal slot.
|
||||
if (silent) silentRestore();
|
||||
|
||||
renderPos = calcPos;
|
||||
}
|
||||
|
||||
private void placeSupportBlock() {
|
||||
if (calcPos == null) return;
|
||||
if (rotate.getValue() && placeRots == null) return;
|
||||
if (!placeTimer.passedMs(placeDelay.getValue().longValue())) return;
|
||||
|
||||
int obsidianSlot = InventoryUtil.find(Items.OBSIDIAN, InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
int endStoneSlot = InventoryUtil.find(Items.END_STONE, InventoryUtil.HOTBAR_SCOPE).slot();
|
||||
int supportSlot = obsidianSlot != -1 ? obsidianSlot : endStoneSlot;
|
||||
|
||||
if (supportSlot == -1) {
|
||||
renderPos = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int oldSlot = InventoryUtil.selected();
|
||||
boolean silent = false;
|
||||
|
||||
if (autoSwitch.getValue() == AutoSwitch.Normal) {
|
||||
if (!pause.getValue() || !isEatingGap()) InventoryUtil.swap(supportSlot);
|
||||
} else if (autoSwitch.getValue() == AutoSwitch.Silent) {
|
||||
// Packet-only switch, same as placeCrystal: the client keeps its tool in hand.
|
||||
if (oldSlot != supportSlot) {
|
||||
silentSelect(supportSlot);
|
||||
silent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (autoSwitch.getValue() == AutoSwitch.None
|
||||
&& mc.player.getMainHandItem().getItem() != Items.OBSIDIAN
|
||||
&& mc.player.getMainHandItem().getItem() != Items.END_STONE) {
|
||||
renderPos = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Direction side = BlockUtil.getPlaceableSideCrystal(calcPos, strictDirection.getValue());
|
||||
if (side != null) {
|
||||
Vec3 vec = calcPos.getCenter().add(side.getStepX() * 0.5, side.getStepY() * 0.5, side.getStepZ() * 0.5);
|
||||
if (side.getAxis().isHorizontal()) vec = vec.add(0, 0.45, 0);
|
||||
|
||||
BlockHitResult hit = new BlockHitResult(vec, side, calcPos, false);
|
||||
mc.gameMode.useItemOn(mc.player, InteractionHand.MAIN_HAND, hit);
|
||||
mc.player.swing(InteractionHand.MAIN_HAND);
|
||||
|
||||
lastPos = calcPos;
|
||||
placeTimer.reset();
|
||||
|
||||
// After placing support block, try to place crystal on next tick
|
||||
placeIsSupport = false;
|
||||
}
|
||||
|
||||
// Restore on every path, including failed placements.
|
||||
if (silent) silentRestore();
|
||||
|
||||
renderPos = calcPos;
|
||||
}
|
||||
|
||||
private boolean canBreakCrystal(EndCrystal crystal) {
|
||||
double distance = mc.player.getEyePosition().distanceTo(crystal.position().add(0, 1.700000047683716, 0));
|
||||
if (rotationType.getValue() == RotationType.GrimAbuse && crystal.blockPosition().getY() > mc.player.blockPosition().getY()) {
|
||||
distance = mc.player.getEyePosition().distanceTo(crystal.position().add(0, 0.5, 0));
|
||||
}
|
||||
if (ticksExisted.getValue() != 0 && crystal.tickCount <= ticksExisted.getValue()) return false;
|
||||
if (distance > breakRange.getValue()) return false;
|
||||
return canEntityBeSeen(crystal) || distance <= breakWallsRange.getValue();
|
||||
}
|
||||
|
||||
private boolean canEntityBeSeen(Entity entity) {
|
||||
Vec3 entityPos = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ());
|
||||
if (entity instanceof EndCrystal) {
|
||||
if (canVecBeSeen(entity.position().add(0, 1.700000047683716, 0))) return true;
|
||||
}
|
||||
return canVecBeSeen(entityPos);
|
||||
}
|
||||
|
||||
private boolean canVecBeSeen(Vec3 vec) {
|
||||
BlockHitResult result = mc.level.clip(new ClipContext(
|
||||
mc.player.getEyePosition(), vec,
|
||||
ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mc.player));
|
||||
return result.getType() == HitResult.Type.MISS;
|
||||
}
|
||||
|
||||
private boolean isWeakened() {
|
||||
return mc.player.getEffect(MobEffects.WEAKNESS) != null;
|
||||
}
|
||||
|
||||
private boolean isEatingGap() {
|
||||
ItemStack stack = mc.player.getMainHandItem();
|
||||
return (stack.getItem() == Items.GOLDEN_APPLE || stack.getItem() == Items.ENCHANTED_GOLDEN_APPLE)
|
||||
&& mc.player.isUsingItem();
|
||||
}
|
||||
|
||||
private int crystalSlot() {
|
||||
cc.fros.util.inventory.Result result = InventoryUtil.find(Items.END_CRYSTAL, InventoryUtil.HOTBAR_SCOPE);
|
||||
if (!result.found()) return -1;
|
||||
if (result.type() == ResultType.OFFHAND) return -1;
|
||||
return result.slot();
|
||||
}
|
||||
|
||||
private int swordSlot() {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
ItemStack stack = mc.player.getInventory().getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
if (stack.is(ItemTags.SWORDS) || stack.is(ItemTags.AXES)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Tells the server we hold {@code slot} without touching the client's own selection. */
|
||||
private static void silentSelect(int slot) {
|
||||
mc.getConnection().send(new ServerboundSetCarriedItemPacket(slot));
|
||||
}
|
||||
|
||||
/** Puts the server back on whatever the client is really holding. */
|
||||
private static void silentRestore() {
|
||||
mc.getConnection().send(new ServerboundSetCarriedItemPacket(InventoryUtil.selected()));
|
||||
}
|
||||
|
||||
private static double square(double value) {
|
||||
return value * value;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onRender3D(Render3DEvent event) {
|
||||
if (renderPos == null || target == null) return;
|
||||
if (crystalSlot() == -1 && !offhand) return;
|
||||
|
||||
AABB box = new AABB(renderPos);
|
||||
RenderUtil.drawBoxFilled(event.getMatrix(), box, fillColor.getValue());
|
||||
RenderUtil.drawBox(event.getMatrix(), box, lineColor.getValue(), 1.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayInfo() {
|
||||
return target == null || displayDmg <= 0 ? null : String.format(Locale.ROOT, "%.1f", displayDmg);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Types
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private record BreakResult(double damage, Entity calcCrystal, Angles rots) {
|
||||
}
|
||||
|
||||
private record PlaceResult(double bestDmg, BlockPos calcPos, Angles rots, boolean isSupport) {
|
||||
}
|
||||
|
||||
private static final class Result {
|
||||
final Player target;
|
||||
final double bestBreakDmg;
|
||||
final double bestPlaceDmg;
|
||||
final BlockPos calcPos;
|
||||
final Entity calcCrystal;
|
||||
final Angles placeRots;
|
||||
final Angles breakRots;
|
||||
final boolean placeIsSupport;
|
||||
|
||||
Result(Player target, BreakResult breakResult, PlaceResult placeResult) {
|
||||
this.target = target;
|
||||
this.bestBreakDmg = breakResult.damage;
|
||||
this.calcCrystal = breakResult.calcCrystal;
|
||||
this.breakRots = breakResult.rots;
|
||||
this.bestPlaceDmg = placeResult.bestDmg;
|
||||
this.calcPos = placeResult.calcPos;
|
||||
this.placeRots = placeResult.rots;
|
||||
this.placeIsSupport = placeResult.isSupport();
|
||||
}
|
||||
|
||||
double getDamage() {
|
||||
return Math.max(bestBreakDmg, bestPlaceDmg);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Page {
|
||||
Calc, Place, Break, Misc, Render
|
||||
}
|
||||
|
||||
public enum Sort {
|
||||
Damage, Range
|
||||
}
|
||||
|
||||
public enum AutoSwitch {
|
||||
None, Normal, Silent
|
||||
}
|
||||
|
||||
public enum AntiWeakness {
|
||||
None, Normal, Silent
|
||||
}
|
||||
|
||||
public enum MultiTask {
|
||||
None, Soft, Strong
|
||||
}
|
||||
|
||||
public enum RotationType {
|
||||
Simple, Silent, GrimAbuse
|
||||
}
|
||||
|
||||
public enum PlaceMode {
|
||||
Normal, // Standard placement on obsidian/bedrock
|
||||
AirPlace, // Place crystals on sides of blocks in air
|
||||
Support, // Place support blocks (obsidian/end stone) then crystals
|
||||
AutoSurroundAnchor // Break surround, place anchor, charge+explode
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import cc.fros.features.modules.client.ClickGuiModule;
|
||||
import cc.fros.features.modules.client.HudEditorModule;
|
||||
import cc.fros.features.modules.client.NotificationsModule;
|
||||
import cc.fros.features.modules.combat.KillAuraModule;
|
||||
import cc.fros.features.modules.combat.CrystalAuraModule;
|
||||
import cc.fros.features.modules.combat.CriticalsModule;
|
||||
import cc.fros.features.modules.combat.KeyPearlModule;
|
||||
import cc.fros.features.modules.hud.ArrayListHudModule;
|
||||
@@ -77,6 +78,7 @@ public class ModuleManager implements Jsonable, Util {
|
||||
register(new NoFallModule());
|
||||
register(new KeyPearlModule());
|
||||
register(new KillAuraModule());
|
||||
register(new CrystalAuraModule());
|
||||
register(new FastAccelModule());
|
||||
|
||||
LOGGER.info("Registered {} modules", modules.size());
|
||||
|
||||
Reference in New Issue
Block a user