package dev.crucifixbeta.mod.modules.impl.combat;

import asm.accessors.IEntity;
import dev.crucifixbeta.CrucifixBeta;
import dev.crucifixbeta.api.events.eventbus.EventListener;
import dev.crucifixbeta.api.events.impl.ClientTickEvent;
import dev.crucifixbeta.api.events.impl.EntitySpawnedEvent;
import dev.crucifixbeta.api.events.impl.PacketEvent;
import dev.crucifixbeta.api.events.impl.Render3DEvent;
import dev.crucifixbeta.api.events.impl.RotationEvent;
import dev.crucifixbeta.api.utils.combat.CombatUtil;
import dev.crucifixbeta.api.utils.combat.crystal.AsyncCrystalConfig;
import dev.crucifixbeta.api.utils.combat.crystal.AsyncCrystalManager;
import dev.crucifixbeta.api.utils.combat.crystal.AsyncCrystalResult;
import dev.crucifixbeta.api.utils.combat.crystal.CrystalEntitySnapshot;
import dev.crucifixbeta.api.utils.combat.crystal.CrystalWorldSnapshot;
import dev.crucifixbeta.api.utils.entity.PlayerEntityPredict;
import dev.crucifixbeta.api.utils.math.AnimateUtil;
import dev.crucifixbeta.api.utils.math.Animation;
import dev.crucifixbeta.api.utils.math.DamageUtils;
import dev.crucifixbeta.api.utils.math.Easing;
import dev.crucifixbeta.api.utils.math.ExplosionUtil;
import dev.crucifixbeta.api.utils.math.MathUtil;
import dev.crucifixbeta.api.utils.math.Timer;
import dev.crucifixbeta.api.utils.player.EntityUtil;
import dev.crucifixbeta.api.utils.player.InventoryUtil;
import dev.crucifixbeta.api.utils.render.ColorUtil;
import dev.crucifixbeta.api.utils.render.JelloUtil;
import dev.crucifixbeta.api.utils.render.Render3DUtil;
import dev.crucifixbeta.api.utils.world.BlockPosX;
import dev.crucifixbeta.api.utils.world.BlockUtil;
import dev.crucifixbeta.core.impl.ThreadManager;
import dev.crucifixbeta.mod.modules.Module;
import dev.crucifixbeta.mod.modules.impl.client.AntiCheat;
import dev.crucifixbeta.mod.modules.impl.client.ClientSetting;
import dev.crucifixbeta.mod.modules.impl.exploit.Blink;
import dev.crucifixbeta.mod.modules.impl.movement.ElytraFly;
import dev.crucifixbeta.mod.modules.impl.player.PacketMine;
import dev.crucifixbeta.mod.modules.settings.enums.SwingSide;
import dev.crucifixbeta.mod.modules.settings.enums.Timing;
import dev.crucifixbeta.mod.modules.settings.impl.BindSetting;
import dev.crucifixbeta.mod.modules.settings.impl.BooleanSetting;
import dev.crucifixbeta.mod.modules.settings.impl.ColorSetting;
import dev.crucifixbeta.mod.modules.settings.impl.EnumSetting;
import dev.crucifixbeta.mod.modules.settings.impl.SliderSetting;
import java.awt.Color;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.Entity.RemovalReason;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.entity.decoration.EndCrystalEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket;
import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.HitResult.Type;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.RaycastContext.FluidHandling;
import net.minecraft.world.RaycastContext.ShapeType;

@Environment(EnvType.CLIENT)
public class AutoCrystal extends Module {
   public static AutoCrystal INSTANCE;
   public BlockPos crystalPos;
   public final Timer lastBreakTimer = new Timer();
   private final EnumSetting<AutoCrystal.Page> page = this.add(new EnumSetting<>("Page", AutoCrystal.Page.General));
   final Animation animation = new Animation();
   final DecimalFormat df = new DecimalFormat("0.0");
   private final Timer baseTimer = new Timer();
   private final Timer placeTimer = new Timer();
   private final Timer noPosTimer = new Timer();
   private final Timer switchTimer = new Timer();
   private final Timer calcDelay = new Timer();
   private final Timer cevTimer = new Timer();
   private final Timer cevPlaceTimer = new Timer();
   private final BindSetting pause = this.add(new BindSetting("Pause", -1, () -> this.page.is(AutoCrystal.Page.Check)));
   private final BooleanSetting preferAnchor = this.add(
      new BooleanSetting("PreferAnchor", true, () -> this.page.getValue() == AutoCrystal.Page.Check)
   );
   private final BooleanSetting breakOnlyHasCrystal = this.add(
      new BooleanSetting("OnlyHold", true, () -> this.page.getValue() == AutoCrystal.Page.Check)
   );
   private final BooleanSetting eatingPause = this.add(
      new BooleanSetting("EatingPause", true, () -> this.page.getValue() == AutoCrystal.Page.Check)
   );
   private final BooleanSetting healthPause = this.add(
      new BooleanSetting("HealthPause", false, () -> this.page.getValue() == AutoCrystal.Page.Check).setParent()
   );
   private final SliderSetting pauseHealth = this.add(
      new SliderSetting("PauseHealth", 8.0, 0.0, 36.0, 0.5, () -> this.page.getValue() == AutoCrystal.Page.Check && this.healthPause.isOpen())
         .setSuffix("hp")
   );
   private final SliderSetting switchCooldown = this.add(
      new SliderSetting("SwitchPause", 100, 0, 1000, () -> this.page.getValue() == AutoCrystal.Page.Check)
         .setSuffix("ms")
   );
   private final SliderSetting targetRange = this.add(
      new SliderSetting("TargetRange", 12.0, 0.0, 20.0, () -> this.page.getValue() == AutoCrystal.Page.Check)
         .setSuffix("m")
   );
   private final SliderSetting maxTargets = this.add(
      new SliderSetting("MaxTargets", 4, 1, 16, () -> this.page.getValue() == AutoCrystal.Page.Check)
   );
   private final BooleanSetting ignoreNakeds = this.add(
      new BooleanSetting("IgnoreNakeds", false, () -> this.page.getValue() == AutoCrystal.Page.Check)
   );
   private final SliderSetting updateDelay = this.add(
      new SliderSetting("UpdateDelay", 50, 0, 1000, () -> this.page.getValue() == AutoCrystal.Page.Check).setSuffix("ms")
   );
   private final BooleanSetting pauseKillAura = this.add(
      new BooleanSetting("PauseKillAura", true, () -> this.page.getValue() == AutoCrystal.Page.Check).setParent()
   );
   private final SliderSetting killAuraPauseDamage = this.add(
      new SliderSetting("KAPauseDamage", 8.0, 0.0, 36.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Check && this.pauseKillAura.isOpen())
         .setSuffix("dmg")
   );
   private final SliderSetting killAuraPauseHold = this.add(
      new SliderSetting("KAPauseHold", 250, 0, 1000, () -> this.page.getValue() == AutoCrystal.Page.Check && this.pauseKillAura.isOpen())
         .setSuffix("ms")
   );
   private final BooleanSetting cevBreaker = this.add(
      new BooleanSetting("CevBreaker", false, () -> this.page.getValue() == AutoCrystal.Page.Cev).setParent()
   );
   private final SliderSetting cevTargetRange = this.add(
      new SliderSetting("CevTargetRange", 5.0, 0.0, 8.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
         .setSuffix("m")
   );
   private final SliderSetting cevBreakRange = this.add(
      new SliderSetting("CevBreakRange", 5.0, 0.0, 8.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
         .setSuffix("m")
   );
   private final SliderSetting cevDelay = this.add(
      new SliderSetting("CevDelay", 100, 0, 500, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
         .setSuffix("ms")
   );
   private final BooleanSetting cevRotate = this.add(
      new BooleanSetting("CevRotate", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
   );
   private final BooleanSetting cevGround = this.add(
      new BooleanSetting("CevGround", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
   );
   private final BooleanSetting cevInventorySwap = this.add(
      new BooleanSetting("CevInventorySwap", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
   );
   private final BooleanSetting cevTop = this.add(
      new BooleanSetting("CevTop", false, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
   );
   private final BooleanSetting cevBevel = this.add(
      new BooleanSetting("CevBevel", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen())
   );
   private final BooleanSetting cevCrystal = this.add(
      new BooleanSetting("CevCrystal", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen()).setParent()
   );
   private final BooleanSetting cevDetectProgress = this.add(
      new BooleanSetting(
         "CevDetectProgress", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen() && this.cevCrystal.isOpen()
      )
   );
   private final SliderSetting cevProgress = this.add(
      new SliderSetting(
         "CevProgress",
         0.9,
         0.0,
         1.0,
         0.01,
         () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen() && this.cevCrystal.isOpen() && this.cevDetectProgress.getValue()
      )
   );
   private final BooleanSetting cevAfterBreak = this.add(
      new BooleanSetting(
         "CevAfterBreak", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen() && this.cevCrystal.isOpen()
      )
   );
   private final BooleanSetting cevPlaceRotate = this.add(
      new BooleanSetting(
         "CevPlaceRotate", true, () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen() && this.cevCrystal.isOpen()
      )
   );
   private final SliderSetting cevPlaceDelay = this.add(
      new SliderSetting(
            "CevPlaceDelay",
            100,
            0,
            1000,
            () -> this.page.getValue() == AutoCrystal.Page.Cev && this.cevBreaker.isOpen() && this.cevCrystal.isOpen()
         )
         .setSuffix("ms")
   );
   private final BooleanSetting rotate = this.add(
      new BooleanSetting("Rotate", true, () -> this.page.getValue() == AutoCrystal.Page.Rotation).setParent()
   );
   private final BooleanSetting onPlace = this.add(
      new BooleanSetting("OnPlace", false, () -> this.rotate.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation)
   );
   private final BooleanSetting onBreak = this.add(
      new BooleanSetting("OnBreak", false, () -> this.rotate.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation)
   );
   private final BooleanSetting yawStep = this.add(
      new BooleanSetting("YawStep", false, () -> this.rotate.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation).setParent()
   );
   private final BooleanSetting whenElytra = this.add(
      new BooleanSetting(
         "FallFlying", true, () -> this.rotate.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation && this.yawStep.isOpen()
      )
   );
   private final SliderSetting steps = this.add(
      new SliderSetting(
         "Steps",
         0.05,
         0.0,
         1.0,
         0.01,
         () -> this.rotate.isOpen() && this.yawStep.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation
      )
   );
   private final BooleanSetting checkFov = this.add(
      new BooleanSetting(
         "OnlyLooking", true, () -> this.rotate.isOpen() && this.yawStep.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation
      )
   );
   private final SliderSetting fov = this.add(
      new SliderSetting(
         "Fov",
         20.0,
         0.0,
         360.0,
         0.1,
         () -> this.rotate.isOpen() && this.yawStep.isOpen() && this.checkFov.getValue() && this.page.getValue() == AutoCrystal.Page.Rotation
      )
   );
   private final SliderSetting priority = this.add(
      new SliderSetting(
         "Priority", 10, 0, 100, () -> this.rotate.isOpen() && this.yawStep.isOpen() && this.page.getValue() == AutoCrystal.Page.Rotation
      )
   );
   private final SliderSetting minDamage = this.add(
      new SliderSetting("Min", 5.0, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.General)
         .setSuffix("dmg")
   );
   private final SliderSetting maxSelf = this.add(
      new SliderSetting("Max", 12.0, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.General)
         .setSuffix("dmg")
   );
   private final SliderSetting damageRatio = this.add(
      new SliderSetting("DamageRatio", 0.0, 0.0, 10.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.General)
         .setSuffix("x")
   );
   private final SliderSetting reserve = this.add(
      new SliderSetting("Reserve", 2.0, 0.0, 10.0, () -> this.page.getValue() == AutoCrystal.Page.General)
         .setSuffix("hp")
   );
   private final BooleanSetting balance = this.add(
      new BooleanSetting("Balance", true, () -> this.page.getValue() == AutoCrystal.Page.General).setParent()
   );
   private final SliderSetting balanceOffset = this.add(
      new SliderSetting("BalanceOffset", 0.0, -20.0, 20.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.General && this.balance.isOpen())
         .setSuffix("hp")
   );
   private final BooleanSetting place = this.add(
      new BooleanSetting("Place", true, () -> this.page.getValue() == AutoCrystal.Page.General).setParent()
   );
   public final SliderSetting placeRange = this.add(
      new SliderSetting("PlaceRange", 5.0, 0.0, 6.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
         .setSuffix("m")
   );
   private final SliderSetting placeDelay = this.add(
      new SliderSetting("PlaceDelay", 300, 0, 1000, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
         .setSuffix("ms")
   );
   private final EnumSetting<AutoCrystal.SwapMode> autoSwap = this.add(
      new EnumSetting<>("AutoSwap", AutoCrystal.SwapMode.None, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting afterBreak = this.add(
      new BooleanSetting("AfterBreak", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting forcePlace = this.add(
      new BooleanSetting("ForcePlace", false, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting smartSwitch = this.add(
      new BooleanSetting("SmartSwitch", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting syncAfterPlace = this.add(
      new BooleanSetting("SyncAfterPlace", false, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting packetOptimize = this.add(
      new BooleanSetting("PacketOptimize", true, () -> this.page.getValue() == AutoCrystal.Page.General).setParent()
   );
   private final SliderSetting maxPacketsPerTick = this.add(
      new SliderSetting(
         "MaxPackets", 4.0, 1.0, 10.0, 1.0, () -> this.packetOptimize.isOpen() && this.page.getValue() == AutoCrystal.Page.General
      )
   );
   private final BooleanSetting skipRedundantSwap = this.add(
      new BooleanSetting("SkipRedundantSwap", true, () -> this.packetOptimize.isOpen() && this.page.getValue() == AutoCrystal.Page.General)
   );
   private final BooleanSetting breakSetting = this.add(
      new BooleanSetting("Break", true, () -> this.page.getValue() == AutoCrystal.Page.General).setParent()
   );
   public final SliderSetting breakRange = this.add(
      new SliderSetting("BreakRange", 4.0, 0.0, 6.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
         .setSuffix("m")
   );
   private final SliderSetting breakDelay = this.add(
      new SliderSetting("BreakDelay", 300, 0, 1000, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
         .setSuffix("ms")
   );
   private final SliderSetting minAge = this.add(
      new SliderSetting("MinAge", 0, 0, 20, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
         .setSuffix("tick")
   );
   private final BooleanSetting breakRemove = this.add(
      new BooleanSetting("Remove", false, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final BooleanSetting onAdd = this.add(
      new BooleanSetting("OnAdd", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final BooleanSetting resetCD = this.add(
      new BooleanSetting("ResetAttack", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final EnumSetting<Timing> timing = this.add(
      new EnumSetting<>("Timing", Timing.All, () -> this.page.getValue() == AutoCrystal.Page.General)
   );
   private final BooleanSetting interactOnRender = this.add(
      new BooleanSetting("InteractOnRender", false, () -> this.page.getValue() == AutoCrystal.Page.General)
   );
   private final SliderSetting placeLimit = this.add(
      new SliderSetting("PlaceLimit", 2, 1, 10, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting instantBreak = this.add(
      new BooleanSetting("InstantBreak", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final BooleanSetting instantPlace = this.add(
      new BooleanSetting("InstantPlace", false, () -> this.page.getValue() == AutoCrystal.Page.General && this.place.isOpen())
   );
   private final BooleanSetting predictAttack = this.add(
      new BooleanSetting("PredictAttack", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final BooleanSetting spawnPredict = this.add(
      new BooleanSetting("SpawnPredict", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen()).setParent()
   );
   private final SliderSetting predictMin = this.add(
      new SliderSetting(
            "PredictMin",
            1.5,
            0.0,
            36.0,
            0.1,
            () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen() && this.spawnPredict.isOpen()
         )
         .setSuffix("dmg")
   );
   private final BooleanSetting crystalOptimizer = this.add(
      new BooleanSetting("CrystalOptimizer", true, () -> this.page.getValue() == AutoCrystal.Page.General && this.breakSetting.isOpen())
   );
   private final SliderSetting wallRange = this.add(
      new SliderSetting("WallRange", 6.0, 0.0, 6.0, () -> this.page.getValue() == AutoCrystal.Page.General)
         .setSuffix("m")
   );
   private final EnumSetting<SwingSide> swingMode = this.add(
      new EnumSetting<>("Swing", SwingSide.All, () -> this.page.getValue() == AutoCrystal.Page.General)
   );
   private final ColorSetting text = this.add(
      new ColorSetting("Text", new Color(-1), () -> this.page.getValue() == AutoCrystal.Page.Render).injectBoolean(true)
   );
   private final EnumSetting<AutoCrystal.TargetESP> mode = this.add(
      new EnumSetting<>("TargetESP", AutoCrystal.TargetESP.Fill, () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final SliderSetting animationTime = this.add(
      new SliderSetting("AnimationTime", 200.0, 0.0, 2000.0, 1.0, () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final EnumSetting<Easing> ease = this.add(
      new EnumSetting<>("Ease", Easing.CubicInOut, () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final ColorSetting color = this.add(
      new ColorSetting("TargetColor", new Color(255, 255, 255, 50), () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final ColorSetting outlineColor = this.add(
      new ColorSetting("TargetOutlineColor", new Color(255, 255, 255, 50), () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final ColorSetting hitColor = this.add(
      new ColorSetting("HitColor", new Color(255, 255, 255, 150), () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final ColorSetting hitOutlineColor = this.add(
      new ColorSetting("HitOutlineColor", new Color(255, 255, 255, 150), () -> this.page.getValue() == AutoCrystal.Page.Render)
   );
   private final BooleanSetting render = this.add(new BooleanSetting("Render", true, () -> this.page.getValue() == AutoCrystal.Page.Render));
   private final BooleanSetting sync = this.add(
      new BooleanSetting("Sync", true, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
   );
   private final BooleanSetting shrink = this.add(
      new BooleanSetting("Shrink", true, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
   );
   private final ColorSetting box = this.add(
      new ColorSetting(
            "Box", new Color(255, 255, 255, 255), () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue()
         )
         .injectBoolean(true)
   );
   private final SliderSetting lineWidth = this.add(
      new SliderSetting("LineWidth", 1.5, 0.01, 3.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
   );
   private final ColorSetting fill = this.add(
      new ColorSetting(
            "Fill", new Color(255, 255, 255, 100), () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue()
         )
         .injectBoolean(true)
   );
   private final SliderSetting sliderSpeed = this.add(
      new SliderSetting("SliderSpeed", 0.2, 0.01, 1.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
   );
   private final SliderSetting startFadeTime = this.add(
      new SliderSetting("StartFade", 0.3, 0.0, 2.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
         .setSuffix("s")
   );
   private final SliderSetting fadeSpeed = this.add(
      new SliderSetting("FadeSpeed", 0.2, 0.01, 1.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Render && this.render.getValue())
   );
   private final SliderSetting attackVecStep = this.add(
      new SliderSetting("AttackVecStep", 0.1, 0.01, 1.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final BooleanSetting thread = this.add(new BooleanSetting("Thread", true, () -> this.page.getValue() == AutoCrystal.Page.Calc));
   private final BooleanSetting doCrystal = this.add(
      new BooleanSetting("InteractInCalc", false, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final SliderSetting selfPredict = this.add(
      new SliderSetting("SelfPredict", 0, 0, 20, () -> this.page.getValue() == AutoCrystal.Page.Calc).setSuffix("ticks")
   );
   private final SliderSetting predictTicks = this.add(
      new SliderSetting("Predict", 4, 0, 20, () -> this.page.getValue() == AutoCrystal.Page.Calc).setSuffix("ticks")
   );
   private final SliderSetting simulation = this.add(
      new SliderSetting("Simulation", 5.0, 0.0, 20.0, 1.0, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final SliderSetting maxMotionY = this.add(
      new SliderSetting("MaxMotionY", 0.34, 0.0, 2.0, 0.01, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final BooleanSetting step = this.add(new BooleanSetting("Step", false, () -> this.page.getValue() == AutoCrystal.Page.Calc));
   private final BooleanSetting doubleStep = this.add(
      new BooleanSetting("DoubleStep", false, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final BooleanSetting jump = this.add(new BooleanSetting("Jump", false, () -> this.page.getValue() == AutoCrystal.Page.Calc));
   private final BooleanSetting inBlockPause = this.add(
      new BooleanSetting("InBlockPause", true, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final BooleanSetting terrainIgnore = this.add(
      new BooleanSetting("TerrainIgnore", true, () -> this.page.getValue() == AutoCrystal.Page.Calc)
   );
   private final BooleanSetting basePlace = this.add(
      new BooleanSetting("BasePlace", true, () -> this.page.getValue() == AutoCrystal.Page.Base)
   );
   private final SliderSetting baseMin = this.add(
      new SliderSetting("BaseMin", 6.0, 0.0, 36.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Base)
         .setSuffix("hp")
   );
   private final SliderSetting baseMax = this.add(
      new SliderSetting("BaseMax", 12.0, 0.0, 36.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Base)
         .setSuffix("hp")
   );
   private final SliderSetting overrideMax = this.add(
      new SliderSetting("MaxOverride", 8.0, 0.0, 36.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Base)
         .setSuffix("hp")
   );
   private final BooleanSetting baseBalance = this.add(
      new BooleanSetting("BaseBalance", true, () -> this.page.getValue() == AutoCrystal.Page.Base)
   );
   private final BooleanSetting onlyBelow = this.add(new BooleanSetting("OnlyBelow", true, () -> this.page.getValue() == AutoCrystal.Page.Base));
   private final BooleanSetting inventory = this.add(new BooleanSetting("InventorySwap", true, () -> this.page.getValue() == AutoCrystal.Page.Base));
   private final BooleanSetting detectMining = this.add(
      new BooleanSetting("DetectMining", true, () -> this.page.getValue() == AutoCrystal.Page.Base)
   );
   private final SliderSetting delay = this.add(
      new SliderSetting("Delay", 3000, 0, 10000, () -> this.page.getValue() == AutoCrystal.Page.Base).setSuffix("ms")
   );
   private final BooleanSetting ignoreMine = this.add(
      new BooleanSetting("IgnoreMine", true, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   private final SliderSetting constantProgress = this.add(
      new SliderSetting("Progress", 90.0, 0.0, 100.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.ignoreMine.isOpen())
         .setSuffix("%")
   );
   private final BooleanSetting antiSurround = this.add(
      new BooleanSetting("AntiSurround", false, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   private final SliderSetting miningProgress = this.add(
      new SliderSetting("MiningProgress", 90.0, 0.0, 100.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.antiSurround.isOpen())
         .setSuffix("%")
   );
   private final SliderSetting antiSurroundMax = this.add(
      new SliderSetting("WhenLower", 5.0, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.antiSurround.isOpen())
         .setSuffix("dmg")
   );
   private final BooleanSetting slowPlace = this.add(
      new BooleanSetting("Timeout", true, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   private final SliderSetting slowDelay = this.add(
      new SliderSetting("TimeoutDelay", 600, 0, 2000, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.slowPlace.isOpen())
         .setSuffix("ms")
   );
   private final SliderSetting slowMinDamage = this.add(
      new SliderSetting("TimeoutMin", 1.5, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.slowPlace.isOpen())
         .setSuffix("dmg")
   );
   private final BooleanSetting lethalOverride = this.add(
      new BooleanSetting("LethalOverride", true, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   private final SliderSetting forceMaxHealth = this.add(
      new SliderSetting("LowerThan", 7.0, 0.0, 36.0, 0.1, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.lethalOverride.isOpen())
         .setSuffix("health")
   );
   private final SliderSetting forceMin = this.add(
      new SliderSetting("ForceMin", 1.5, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.lethalOverride.isOpen())
         .setSuffix("dmg")
   );
   private final BooleanSetting armorBreaker = this.add(
      new BooleanSetting("ArmorBreaker", true, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   private final SliderSetting maxDurable = this.add(
      new SliderSetting("MaxDurable", 8, 0, 100, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.armorBreaker.isOpen())
         .setSuffix("%")
   );
   private final SliderSetting armorBreakerDamage = this.add(
      new SliderSetting("BreakerMin", 3.0, 0.0, 36.0, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.armorBreaker.isOpen())
         .setSuffix("dmg")
   );
   private final BooleanSetting forceWeb = this.add(
      new BooleanSetting("WebReset", true, () -> this.page.getValue() == AutoCrystal.Page.Misc).setParent()
   );
   public final BooleanSetting airPlace = this.add(
      new BooleanSetting("AirPlace", false, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.forceWeb.isOpen())
   );
   public final BooleanSetting replace = this.add(
      new BooleanSetting("Replace", false, () -> this.page.getValue() == AutoCrystal.Page.Misc && this.forceWeb.isOpen())
   );
   private final BooleanSetting targetItems = this.add(
      new BooleanSetting("TargetItems", false, () -> this.page.getValue() == AutoCrystal.Page.Misc)
   );
   private final BooleanSetting ignoreItems = this.add(
      new BooleanSetting("IgnoreItems", false, () -> this.page.getValue() == AutoCrystal.Page.Misc)
   );
   private final SliderSetting hurtTime = this.add(
      new SliderSetting("HurtTime", 10.0, 0.0, 10.0, 1.0, () -> this.page.getValue() == AutoCrystal.Page.Misc)
   );
   private final SliderSetting waitHurt = this.add(
      new SliderSetting("WaitHurt", 10.0, 0.0, 10.0, 1.0, () -> this.page.getValue() == AutoCrystal.Page.Misc)
   );
   private final SliderSetting syncTimeout = this.add(
      new SliderSetting("WaitTimeOut", 500.0, 0.0, 2000.0, 10.0, () -> this.page.getValue() == AutoCrystal.Page.Misc)
   );
   private final Timer syncTimer = new Timer();
   public PlayerEntity displayTarget;
   public float breakDamage;
   public float tempDamage;
   public float lastDamage;
   public Vec3d directionVec = null;
   double currentFade = 0.0;
   private EndCrystalEntity tempBreakCrystal;
   private EndCrystalEntity breakCrystal;
   private BlockPos tempPos;
   private BlockPos syncPos;
   private Vec3d placeVec3d;
   private Vec3d curVec3d;
   int lastSlot;
   BlockPos tempBasePos;
   BlockPos basePos;
   private final Map<BlockPos, Long> placePackets = new HashMap<>();
   private final Set<Integer> deadCrystals = new HashSet<>();
   private final AutoCrystal.AsyncCrystalScanner asyncScanner = new AutoCrystal.AsyncCrystalScanner();
   private final AsyncCrystalManager asyncCrystalManager = new AsyncCrystalManager();
   private final ThreadLocal<CrystalWorldSnapshot> calcSnapshot = new ThreadLocal<>();
   private volatile CrystalWorldSnapshot latestWorldSnapshot;
   private PlayerEntity cevTarget;
   private int placesThisTick;
   private int tempBreakCrystalId = -1;
   private long killAuraPauseUntil;
   private int lastCrystalSlot = -1;
   private int packetsSentThisTick = 0;
   private long lastPacketTick = -1L;

   public AutoCrystal() {
      super("AutoCrystal", Module.Category.Combat);
      this.setChinese("\u81ea\u52a8\u6c34\u6676");
      INSTANCE = this;
      CrucifixBeta.EVENT_BUS.subscribe(new AutoCrystal.CrystalRender());
   }

   @Override
   public String getInfo() {
      if (this.displayTarget != null && this.lastDamage > 0.0F) {
         return this.df.format((double)this.lastDamage);
      } else {
         return this.cevTarget != null && this.cevBreaker.getValue() ? this.cevTarget.getName().getString() : null;
      }
   }

   @Override
   public void onDisable() {
      this.crystalPos = null;
      this.tempPos = null;
      this.cevTarget = null;
      this.killAuraPauseUntil = 0L;
   }

   @Override
   public void onEnable() {
      this.crystalPos = null;
      this.tempPos = null;
      this.tempBreakCrystal = null;
      this.tempBreakCrystalId = -1;
      this.displayTarget = null;
      this.cevTarget = null;
      this.placePackets.clear();
      this.deadCrystals.clear();
      this.placesThisTick = 0;
      this.killAuraPauseUntil = 0L;
      this.lastCrystalSlot = -1;
      this.packetsSentThisTick = 0;
      this.lastPacketTick = -1L;
      this.syncTimer.reset();
      this.lastBreakTimer.reset();
   }

   public boolean shouldPauseKillAura() {
      if (!this.pauseKillAura.getValue() || this.isOff() || nullCheck()) {
         return false;
      } else if (this.hasHighDamageCrystalOpportunity()) {
         this.killAuraPauseUntil = System.currentTimeMillis() + (long)this.killAuraPauseHold.getValueInt();
         return true;
      } else {
         return System.currentTimeMillis() <= this.killAuraPauseUntil;
      }
   }

   public boolean canExecuteNow() {
      if (!this.isOff() && !nullCheck()) {
         if (this.healthPause.getValue() && EntityUtil.getHealth(mc.player) <= this.pauseHealth.getValueFloat()) {
            return false;
         } else if ((!this.eatingPause.getValue() || !this.shouldPauseForUsingItem()) && (!Blink.INSTANCE.isOn() || !Blink.INSTANCE.pauseModule.getValue())) {
            if (this.preferAnchor.getValue() && AutoAnchor.INSTANCE != null && AutoAnchor.INSTANCE.currentPos != null) {
               return false;
            } else {
               return this.pause.isPressed()
                  ? false
                  : this.place.getValue() && this.crystalPos != null && this.lastDamage > 0.0F
                     || this.breakSetting.getValue()
                        && (this.breakCrystal != null || this.tempBreakCrystal != null || this.tempBreakCrystalId != -1)
                        && this.breakDamage > 0.0F;
            }
         } else {
            return false;
         }
      } else {
         return false;
      }
   }

   public float getBestDamageEstimate() {
      return Math.max(this.lastDamage, Math.max(this.tempDamage, this.breakDamage));
   }

   private boolean hasHighDamageCrystalOpportunity() {
      if (!this.place.getValue() || this.displayTarget == null || this.crystalPos == null) {
         return false;
      } else if (!this.displayTarget.isRemoved() && !this.displayTarget.isDead() && CombatUtil.isValid(this.displayTarget)) {
         if (this.lastDamage < this.killAuraPauseDamage.getValueFloat()) {
            return false;
         } else {
            return mc.player.getEyePos().distanceTo(this.crystalPos.toCenterPos()) > this.placeRange.getValue() + 1.5
               ? false
               : this.canPlaceCrystal(this.crystalPos, true, this.shouldIgnoreItems());
         }
      } else {
         return false;
      }
   }

   public void onThread() {
      if (!this.isOff()) {
         if (this.thread.getValue()) {
            this.asyncScanner.run();
         }
      }
   }

   @EventListener
   public void onTick(ClientTickEvent event) {
      if (!nullCheck()) {
         if (this.healthPause.getValue() && EntityUtil.getHealth(mc.player) <= this.pauseHealth.getValueFloat()) {
            this.lastBreakTimer.reset();
         } else {
            if (event.isPre()) {
               this.placesThisTick = 0;
               this.pruneOptimizedCrystalState();
               this.updateWorldSnapshot();
            }

            if ((!this.timing.is(Timing.Pre) || !event.isPost()) && (!this.timing.is(Timing.Post) || !event.isPre())) {
               if (!this.thread.getValue()) {
                  this.updateCrystalPos();
               } else {
                  this.applyAsyncResult();
               }

               if (!this.shouldReturn()) {
                  this.doInteract();
                  BlockPos var2 = this.basePos;
                  if (this.basePlace.getValue() && var2 != null && BlockUtil.canPlace(var2)) {
                     this.doPlace(var2);
                  }

                  if (this.cevBreaker.getValue()) {
                     this.doCevBreaker();
                  }
               }
            }
         }
      }
   }

   @Override
   public void onRender3D(MatrixStack matrixStack) {
      if (this.interactOnRender.getValue() && !this.shouldReturn()) {
         this.doInteract();
         BlockPos var2 = this.basePos;
         if (this.basePlace.getValue() && var2 != null && BlockUtil.canPlace(var2)) {
            this.doPlace(var2);
         }
      }

      if (this.displayTarget != null && !this.noPosTimer.passed(500L)) {
         this.doRender(matrixStack, mc.getRenderTickCounter().getTickDelta(true), this.displayTarget, this.mode.getValue());
      }
   }

   public void doRender(MatrixStack stack, float partialTicks, Entity entity, AutoCrystal.TargetESP mode) {
      switch (mode) {
         case Box:
            Render3DUtil.draw3DBox(
               stack,
               ((IEntity)entity)
                  .getDimensions()
                  .getBoxAt(
                     new Vec3d(
                        MathUtil.interpolate(entity.lastRenderX, entity.getX(), (double)partialTicks),
                        MathUtil.interpolate(entity.lastRenderY, entity.getY(), (double)partialTicks),
                        MathUtil.interpolate(entity.lastRenderZ, entity.getZ(), (double)partialTicks)
                     )
                  )
                  .expand(0.0, 0.1, 0.0),
               ColorUtil.fadeColor(
                  this.color.getValue(), this.hitColor.getValue(), this.animation.get(0.0, (long)this.animationTime.getValueInt(), this.ease.getValue())
               ),
               ColorUtil.fadeColor(
                  this.outlineColor.getValue(),
                  this.hitOutlineColor.getValue(),
                  this.animation.get(0.0, (long)this.animationTime.getValueInt(), this.ease.getValue())
               ),
               true,
               true
            );
            break;
         case Fill:
            Render3DUtil.draw3DBox(
               stack,
               ((IEntity)entity)
                  .getDimensions()
                  .getBoxAt(
                     new Vec3d(
                        MathUtil.interpolate(entity.lastRenderX, entity.getX(), (double)partialTicks),
                        MathUtil.interpolate(entity.lastRenderY, entity.getY(), (double)partialTicks),
                        MathUtil.interpolate(entity.lastRenderZ, entity.getZ(), (double)partialTicks)
                     )
                  )
                  .expand(0.0, 0.1, 0.0),
               ColorUtil.fadeColor(
                  this.color.getValue(), this.hitColor.getValue(), this.animation.get(0.0, (long)this.animationTime.getValueInt(), this.ease.getValue())
               ),
               ColorUtil.fadeColor(
                  this.outlineColor.getValue(),
                  this.hitOutlineColor.getValue(),
                  this.animation.get(0.0, (long)this.animationTime.getValueInt(), this.ease.getValue())
               ),
               false,
               true
            );
            break;
         case Jello:
            JelloUtil.drawJello(stack, entity, this.color.getValue());
            break;
         case ThunderHack:
            Render3DUtil.drawTargetEsp(stack, this.displayTarget, this.color.getValue());
      }
   }

   private void doInteract() {
      BlockPos var1 = this.crystalPos;
      if (var1 != null) {
         this.doCrystal(var1);
      }

      if (this.breakCrystal != null) {
         this.doBreak(this.breakCrystal);
         this.breakCrystal = null;
      }
   }

   @EventListener
   public void onRotate(RotationEvent event) {
      if (this.rotate.getValue()
         && this.shouldYawStep()
         && this.directionVec != null
         && this.displayTarget != null
         && !this.noPosTimer.passed(1000L)
         && !this.shouldReturn()) {
         event.setTarget(this.directionVec, this.steps.getValueFloat(), this.priority.getValueFloat());
      }
   }

   @EventListener(
      priority = -199
   )
   public void onPacketSend(PacketEvent.Send event) {
      if (!event.isCancelled()) {
         Packet var2 = event.getPacket();
         UpdateSelectedSlotC2SPacket var3;
         if (var2 instanceof UpdateSelectedSlotC2SPacket && this.lastSlot != (var3 = (UpdateSelectedSlotC2SPacket)var2).getSelectedSlot()) {
            this.lastSlot = var3.getSelectedSlot();
            this.switchTimer.reset();
         }
      }
   }

   public Vec3d getAttackVec(Vec3d feetPos) {
      return MathUtil.getPointToBoxFromBottom(mc.player.getEyePos(), feetPos, this.breakRange.getValue(), 2.0, this.attackVecStep.getValue());
   }

   private void updateCrystalPos() {
      if (this.calcDelay.passedMs(this.updateDelay.getValue())) {
         this.calcDelay.reset();
         this.calcCrystalPos(false, null);
         CombatUtil.modifyPos = null;
         CombatUtil.modifyBlockState = null;
         this.basePos = this.tempBasePos;
         this.lastDamage = this.tempDamage;
         this.breakCrystal = this.tempBreakCrystal;
         this.crystalPos = this.tempPos;
      }
   }

   private boolean shouldReturn() {
      if ((!this.eatingPause.getValue() || !this.shouldPauseForUsingItem()) && (!Blink.INSTANCE.isOn() || !Blink.INSTANCE.pauseModule.getValue())) {
         if (this.preferAnchor.getValue() && AutoAnchor.INSTANCE != null && AutoAnchor.INSTANCE.currentPos != null) {
            this.lastBreakTimer.reset();
            return true;
         } else if (this.pause.isPressed()) {
            this.lastBreakTimer.reset();
            return true;
         } else {
            return false;
         }
      } else {
         this.lastBreakTimer.reset();
         return true;
      }
   }

   private void doCevBreaker() {
      if (this.cevInventorySwap.getValue() && !EntityUtil.inInventory()) {
         this.cevTarget = null;
      } else if (this.cevGround.getValue() && !mc.player.isOnGround()) {
         this.cevTarget = null;
      } else {
         this.cevTarget = CombatUtil.getClosestEnemy(this.cevTargetRange.getValue());
         if (this.cevTarget != null) {
            BlockPos var1 = EntityUtil.getEntityPos(this.cevTarget);
            BlockPos var2 = PacketMine.getBreakPos();

            for (Direction var6 : Direction.values()) {
               if (var6 != Direction.DOWN && (var6 == Direction.UP || this.cevBevel.getValue()) && (var6 != Direction.UP || this.cevTop.getValue())) {
                  BlockPos var7 = var1.up(1).offset(var6);
                  if (!(var7.up().toCenterPos().distanceTo(mc.player.getPos()) > this.cevBreakRange.getValue())) {
                     Block var8 = this.getCevBlock(var7);
                     if (var8 == Blocks.OBSIDIAN || var8 == Blocks.BEDROCK) {
                        if (var2 == null || !var2.equals(var7)) {
                           PacketMine.INSTANCE.mine(var7);
                        }

                        if (this.cevCrystal.getValue()) {
                           if (this.canPlaceCevCrystal(var7.up())) {
                              boolean var9 = !this.cevDetectProgress.getValue() || PacketMine.progress >= this.cevProgress.getValue();
                              if (var9 && this.cevPlaceTimer.passedMs(this.cevPlaceDelay.getValue())) {
                                 this.placeCevCrystal(var7);
                                 this.cevPlaceTimer.reset();
                              }
                           }

                           if (this.cevAfterBreak.getValue() && PacketMine.complete) {
                              for (Direction var12 : Direction.values()) {
                                 CombatUtil.attackCrystal(var7.offset(var12), this.cevPlaceRotate.getValue(), true);
                              }
                           }
                        }
                        break;
                     }

                     if (!mc.world.isAir(var7)) {
                        if (!this.cevTimer.passedMs(this.cevDelay.getValue())) {
                           return;
                        }

                        PacketMine.INSTANCE.mine(var7);
                        this.cevTimer.reset();
                        break;
                     }

                     if (BlockUtil.canPlace(var7)) {
                        if (!this.cevTimer.passedMs(this.cevDelay.getValue())) {
                           return;
                        }

                        this.placeCevBlock(var7);
                        this.cevTimer.reset();
                        break;
                     }
                  }
               }
            }
         }
      }
   }

   private boolean canPlaceCevCrystal(BlockPos crystalPos) {
      BlockPos var2 = crystalPos.down();
      Block var3 = this.getCevBlock(var2);
      if (var3 != Blocks.BEDROCK && var3 != Blocks.OBSIDIAN) {
         return false;
      } else if (BlockUtil.getClickSideStrict(var2) == null) {
         return false;
      } else {
         return this.cevNoEntity(crystalPos) && this.cevNoEntity(crystalPos.up())
            ? !ClientSetting.INSTANCE.lowVersion.getValue() || mc.world.isAir(crystalPos.up())
            : false;
      }
   }

   private boolean cevNoEntity(BlockPos pos) {
      for (Entity var3 : BlockUtil.getEntities(new Box(pos))) {
         if (!(var3 instanceof ItemEntity) && (!(var3 instanceof ArmorStandEntity) || !AntiCheat.INSTANCE.ignoreArmorStand.getValue())) {
            return false;
         }
      }

      return true;
   }

   private void placeCevCrystal(BlockPos obsidianPos) {
      int var2 = this.getCevCrystalSlot();
      if (var2 != -1) {
         BlockPos var3 = obsidianPos.up();
         int var4 = mc.player.getInventory().selectedSlot;
         this.doCevSwap(var2);
         BlockUtil.placeCrystal(var3, this.cevPlaceRotate.getValue());
         this.doCevRestore(var4, var2);
      }
   }

   private void placeCevBlock(BlockPos pos) {
      int var2 = this.getCevBlockSlot();
      if (var2 != -1) {
         int var3 = mc.player.getInventory().selectedSlot;
         this.doCevSwap(var2);
         if (BlockUtil.canPlace(pos)) {
            if (BlockUtil.allowAirPlace()) {
               BlockUtil.airPlace(pos, this.cevRotate.getValue());
            } else {
               Direction var4 = BlockUtil.getPlaceSide(pos);
               if (var4 != null) {
                  BlockUtil.clickBlock(pos.offset(var4), var4.getOpposite(), this.cevRotate.getValue());
               }
            }
         }

         this.doCevRestore(var3, var2);
      }
   }

   private int getCevBlockSlot() {
      return this.cevInventorySwap.getValue() ? InventoryUtil.findBlockInventorySlot(Blocks.OBSIDIAN) : InventoryUtil.findBlock(Blocks.OBSIDIAN);
   }

   private int getCevCrystalSlot() {
      return this.cevInventorySwap.getValue() ? InventoryUtil.findItemInventorySlot(Items.END_CRYSTAL) : InventoryUtil.findItem(Items.END_CRYSTAL);
   }

   private void doCevSwap(int slot) {
      if (this.cevInventorySwap.getValue()) {
         InventoryUtil.inventorySwap(slot, mc.player.getInventory().selectedSlot);
      } else {
         InventoryUtil.switchToSlot(slot);
      }
   }

   private void doCevRestore(int oldSlot, int swappedSlot) {
      if (this.cevInventorySwap.getValue()) {
         InventoryUtil.inventorySwap(swappedSlot, mc.player.getInventory().selectedSlot);
         EntityUtil.syncInventory();
      } else {
         InventoryUtil.switchToSlot(oldSlot);
      }
   }

   private Block getCevBlock(BlockPos pos) {
      return mc.world.getBlockState(pos).getBlock();
   }

   private void calcCrystalPos(boolean async, CrystalWorldSnapshot snapshot) {
      if (!nullCheck()) {
         this.calcSnapshot.set(snapshot);
         if (this.breakOnlyHasCrystal.getValue()
            && !mc.player.getMainHandStack().getItem().equals(Items.END_CRYSTAL)
            && !mc.player.getOffHandStack().getItem().equals(Items.END_CRYSTAL)
            && !this.hasCrystal()) {
            this.tempPos = null;
            this.tempBreakCrystal = null;
            this.tempBreakCrystalId = -1;
            this.lastBreakTimer.reset();
            this.calcSnapshot.remove();
         } else {
            boolean var3 = this.shouldReturn();
            boolean var4 = this.basePlace.getValue() && this.baseTimer.passedMs(this.delay.getValue()) && this.getBlock() != -1;
            this.tempBreakCrystal = null;
            this.tempBreakCrystalId = -1;
            this.breakDamage = 0.0F;
            this.tempPos = null;
            this.tempDamage = 0.0F;
            this.tempBasePos = null;
            float var5 = 0.0F;
            ArrayList<PlayerEntityPredict> var6 = new ArrayList<>();
            int var7 = 0;

            for (PlayerEntity var9 : CombatUtil.getEnemies((double)this.targetRange.getValueFloat())) {
               if (var7 >= this.maxTargets.getValueInt()) {
                  break;
               }

               if ((!this.ignoreNakeds.getValue() || !this.isNaked(var9)) && var9.hurtTime <= this.hurtTime.getValueInt()) {
                  var6.add(
                     new PlayerEntityPredict(
                        var9,
                        this.maxMotionY.getValue(),
                        this.predictTicks.getValueInt(),
                        this.simulation.getValueInt(),
                        this.step.getValue(),
                        this.doubleStep.getValue(),
                        this.jump.getValue(),
                        this.inBlockPause.getValue()
                     )
                  );
                  var7++;
               }
            }

            PlayerEntityPredict var25 = new PlayerEntityPredict(
               mc.player,
               this.maxMotionY.getValue(),
               this.selfPredict.getValueInt(),
               this.simulation.getValueInt(),
               this.step.getValue(),
               this.doubleStep.getValue(),
               this.jump.getValue(),
               this.inBlockPause.getValue()
            );
            if (var6.isEmpty()) {
               this.lastBreakTimer.reset();
            } else {
               if (async && snapshot != null) {
                  for (CrystalEntitySnapshot var32 : snapshot.crystals()) {
                     Vec3d var35;
                     if (!this.isOptimizedDead(var32.id())
                        && var32.age() >= this.minAge.getValueInt()
                        && (var35 = this.getAttackVec(var32.pos())) != null
                        && !(mc.player.getEyePos().distanceTo(var35) > this.wallRange.getValue())) {
                        float var37 = this.calculateDamage(var32.pos(), var25.player, var25.predict);

                        for (PlayerEntityPredict var42 : var6) {
                           float var27 = this.calculateDamage(var32.pos(), var42.player, var42.predict);
                           if (this.passesDamageRatio(var27, var37)
                              && var27 > this.breakDamage
                              && !((double)var37 > this.maxSelf.getValue())
                              && (
                                 !(this.reserve.getValue() > 0.0)
                                    || !((double)var37 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue())
                              )
                              && (
                                 !(var27 < EntityUtil.getHealth(var42.player))
                                    || !((double)var27 < this.getDamage(var42.player))
                                       && (
                                          !this.balance.getValue()
                                             || (
                                                this.getDamage(var42.player) == this.forceMin.getValue()
                                                   ? !((double)var27 < (double)var37 - 2.5)
                                                   : !((double)var27 < (double)var37 + this.balanceOffset.getValue())
                                             )
                                       )
                              )) {
                              this.breakDamage = var27;
                              this.tempBreakCrystalId = var32.id();
                              this.displayTarget = var42.player;
                           }
                        }
                     }
                  }
               } else {
                  for (Entity var11 : CrucifixBeta.THREAD.getEntities()) {
                     Vec3d var12;
                     if (var11 instanceof EndCrystalEntity var14
                        && !this.isOptimizedDead(var14)
                        && var11.age >= this.minAge.getValueInt()
                        && (var12 = this.getAttackVec(var14.getPos())) != null
                        && (mc.player.canSee(var14) || !(mc.player.getEyePos().distanceTo(var12) > this.wallRange.getValue()))) {
                        float var13 = this.calculateDamage(var14.getPos(), var25.player, var25.predict);

                        for (PlayerEntityPredict var16 : var6) {
                           float var26 = this.calculateDamage(var14.getPos(), var16.player, var16.predict);
                           if (this.passesDamageRatio(var26, var13)
                              && var26 > this.breakDamage
                              && !((double)var13 > this.maxSelf.getValue())
                              && (
                                 !(this.reserve.getValue() > 0.0)
                                    || !((double)var13 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue())
                              )
                              && (
                                 !(var26 < EntityUtil.getHealth(var16.player))
                                    || !((double)var26 < this.getDamage(var16.player))
                                       && (
                                          !this.balance.getValue()
                                             || (
                                                this.getDamage(var16.player) == this.forceMin.getValue()
                                                   ? !((double)var26 < (double)var13 - 2.5)
                                                   : !((double)var26 < (double)var13 + this.balanceOffset.getValue())
                                             )
                                       )
                              )) {
                              this.breakDamage = var26;
                              this.tempBreakCrystal = var14;
                              this.tempBreakCrystalId = var14.getId();
                              this.displayTarget = var16.player;
                           }
                        }
                     }
                  }
               }

               if (!async && this.doCrystal.getValue() && this.tempBreakCrystal != null && !var3) {
                  this.doBreak(this.tempBreakCrystal);
                  this.tempBreakCrystal = null;
               }

               for (BlockPos var33 : BlockUtil.getSphere((float)this.breakRange.getValue() + 1.5F)) {
                  boolean var40 = false;
                  CombatUtil.modifyPos = null;
                  CombatUtil.modifyBlockState = null;
                  if (var4 && this.canPlaceBaseCandidate(var33.down())) {
                     CombatUtil.modifyPos = var33.down();
                     CombatUtil.modifyBlockState = Blocks.OBSIDIAN.getDefaultState();
                     var40 = true;
                  }

                  Vec3d var36;
                  if ((!var40 || !CrucifixBeta.BREAK.isMining(var33.down()) || !this.detectMining.getValue())
                     && (var36 = this.getAttackVec(var33.toBottomCenterPos())) != null
                     && !this.behindWall(var33, var36)
                     && this.canTouchCandidate(var33.down())
                     && this.canPlaceCrystal(var33, true, this.shouldIgnoreItems())) {
                     float var38 = var40
                        ? this.calculateBaseDamage(var33, var25.player, var25.predict)
                        : this.calculateDamage(var33, var25.player, var25.predict);

                     for (PlayerEntityPredict var45 : var6) {
                        if (!var40 || !this.onlyBelow.getValue() || !((double)var33.getY() - 0.5 > var45.player.getY())) {
                           float var28 = var40
                              ? this.calculateBaseDamage(var33, var45.player, var45.predict)
                              : this.calculateDamage(var33, var45.player, var45.predict);
                           if (var40) {
                              if (this.passesDamageRatio(var28, var38)
                                 && (double)this.tempDamage <= this.overrideMax.getValue()
                                 && var28 > this.tempDamage
                                 && var28 > var5
                                 && !((double)var38 > this.baseMax.getValue())
                                 && (
                                    !(this.reserve.getValue() > 0.0)
                                       || !((double)var38 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue())
                                 )
                                 && (
                                    !(var28 < EntityUtil.getHealth(var45.player))
                                       || !((double)var28 < this.baseMin.getValue()) && (!this.baseBalance.getValue() || !(var28 < var38))
                                 )) {
                                 this.displayTarget = var45.player;
                                 var5 = var28;
                                 this.tempBasePos = var33.down();
                                 this.tempPos = null;
                              }
                           } else if (this.passesDamageRatio(var28, var38)
                              && var28 > this.tempDamage
                              && (var28 >= var5 || (double)this.tempDamage > this.overrideMax.getValue())
                              && !((double)var38 > this.maxSelf.getValue())
                              && (
                                 !(this.reserve.getValue() > 0.0)
                                    || !((double)var38 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue())
                              )
                              && (
                                 !(var28 < EntityUtil.getHealth(var45.player))
                                    || !((double)var28 < this.getDamage(var45.player))
                                       && (
                                          !this.balance.getValue()
                                             || (
                                                this.getDamage(var45.player) == this.forceMin.getValue()
                                                   ? !((double)var28 < (double)var38 - 2.5)
                                                   : !((double)var28 < (double)var38 + this.balanceOffset.getValue())
                                             )
                                       )
                              )) {
                              this.displayTarget = var45.player;
                              this.tempPos = var33;
                              this.tempBasePos = null;
                              this.tempDamage = var28;
                           }
                        }
                     }
                  }
               }

               CombatUtil.modifyPos = null;
               CombatUtil.modifyBlockState = null;
               if (this.antiSurround.getValue()
                  && PacketMine.getBreakPos() != null
                  && PacketMine.progress >= (double)this.miningProgress.getValueFloat()
                  && !BlockUtil.hasEntity(PacketMine.getBreakPos(), false)
                  && this.tempDamage <= this.antiSurroundMax.getValueFloat()) {
                  for (PlayerEntityPredict var34 : var6) {
                     BlockPosX var41 = new BlockPosX(var34.player.getPos().add(0.0, 0.5, 0.0));
                     if (!BlockUtil.canCollide(var34.player, new Box(var41))) {
                        for (Direction var18 : Direction.values()) {
                           BlockPos var19;
                           if (var18 != Direction.DOWN && var18 != Direction.UP && (var19 = var41.offset(var18)).equals(PacketMine.getBreakPos())) {
                              for (Direction var23 : Direction.values()) {
                                 float var24;
                                 if (var23 != Direction.DOWN
                                    && var23 != Direction.UP
                                    && this.canPlaceCrystal(var19.offset(var23), false, this.shouldIgnoreItems())
                                    && (double)(var24 = this.calculateDamage(var19.offset(var23), var25.player, var25.predict)) < this.maxSelf.getValue()
                                    && (
                                       !(this.reserve.getValue() > 0.0)
                                          || !((double)var24 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue())
                                    )) {
                                    this.tempPos = var19.offset(var23);
                                    if (!async && this.doCrystal.getValue() && this.tempPos != null && !var3) {
                                       this.doCrystal(this.tempPos);
                                    }

                                    return;
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }

            if (!async && this.doCrystal.getValue() && this.tempPos != null && !var3) {
               this.doCrystal(this.tempPos);
            }

            this.calcSnapshot.remove();
         }
      }
   }

   private void applyAsyncResult() {
      AsyncCrystalResult var1 = this.asyncScanner.poll();
      if (var1 != null) {
         this.basePos = var1.basePos();
         this.lastDamage = var1.damage();
         this.breakCrystal = this.resolveCrystal(var1.breakCrystalId());
         this.crystalPos = var1.crystalPos();
         this.displayTarget = this.resolvePlayer(var1.displayTargetId());
      }
   }

   @EventListener
   private void onEntity(EntitySpawnedEvent event) {
      if (event.getEntity() instanceof EndCrystalEntity var3) {
         BlockPos var4 = var3.getBlockPos().down();
         boolean var5 = this.placePackets.remove(var4) != null || var4.equals(this.syncPos == null ? null : this.syncPos.down());
         if ((this.onAdd.getValue() || this.instantBreak.getValue() || this.predictAttack.getValue()) && var5) {
            this.doBreak(var3);
            if (this.predictAttack.getValue()) {
               this.markDead(var3);
            }
         } else if (this.spawnPredict.getValue() && this.shouldSpawnPredict(var3)) {
            this.doBreak(var3);
            this.markDead(var3);
         }
      }
   }

   public boolean canPlaceCrystal(BlockPos pos, boolean ignoreCrystal, boolean ignoreItem) {
      BlockPos var4 = pos.down();
      BlockPos var5 = var4.up();
      BlockPos var6 = var5.up();
      CrystalWorldSnapshot var7 = this.calcSnapshot.get();
      if (var7 != null) {
         boolean var8 = var7.isAir(var5) || var7.hasCrystal(var5) && var7.block(var5) == Blocks.FIRE;
         boolean var9 = !ClientSetting.INSTANCE.lowVersion.getValue() || var7.isAir(var6);
         return (var7.block(var4) == Blocks.BEDROCK || var7.block(var4) == Blocks.OBSIDIAN)
            && this.canTouchSnapshot(var4)
            && this.noEntityBlockCrystal(var5, ignoreCrystal, ignoreItem)
            && this.noEntityBlockCrystal(var6, ignoreCrystal, ignoreItem)
            && var8
            && var9;
      } else {
         return (BlockUtil.getBlock(var4) == Blocks.BEDROCK || BlockUtil.getBlock(var4) == Blocks.OBSIDIAN)
            && BlockUtil.getClickSideStrict(var4) != null
            && this.noEntityBlockCrystal(var5, ignoreCrystal, ignoreItem)
            && this.noEntityBlockCrystal(var6, ignoreCrystal, ignoreItem)
            && (mc.world.isAir(var5) || BlockUtil.hasCrystal(var5) && BlockUtil.getBlock(var5) == Blocks.FIRE)
            && (!ClientSetting.INSTANCE.lowVersion.getValue() || mc.world.isAir(var6));
      }
   }

   private boolean noEntityBlockCrystal(BlockPos pos, boolean ignoreCrystal, boolean ignoreItem) {
      CrystalWorldSnapshot var4 = this.calcSnapshot.get();
      if (var4 != null) {
         for (CrystalEntitySnapshot var8 : var4.entities(new Box(pos))) {
            if (var8.alive()
               && (!var8.item() || !ignoreItem)
               && (
                  !var8.endCrystal()
                     || !ignoreCrystal
                     || this.getAttackVec(var8.pos()) == null
                     || !(mc.player.getEyePos().distanceTo(var8.pos()) <= this.wallRange.getValue())
               )) {
               return false;
            }
         }

         return true;
      } else {
         for (Entity var6 : BlockUtil.getEntities(new Box(pos))) {
            if (var6.isAlive()
               && (!(var6 instanceof ItemEntity) || !ignoreItem)
               && (
                  !(var6 instanceof EndCrystalEntity)
                     || !ignoreCrystal
                     || this.getAttackVec(var6.getPos()) == null
                     || !mc.player.canSee(var6) && !(mc.player.getEyePos().distanceTo(var6.getPos()) <= this.wallRange.getValue())
               )) {
               return false;
            }
         }

         return true;
      }
   }

   public boolean behindWall(BlockPos pos, Vec3d attackVec) {
      if (this.calcSnapshot.get() != null) {
         return mc.player.getEyePos().distanceTo(attackVec) > this.wallRange.getValue();
      } else {
         Vec3d var3 = new Vec3d((double)pos.getX() + 0.5, (double)pos.getY() + 1.7, (double)pos.getZ() + 0.5);
         BlockHitResult var4 = mc.world.raycast(new RaycastContext(mc.player.getEyePos(), var3, ShapeType.COLLIDER, FluidHandling.NONE, mc.player));
         return var4 != null && var4.getType() != Type.MISS ? mc.player.getEyePos().distanceTo(attackVec) > this.wallRange.getValue() : false;
      }
   }

   private boolean canTouch(BlockPos pos) {
      Direction var2 = BlockUtil.getClickSideStrict(pos);
      if (var2 == null) {
         return false;
      } else {
         Vec3d var3 = new Vec3d((double)var2.getVector().getX() * 0.5, (double)var2.getVector().getY() * 0.5, (double)var2.getVector().getZ() * 0.5);
         return pos.toCenterPos().add(var3).distanceTo(mc.player.getEyePos()) <= this.placeRange.getValue();
      }
   }

   private boolean canTouchCandidate(BlockPos pos) {
      return this.calcSnapshot.get() == null ? this.canTouch(pos) : this.canTouchSnapshot(pos);
   }

   private boolean canPlaceBaseCandidate(BlockPos pos) {
      CrystalWorldSnapshot var2 = this.calcSnapshot.get();
      return var2 == null ? BlockUtil.canPlace(pos) : var2.canReplace(pos) && !this.hasBlockingSnapshotEntity(pos, false, false) && this.canTouchSnapshot(pos);
   }

   private boolean hasBlockingSnapshotEntity(BlockPos pos, boolean ignoreCrystal, boolean ignoreItem) {
      CrystalWorldSnapshot var4 = this.calcSnapshot.get();
      if (var4 == null) {
         return BlockUtil.hasEntity(pos, ignoreCrystal);
      } else {
         for (CrystalEntitySnapshot var6 : var4.entities(new Box(pos))) {
            if (var6.alive() && (!var6.item() || !ignoreItem) && (!var6.endCrystal() || !ignoreCrystal)) {
               return true;
            }
         }

         return false;
      }
   }

   private boolean canTouchSnapshot(BlockPos pos) {
      Vec3d var2 = this.calcSnapshot.get() == null ? mc.player.getEyePos() : this.calcSnapshot.get().eyePos();

      for (Direction var6 : Direction.values()) {
         Vec3d var7 = new Vec3d((double)var6.getVector().getX() * 0.5, (double)var6.getVector().getY() * 0.5, (double)var6.getVector().getZ() * 0.5);
         if (pos.toCenterPos().add(var7).distanceTo(var2) <= this.placeRange.getValue()) {
            return true;
         }
      }

      return false;
   }

   private void updateWorldSnapshot() {
      if (this.thread.getValue() && mc.world != null && mc.player != null) {
         double var1 = Math.max(Math.max(this.targetRange.getValue(), this.breakRange.getValue() + 4.0), this.placeRange.getValue() + 4.0);
         this.latestWorldSnapshot = CrystalWorldSnapshot.capture(mc.world, CrucifixBeta.THREAD.getEntities(), mc.player.getEyePos(), var1);
      } else {
         this.latestWorldSnapshot = null;
      }
   }

   private void doCrystal(BlockPos pos) {
      if (this.canPlaceCrystal(pos, false, this.shouldIgnoreItems())) {
         this.doPlace(pos, this.rotate.getValue() && this.onPlace.getValue());
      }

      this.doBreak(pos);
   }

   private boolean shouldIgnoreItems() {
      return this.targetItems.getValue() || this.ignoreItems.getValue();
   }

   private void doPlace(BlockPos pos) {
      if (this.baseTimer.passed((long)this.delay.getValue())) {
         if (!this.detectMining.getValue() || !CrucifixBeta.BREAK.isMining(pos)) {
            int var2 = this.getBlock();
            if (var2 != -1) {
               int var3 = mc.player.getInventory().selectedSlot;
               this.baseSwap(var2);
               BlockUtil.placeBlock(pos, this.rotate.getValue());
               if (this.inventory.getValue()) {
                  this.baseSwap(var2);
                  EntityUtil.syncInventory();
               } else {
                  this.baseSwap(var3);
               }

               this.baseTimer.reset();
            }
         }
      }
   }

   public float calculateDamage(BlockPos pos, PlayerEntity player, PlayerEntity predict) {
      return this.calculateDamage(new Vec3d((double)pos.getX() + 0.5, (double)pos.getY(), (double)pos.getZ() + 0.5), player, predict);
   }

   public float calculateDamage(Vec3d pos, PlayerEntity player, PlayerEntity predict) {
      CrystalWorldSnapshot var4 = this.calcSnapshot.get();
      if (var4 != null) {
         DamageUtils.RaycastFactory var9 = (context, blockPos) -> {
            if (this.ignoreMine.getValue()
               && PacketMine.getBreakPos() != null
               && blockPos.equals(PacketMine.getBreakPos())
               && mc.player.getEyePos().distanceTo(PacketMine.getBreakPos().toCenterPos()) <= PacketMine.INSTANCE.range.getValue()
               && PacketMine.progress >= this.constantProgress.getValue() / 100.0) {
               return null;
            } else if (CombatUtil.modifyPos != null && blockPos.equals(CombatUtil.modifyPos)) {
               return CombatUtil.modifyBlockState.getCollisionShape(var4, blockPos).raycast(context.start(), context.end(), blockPos);
            } else {
               return var4.block(blockPos).getBlastResistance() < 600.0F && this.terrainIgnore.getValue()
                  ? null
                  : var4.blockState(blockPos).getCollisionShape(var4, blockPos).raycast(context.start(), context.end(), blockPos);
            }
         };
         return DamageUtils.explosionDamage(player, predict, pos, 12.0F, var9);
      } else {
         float var5;
         try {
            if (this.ignoreMine.getValue()
               && PacketMine.getBreakPos() != null
               && mc.player.getEyePos().distanceTo(PacketMine.getBreakPos().toCenterPos()) <= PacketMine.INSTANCE.range.getValue()
               && PacketMine.progress >= this.constantProgress.getValue() / 100.0) {
               CombatUtil.modifyPos = PacketMine.getBreakPos();
               CombatUtil.modifyBlockState = Blocks.AIR.getDefaultState();
            }

            if (this.terrainIgnore.getValue()) {
               CombatUtil.terrainIgnore = true;
            }

            var5 = ExplosionUtil.calculateDamage(pos, player, predict, 6.0F);
         } finally {
            CombatUtil.modifyPos = null;
            CombatUtil.modifyBlockState = null;
            CombatUtil.terrainIgnore = false;
         }

         return var5;
      }
   }

   public float calculateBaseDamage(BlockPos pos, PlayerEntity player, PlayerEntity predict) {
      CrystalWorldSnapshot var4 = this.calcSnapshot.get();
      if (var4 != null) {
         DamageUtils.RaycastFactory var9 = (context, blockPos) -> {
            if (blockPos.equals(pos.down())) {
               return Blocks.OBSIDIAN.getDefaultState().getCollisionShape(var4, blockPos).raycast(context.start(), context.end(), blockPos);
            } else {
               return var4.block(blockPos).getBlastResistance() < 600.0F && this.terrainIgnore.getValue()
                  ? null
                  : var4.blockState(blockPos).getCollisionShape(var4, blockPos).raycast(context.start(), context.end(), blockPos);
            }
         };
         return DamageUtils.explosionDamage(player, predict, new Vec3d((double)pos.getX() + 0.5, (double)pos.getY(), (double)pos.getZ() + 0.5), 12.0F, var9);
      } else {
         float var5;
         try {
            if (this.terrainIgnore.getValue()) {
               CombatUtil.terrainIgnore = true;
            }

            var5 = DamageUtils.overridingExplosionDamage(
               player,
               predict,
               new Vec3d((double)pos.getX() + 0.5, (double)pos.getY(), (double)pos.getZ() + 0.5),
               12.0F,
               pos.down(),
               Blocks.OBSIDIAN.getDefaultState()
            );
         } finally {
            CombatUtil.terrainIgnore = false;
         }

         return var5;
      }
   }

   private double getDamage(PlayerEntity target) {
      if (this.slowPlace.getValue() && this.lastBreakTimer.passed((long)this.slowDelay.getValue())) {
         return this.slowMinDamage.getValue();
      } else if (this.lethalOverride.getValue() && (double)EntityUtil.getHealth(target) <= this.forceMaxHealth.getValue()) {
         return this.forceMin.getValue();
      } else {
         if (this.armorBreaker.getValue()) {
            for (ItemStack var4 : target.getInventory().armor) {
               if (!var4.isEmpty() && !((double)EntityUtil.getDamagePercent(var4) > this.maxDurable.getValue())) {
                  return this.armorBreakerDamage.getValue();
               }
            }
         }

         return this.minDamage.getValue();
      }
   }

   private boolean passesDamageRatio(float damage, float selfDamage) {
      double var3 = this.damageRatio.getValue();
      return !(var3 <= 0.0) && !(selfDamage <= 0.0F) ? (double)damage >= (double)selfDamage * var3 : true;
   }

   private boolean isNaked(PlayerEntity player) {
      for (ItemStack var3 : player.getArmorItems()) {
         if (!var3.isEmpty()) {
            return false;
         }
      }

      return true;
   }

   private boolean shouldSpawnPredict(EndCrystalEntity crystal) {
      if (mc.player != null && mc.world != null && crystal.isAlive()) {
         if (crystal.age < this.minAge.getValueInt()) {
            return false;
         } else {
            Vec3d var2;
            if ((var2 = this.getAttackVec(crystal.getPos())) == null) {
               return false;
            } else if (mc.player.getEyePos().distanceTo(var2) > this.breakRange.getValue()) {
               return false;
            } else if (!mc.player.canSee(crystal) && mc.player.getEyePos().distanceTo(var2) > this.wallRange.getValue()) {
               return false;
            } else {
               float var3 = this.calculateDamage(crystal.getPos(), mc.player, mc.player);
               if ((double)var3 > this.maxSelf.getValue()) {
                  return false;
               } else if (this.reserve.getValue() > 0.0
                  && (double)var3 > (double)(mc.player.getHealth() + mc.player.getAbsorptionAmount()) - this.reserve.getValue()) {
                  return false;
               } else {
                  for (PlayerEntity var5 : CombatUtil.getEnemies((double)this.targetRange.getValueFloat())) {
                     if (var5.hurtTime <= this.hurtTime.getValueInt() && (!this.ignoreNakeds.getValue() || !this.isNaked(var5))) {
                        float var6 = this.calculateDamage(crystal.getPos(), var5, var5);
                        if (this.passesDamageRatio(var6, var3)) {
                           double var7 = Math.min(this.getDamage(var5), this.predictMin.getValue());
                           if ((double)var6 >= var7 || var6 >= EntityUtil.getHealth(var5)) {
                              this.displayTarget = var5;
                              this.lastDamage = var6;
                              this.syncPos = crystal.getBlockPos();
                              return true;
                           }
                        }
                     }
                  }

                  return false;
               }
            }
         }
      } else {
         return false;
      }
   }

   private boolean shouldYawStep() {
      return this.whenElytra.getValue() || !mc.player.isFallFlying() && (!ElytraFly.INSTANCE.isOn() || !ElytraFly.INSTANCE.isFallFlying())
         ? this.yawStep.getValue()
         : false;
   }

   public boolean hasCrystal() {
      return this.autoSwap.getValue() == AutoCrystal.SwapMode.None ? false : this.getCrystal() != -1;
   }

   private void doBreak(EndCrystalEntity entity) {
      this.noPosTimer.reset();
      if (this.breakSetting.getValue()) {
         if (entity.isAlive()) {
            if (this.displayTarget == null
               || this.displayTarget.hurtTime <= this.waitHurt.getValueInt()
               || this.syncTimer.passedMs(this.syncTimeout.getValue())) {
               this.lastBreakTimer.reset();
               if (this.switchTimer.passed((long)this.switchCooldown.getValue())) {
                  this.syncTimer.reset();
                  if (entity.age >= this.minAge.getValueInt()) {
                     if (!this.shouldYawStep() && !CombatUtil.breakTimer.passed((long)this.breakDelay.getValue())) {
                        if (this.forcePlace.getValue() && this.crystalPos != null) {
                           this.doPlace(this.crystalPos, false);
                        }
                     } else {
                        Vec3d var2;
                        if (this.rotate.getValue()
                           && this.onBreak.getValue()
                           && !this.faceVector((var2 = this.getAttackVec(entity.getPos())) == null ? entity.getPos() : var2)) {
                           if (this.forcePlace.getValue() && this.crystalPos != null) {
                              this.doPlace(this.crystalPos, false);
                           }
                        } else if (this.shouldYawStep() && !CombatUtil.breakTimer.passed((long)this.breakDelay.getValue())) {
                           if (this.forcePlace.getValue() && this.crystalPos != null) {
                              this.doPlace(this.crystalPos, false);
                           }
                        } else if (this.tryUseCombatTask()) {
                           if (this.canSendCrystalPacket()) {
                              this.animation.to = 1.0;
                              this.animation.from = 1.0;
                              CombatUtil.breakTimer.reset();
                              this.syncPos = entity.getBlockPos();
                              mc.getNetworkHandler().sendPacket(PlayerInteractEntityC2SPacket.attack(entity, mc.player.isSneaking()));
                              if (this.resetCD.getValue()) {
                                 mc.player.resetLastAttackedTicks();
                              }

                              EntityUtil.swingHand(Hand.MAIN_HAND, this.swingMode.getValue());
                              if (this.breakRemove.getValue()) {
                                 mc.world.removeEntity(entity.getId(), RemovalReason.KILLED);
                              }

                              this.markDead(entity);
                              BlockPos var3 = this.crystalPos;
                              if (this.crystalPos != null
                                 && this.displayTarget != null
                                 && (double)this.lastDamage >= this.getDamage(this.displayTarget)
                                 && (this.afterBreak.getValue() || this.instantPlace.getValue())
                                 && (
                                    !this.rotate.getValue()
                                       || !this.shouldYawStep()
                                       || !this.checkFov.getValue()
                                       || CrucifixBeta.ROTATION.inFov(entity.getPos(), this.fov.getValueFloat())
                                 )) {
                                 this.doPlace(var3, false, this.instantPlace.getValue());
                              }

                              if (this.forceWeb.getValue() && AutoWeb.INSTANCE.isOn()) {
                                 AutoWeb.force = true;
                              }

                              if (this.rotate.getValue() && !this.shouldYawStep()) {
                                 CrucifixBeta.ROTATION.snapBack();
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   private void doBreak(BlockPos pos) {
      this.noPosTimer.reset();
      if (this.breakSetting.getValue()) {
         if (this.displayTarget == null || this.displayTarget.hurtTime <= this.waitHurt.getValueInt() || this.syncTimer.passedMs(this.syncTimeout.getValue())) {
            this.lastBreakTimer.reset();
            if (this.switchTimer.passed((long)this.switchCooldown.getValue())) {
               this.syncTimer.reset();

               for (EndCrystalEntity var3 : BlockUtil.getEndCrystals(
                  new Box(
                     (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 2), (double)(pos.getZ() + 1)
                  )
               )) {
                  if (var3.age >= this.minAge.getValueInt() && var3.isAlive()) {
                     if (!this.shouldYawStep() && !CombatUtil.breakTimer.passed((long)this.breakDelay.getValue())) {
                        if (this.forcePlace.getValue() && this.crystalPos != null) {
                           this.doPlace(this.crystalPos, false);
                        }

                        return;
                     }

                     Vec3d var4;
                     if (this.rotate.getValue()
                        && this.onBreak.getValue()
                        && !this.faceVector((var4 = this.getAttackVec(var3.getPos())) == null ? var3.getPos() : var4)) {
                        if (this.forcePlace.getValue() && this.crystalPos != null) {
                           this.doPlace(this.crystalPos, false);
                        }

                        return;
                     }

                     if (this.shouldYawStep() && !CombatUtil.breakTimer.passed((long)this.breakDelay.getValue())) {
                        if (this.forcePlace.getValue() && this.crystalPos != null) {
                           this.doPlace(this.crystalPos, false);
                        }

                        return;
                     }

                     if (!this.tryUseCombatTask()) {
                        return;
                     }

                     if (!this.canSendCrystalPacket()) {
                        return;
                     }

                     this.animation.to = 1.0;
                     this.animation.from = 1.0;
                     CombatUtil.breakTimer.reset();
                     this.syncPos = pos;
                     mc.getNetworkHandler().sendPacket(PlayerInteractEntityC2SPacket.attack(var3, mc.player.isSneaking()));
                     if (this.resetCD.getValue()) {
                        mc.player.resetLastAttackedTicks();
                     }

                     EntityUtil.swingHand(Hand.MAIN_HAND, this.swingMode.getValue());
                     if (this.breakRemove.getValue()) {
                        mc.world.removeEntity(var3.getId(), RemovalReason.KILLED);
                     }

                     this.markDead(var3);
                     BlockPos var5 = this.crystalPos;
                     if (this.crystalPos != null
                        && this.displayTarget != null
                        && (double)this.lastDamage >= this.getDamage(this.displayTarget)
                        && (this.afterBreak.getValue() || this.instantPlace.getValue())
                        && (
                           !this.rotate.getValue()
                              || !this.shouldYawStep()
                              || !this.checkFov.getValue()
                              || CrucifixBeta.ROTATION.inFov(var3.getPos(), this.fov.getValueFloat())
                        )) {
                        this.doPlace(var5, false, this.instantPlace.getValue());
                     }

                     if (this.forceWeb.getValue() && AutoWeb.INSTANCE.isOn()) {
                        AutoWeb.force = true;
                     }

                     if (this.rotate.getValue() && !this.shouldYawStep()) {
                        CrucifixBeta.ROTATION.snapBack();
                     }

                     return;
                  }
               }

               if (this.forcePlace.getValue() && this.crystalPos != null) {
                  this.doPlace(this.crystalPos, false);
               }
            }
         }
      }
   }

   private void doPlace(BlockPos pos, boolean rotate) {
      this.doPlace(pos, rotate, false);
   }

   private void doPlace(BlockPos pos, boolean rotate, boolean forceInstant) {
      this.noPosTimer.reset();
      if (this.place.getValue()) {
         if (this.placesThisTick < this.placeLimit.getValueInt()) {
            if (mc.player.getMainHandStack().getItem().equals(Items.END_CRYSTAL)
               || mc.player.getOffHandStack().getItem().equals(Items.END_CRYSTAL)
               || this.hasCrystal()) {
               if (this.canTouch(pos.down())) {
                  BlockPos var4 = pos.down();
                  Direction var5 = BlockUtil.getClickSide(var4);
                  if (var5 != null) {
                     Vec3d var6 = var4.toCenterPos()
                        .add((double)var5.getVector().getX() * 0.5, (double)var5.getVector().getY() * 0.5, (double)var5.getVector().getZ() * 0.5);
                     if (var5 != Direction.UP && var5 != Direction.DOWN) {
                        var6 = var6.add(0.0, 0.45, 0.0);
                     }

                     if (forceInstant || this.shouldYawStep() || this.placeTimer.passed((long)this.placeDelay.getValue())) {
                        if (!rotate || this.faceVector(var6)) {
                           if (forceInstant || this.placeTimer.passed((long)this.placeDelay.getValue())) {
                              if (this.tryUseCombatTask()) {
                                 if (!mc.player.getMainHandStack().getItem().equals(Items.END_CRYSTAL)
                                    && !mc.player.getOffHandStack().getItem().equals(Items.END_CRYSTAL)) {
                                    this.placeTimer.reset();
                                    this.syncPos = pos;
                                    int var7 = mc.player.getInventory().selectedSlot;
                                    int var8 = this.getCrystal();
                                    if (var8 == -1) {
                                       return;
                                    }

                                    boolean var9 = !this.skipRedundantSwap.getValue()
                                       || this.autoSwap.getValue() == AutoCrystal.SwapMode.Inventory
                                       || var7 != var8;
                                    if (var9) {
                                       this.doSwap(var8);
                                    }

                                    this.placeCrystal(pos);
                                    this.lastCrystalSlot = var8;
                                    if (var9 && this.autoSwap.getValue() == AutoCrystal.SwapMode.Silent) {
                                       this.doSwap(var7);
                                    } else if (var9 && this.autoSwap.getValue() == AutoCrystal.SwapMode.Inventory) {
                                       this.doSwap(var8);
                                       EntityUtil.syncInventory();
                                    }
                                 } else {
                                    this.placeTimer.reset();
                                    this.syncPos = pos;
                                    this.placeCrystal(pos);
                                 }

                                 if (this.syncAfterPlace.getValue()) {
                                    EntityUtil.syncInventory();
                                 }

                                 if (rotate && !this.shouldYawStep()) {
                                    CrucifixBeta.ROTATION.snapBack();
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   private void doSwap(int slot) {
      if (this.autoSwap.getValue() == AutoCrystal.SwapMode.Silent || this.autoSwap.getValue() == AutoCrystal.SwapMode.Normal) {
         InventoryUtil.switchToSlot(slot);
      } else if (this.autoSwap.getValue() == AutoCrystal.SwapMode.Inventory) {
         InventoryUtil.inventorySwap(slot, mc.player.getInventory().selectedSlot);
      }
   }

   private boolean canSendCrystalPacket() {
      if (this.packetOptimize.getValue() && mc.world != null) {
         long var1 = mc.world.getTime();
         if (var1 != this.lastPacketTick) {
            this.lastPacketTick = var1;
            this.packetsSentThisTick = 1;
            return true;
         } else {
            this.packetsSentThisTick++;
            return this.packetsSentThisTick <= this.maxPacketsPerTick.getValueInt();
         }
      } else {
         return true;
      }
   }

   private void baseSwap(int slot) {
      if (!this.inventory.getValue()) {
         InventoryUtil.switchToSlot(slot);
      } else {
         InventoryUtil.inventorySwap(slot, mc.player.getInventory().selectedSlot);
      }
   }

   private int getBlock() {
      return this.inventory.getValue() ? InventoryUtil.findBlockInventorySlot(Blocks.OBSIDIAN) : InventoryUtil.findBlock(Blocks.OBSIDIAN);
   }

   private int getCrystal() {
      if (this.smartSwitch.getValue() && this.lastCrystalSlot != -1 && InventoryUtil.isItemInSlot(this.lastCrystalSlot, Items.END_CRYSTAL)) {
         return this.lastCrystalSlot;
      } else if (this.autoSwap.getValue() == AutoCrystal.SwapMode.Silent || this.autoSwap.getValue() == AutoCrystal.SwapMode.Normal) {
         return InventoryUtil.findItem(Items.END_CRYSTAL);
      } else {
         return this.autoSwap.getValue() == AutoCrystal.SwapMode.Inventory ? InventoryUtil.findItemInventorySlot(Items.END_CRYSTAL) : -1;
      }
   }

   private void placeCrystal(BlockPos pos) {
      boolean var2 = mc.player.getOffHandStack().getItem() == Items.END_CRYSTAL;
      BlockPos var3 = pos.down();
      Direction var4 = BlockUtil.getClickSide(var3);
      if (var4 != null) {
         this.placePackets.put(var3, System.currentTimeMillis());
         this.placesThisTick++;
         BlockUtil.clickBlock(var3, var4, false, var2 ? Hand.OFF_HAND : Hand.MAIN_HAND, this.swingMode.getValue());
      }
   }

   private void markDead(Entity entity) {
      if (entity != null && this.crystalOptimizer.getValue()) {
         this.deadCrystals.add(entity.getId());
      }
   }

   private boolean isOptimizedDead(Entity entity) {
      return this.crystalOptimizer.getValue() && entity != null && this.deadCrystals.contains(entity.getId());
   }

   private boolean isOptimizedDead(int entityId) {
      return this.crystalOptimizer.getValue() && this.deadCrystals.contains(entityId);
   }

   private void pruneOptimizedCrystalState() {
      long var1 = System.currentTimeMillis();
      Iterator var3 = this.placePackets.entrySet().iterator();

      while (var3.hasNext()) {
         if (var1 - (Long)((Entry)var3.next()).getValue() > 1000L) {
            var3.remove();
         }
      }

      if (!this.deadCrystals.isEmpty() && mc.world != null) {
         this.deadCrystals.removeIf(id -> mc.world.getEntityById(id) == null);
      }
   }

   private boolean faceVector(Vec3d directionVec) {
      if (directionVec == null) {
         return false;
      } else if (!this.shouldYawStep()) {
         CrucifixBeta.ROTATION.lookAt(directionVec);
         return true;
      } else {
         this.directionVec = directionVec;
         return CrucifixBeta.ROTATION.inFov(directionVec, this.fov.getValueFloat()) ? true : !this.checkFov.getValue();
      }
   }

   private EndCrystalEntity resolveCrystal(int entityId) {
      if (entityId != -1 && mc.world != null) {
         Entity var2 = mc.world.getEntityById(entityId);
         return var2 instanceof EndCrystalEntity ? (EndCrystalEntity)var2 : null;
      } else {
         return null;
      }
   }

   private PlayerEntity resolvePlayer(int entityId) {
      if (entityId != -1 && mc.world != null) {
         Entity var2 = mc.world.getEntityById(entityId);
         return var2 instanceof PlayerEntity ? (PlayerEntity)var2 : null;
      } else {
         return null;
      }
   }

   private AsyncCrystalConfig asyncConfig() {
      return new AsyncCrystalConfig(
         this.targetRange.getValue(),
         this.placeRange.getValue(),
         this.breakRange.getValue(),
         this.wallRange.getValue(),
         this.minDamage.getValue(),
         this.maxSelf.getValue(),
         this.reserve.getValue(),
         this.shouldIgnoreItems(),
         ClientSetting.INSTANCE.lowVersion.getValue(),
         this.terrainIgnore.getValue(),
         this.minAge.getValueInt(),
         mc.player.getHealth() + mc.player.getAbsorptionAmount()
      );
   }
@Environment(EnvType.CLIENT)
   private class AsyncCrystalScanner {
      private CompletableFuture<AsyncCrystalResult> future;
      private AsyncCrystalResult latest;

      public void run() {
         if (AutoCrystal.this.calcDelay.passedMs(AutoCrystal.this.updateDelay.getValue())) {
            if (this.future == null || this.future.isDone()) {
               AutoCrystal.this.calcDelay.reset();
               CrystalWorldSnapshot var1 = AutoCrystal.this.latestWorldSnapshot;
               if (var1 != null) {
                  this.future = CompletableFuture.supplyAsync(
                     () -> {
                        BlockPos var2 = AutoCrystal.this.tempBasePos;
                        float var3 = AutoCrystal.this.tempDamage;
                        EndCrystalEntity var4 = AutoCrystal.this.tempBreakCrystal;
                        int var5 = AutoCrystal.this.tempBreakCrystalId;
                        BlockPos var6 = AutoCrystal.this.tempPos;
                        PlayerEntity var7 = AutoCrystal.this.displayTarget;
                        float var8 = AutoCrystal.this.breakDamage;

                        AsyncCrystalResult var11;
                        try {
                           AutoCrystal.this.calcCrystalPos(true, var1);
                           AsyncCrystalResult var9 = new AsyncCrystalResult(
                              AutoCrystal.this.tempBasePos,
                              AutoCrystal.this.tempDamage,
                              AutoCrystal.this.tempBreakCrystalId,
                              AutoCrystal.this.tempPos,
                              AutoCrystal.this.displayTarget == null ? -1 : AutoCrystal.this.displayTarget.getId()
                           );
                           AsyncCrystalResult var17 = AutoCrystal.this.asyncCrystalManager.scan(var1, AutoCrystal.this.asyncConfig());
                           if (!(var17.damage() > var9.damage())) {
                              return var9;
                           }

                           var11 = var17;
                        } catch (Throwable var15) {
                           var15.printStackTrace();
                           return null;
                        } finally {
                           AutoCrystal.this.tempBasePos = var2;
                           AutoCrystal.this.tempDamage = var3;
                           AutoCrystal.this.tempBreakCrystal = var4;
                           AutoCrystal.this.tempBreakCrystalId = var5;
                           AutoCrystal.this.tempPos = var6;
                           AutoCrystal.this.displayTarget = var7;
                           AutoCrystal.this.breakDamage = var8;
                           CombatUtil.modifyPos = null;
                           CombatUtil.modifyBlockState = null;
                           CombatUtil.terrainIgnore = false;
                           AutoCrystal.this.calcSnapshot.remove();
                        }

                        return var11;
                     },
                     ThreadManager.EXECUTOR
                  );
               }
            }
         }
      }

      public AsyncCrystalResult poll() {
         if (this.future != null && this.future.isDone()) {
            try {
               this.latest = this.future.get();
            } catch (Exception var2) {
               var2.printStackTrace();
               this.latest = null;
            }

            this.future = null;
         }

         return this.latest;
      }
   }

   @Environment(EnvType.CLIENT)
   private class CrystalRender {
      @EventListener
      public void onRender3D(Render3DEvent event) {
         BlockPos var2 = AutoCrystal.this.sync.getValue() && AutoCrystal.this.crystalPos != null ? AutoCrystal.this.syncPos : AutoCrystal.this.crystalPos;
         if (var2 != null) {
            AutoCrystal.this.placeVec3d = var2.down().toCenterPos();
         }

         if (AutoCrystal.this.placeVec3d != null) {
            AutoCrystal.this.currentFade = AutoCrystal.this.fadeSpeed.getValue() >= 1.0
               ? (AutoCrystal.this.noPosTimer.passed((long)(AutoCrystal.this.startFadeTime.getValue() * 1000.0)) ? 0.0 : 0.5)
               : AnimateUtil.animate(
                  AutoCrystal.this.currentFade,
                  AutoCrystal.this.noPosTimer.passed((long)(AutoCrystal.this.startFadeTime.getValue() * 1000.0)) ? 0.0 : 0.5,
                  AutoCrystal.this.fadeSpeed.getValue() / 10.0
               );
            if (AutoCrystal.this.currentFade == 0.0) {
               AutoCrystal.this.curVec3d = null;
            } else {
               AutoCrystal.this.curVec3d = AutoCrystal.this.curVec3d != null && !(AutoCrystal.this.sliderSpeed.getValue() >= 1.0)
                  ? new Vec3d(
                     AnimateUtil.animate(AutoCrystal.this.curVec3d.x, AutoCrystal.this.placeVec3d.x, AutoCrystal.this.sliderSpeed.getValue() / 10.0),
                     AnimateUtil.animate(AutoCrystal.this.curVec3d.y, AutoCrystal.this.placeVec3d.y, AutoCrystal.this.sliderSpeed.getValue() / 10.0),
                     AnimateUtil.animate(AutoCrystal.this.curVec3d.z, AutoCrystal.this.placeVec3d.z, AutoCrystal.this.sliderSpeed.getValue() / 10.0)
                  )
                  : AutoCrystal.this.placeVec3d;
               if (AutoCrystal.this.render.getValue()) {
                  Box var4 = new Box(AutoCrystal.this.curVec3d, AutoCrystal.this.curVec3d);
                  var4 = AutoCrystal.this.shrink.getValue() ? var4.expand(AutoCrystal.this.currentFade) : var4.expand(0.5);
                  MatrixStack var5 = event.matrixStack;
                  if (AutoCrystal.this.fill.booleanValue) {
                     Render3DUtil.drawFill(
                        var5,
                        var4,
                        ColorUtil.injectAlpha(
                           AutoCrystal.this.fill.getValue(), (int)((double)AutoCrystal.this.fill.getValue().getAlpha() * AutoCrystal.this.currentFade * 2.0)
                        )
                     );
                  }

                  if (AutoCrystal.this.box.booleanValue) {
                     Render3DUtil.drawBox(
                        var5,
                        var4,
                        ColorUtil.injectAlpha(
                           AutoCrystal.this.box.getValue(), (int)((double)AutoCrystal.this.box.getValue().getAlpha() * AutoCrystal.this.currentFade * 2.0)
                        ),
                        AutoCrystal.this.lineWidth.getValueFloat()
                     );
                  }
               }

               if (AutoCrystal.this.text.booleanValue
                  && AutoCrystal.this.lastDamage > 0.0F
                  && !AutoCrystal.this.noPosTimer.passed((long)(AutoCrystal.this.startFadeTime.getValue() * 1000.0))) {
                  Render3DUtil.drawText3D(
                     AutoCrystal.this.df.format((double)AutoCrystal.this.lastDamage), AutoCrystal.this.curVec3d, AutoCrystal.this.text.getValue()
                  );
               }
            }
         }
      }
   }

   @Environment(EnvType.CLIENT)
   private static enum Page {
      General,
      Base,
      Misc,
      Cev,
      Rotation,
      Check,
      Calc,
      Render;

}

   @Environment(EnvType.CLIENT)
   private static enum SwapMode {
      None,
      Normal,
      Silent,
      Inventory;

}

   @Environment(EnvType.CLIENT)
   public static enum TargetESP {
      Box,
      Fill,
      Jello,
      ThunderHack,
      None;

}
}
