feat: GRENADE parabolic nade (35/5.5, 25TP, 15cd) + DIVE sky strike (25dmg knockback, 87.5TP, 120cd)

This commit is contained in:
SashegDev
2026-09-17 08:41:14 +00:00
parent 39ffe477a5
commit ee4eb7f897
6 changed files with 482 additions and 3 deletions
@@ -25,7 +25,7 @@ public class BlackKnifeListener implements Listener {
private final java.util.Map<java.util.UUID, BukkitRunnable> previewTasks = new java.util.HashMap<>();
private final java.util.Map<java.util.UUID, BukkitRunnable> lungeHighlightTasks = new java.util.HashMap<>();
private final java.util.Map<java.util.UUID, Integer> swordCombo = new java.util.HashMap<>();
private static final String[] MODES = {"STARS", "SWORD", "DIAMOND", "DIAMOND_TRI", "BARRAGE", "LUNGE", "SNIPE", "TRI", "OVERHEAD"};
private static final String[] MODES = {"STARS", "SWORD", "DIAMOND", "DIAMOND_TRI", "BARRAGE", "LUNGE", "SNIPE", "TRI", "OVERHEAD", "GRENADE", "DIVE"};
public BlackKnifeListener(BlackKnifePlugin plugin) { this.plugin = plugin; }
@@ -155,6 +155,15 @@ public class BlackKnifeListener implements Listener {
if (LungeAttack.tryOverhead(p, tm, gaze)) {
p.setCooldown(item.getType(), 50);
}
} else if (m.equals("GRENADE")) {
if (GrenadeAttack.tryUse(p, tm)) {
p.setCooldown(item.getType(), 15);
}
} else if (m.equals("DIVE")) {
if (SkyDiveAttack.tryUse(p, tm)) {
p.setCooldown(item.getType(), 120);
clearHighlight(p);
}
}
e.setCancelled(true);
}
@@ -269,6 +278,44 @@ public class BlackKnifeListener implements Listener {
};
r.runTaskTimer(plugin, 1L, 4L);
lungeHighlightTasks.put(p.getUniqueId(), r);
} else if (m.equals("GRENADE")) {
BukkitRunnable r = new BukkitRunnable() {
@Override public void run() {
if (!p.isOnline() || !getMode(p).equals("GRENADE")) { cancel(); return; }
if (p.hasCooldown(Material.NETHERITE_SWORD)) return;
if (!BlackKnifeItem.is(p.getInventory().getItemInMainHand()) && !BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return;
Location gp = getGazePoint(p);
for (int i = 0; i < 16; i++) {
double a = (double) i / 16 * Math.PI * 2;
Location ring = gp.clone().add(Math.cos(a) * 1.5, 0.1, Math.sin(a) * 1.5);
p.spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0, new Particle.DustOptions(Color.fromRGB(255, 40, 40), 1.2f));
}
for (int i = 0; i < 24; i++) {
double a = (double) i / 24 * Math.PI * 2;
Location ring = gp.clone().add(Math.cos(a) * 3.0, 0.1, Math.sin(a) * 3.0);
p.spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0, new Particle.DustOptions(Color.fromRGB(255, 200, 60), 1.0f));
}
}
};
r.runTaskTimer(plugin, 1L, 4L);
lungeHighlightTasks.put(p.getUniqueId(), r);
} else if (m.equals("DIVE")) {
BukkitRunnable r = new BukkitRunnable() {
@Override public void run() {
if (!p.isOnline() || !getMode(p).equals("DIVE")) { cancel(); return; }
if (p.hasCooldown(Material.NETHERITE_SWORD)) return;
if (!BlackKnifeItem.is(p.getInventory().getItemInMainHand()) && !BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return;
Location gp = getGazePoint(p);
for (int i = 0; i < 16; i++) {
double a = (double) i / 16 * Math.PI * 2;
Location ring = gp.clone().add(Math.cos(a) * 1.2, 0.1, Math.sin(a) * 1.2);
p.spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0, new Particle.DustOptions(Color.fromRGB(80, 160, 255), 1.2f));
}
p.spawnParticle(Particle.CLOUD, gp.clone().add(0, 0.2, 0), 1, 0.1, 0.1, 0.1, 0);
}
};
r.runTaskTimer(plugin, 1L, 4L);
lungeHighlightTasks.put(p.getUniqueId(), r);
} else if (m.equals("DIAMOND_TRI")) {
BukkitRunnable r = new BukkitRunnable() {
@Override public void run() {
@@ -31,6 +31,7 @@ public class BlackKnifePlugin extends JavaPlugin {
getServer().getPluginManager().registerEvents(knifeListener, this);
getServer().getPluginManager().registerEvents(swoonHandler, this);
getServer().getPluginManager().registerEvents(new AttackTpListener(tension), this);
getServer().getPluginManager().registerEvents(new SkyDiveAttack(), this);
getServer().getPluginManager().registerEvents(new ParryHandler(this), this);
getServer().getPluginManager().registerEvents(magicListener, this);
getServer().getPluginManager().registerEvents(chatHandler, this);
@@ -25,7 +25,7 @@ public class EntityTracker {
boolean isBk = e.getPersistentDataContainer().has(BK_MARK, PersistentDataType.BYTE) || tracked.contains(e.getUniqueId());
if (!isBk && e instanceof org.bukkit.entity.BlockDisplay bd) {
var mat = bd.getBlock().getMaterial();
if (mat == org.bukkit.Material.DEEPSLATE_TILES || mat == org.bukkit.Material.SCULK_CATALYST || mat == org.bukkit.Material.AMETHYST_BLOCK || mat == org.bukkit.Material.MEDIUM_AMETHYST_BUD) isBk = true;
if (mat == org.bukkit.Material.DEEPSLATE_TILES || mat == org.bukkit.Material.SCULK_CATALYST || mat == org.bukkit.Material.AMETHYST_BLOCK || mat == org.bukkit.Material.MEDIUM_AMETHYST_BUD || mat == org.bukkit.Material.TNT) isBk = true;
}
if (isBk) { untrack(e); e.remove(); count++; }
}
@@ -0,0 +1,211 @@
package me.sashegdev.blackknife;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.Display;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.ArrayDeque;
import java.util.Deque;
public class GrenadeAttack {
private static final float COST = 25f;
private static final double LETHAL_RADIUS = 1.5;
private static final double CHIP_RADIUS = 3.0;
private static final double LETHAL_DAMAGE = 35.0;
private static final double CHIP_DAMAGE = 5.5;
private static final double MAX_RANGE = 30.0;
private static final double ARC_MIN = 1.5;
private static final double ARC_PER_BLOCK = 0.25;
private static final double ARC_MAX = 4.0;
public static boolean tryUse(Player p, TensionManager tension) {
if (SwoonHandler.isSwooned(p)) return false;
if (tension.getTP(p) < COST) {
p.sendActionBar(Component.text("Need 10% TP").color(NamedTextColor.RED));
return false;
}
tension.setTP(p, tension.getTP(p) - COST);
Location eye = p.getEyeLocation();
Vector dir = eye.getDirection().normalize();
Location target = computeTarget(p, eye, dir);
double dist = eye.distance(target);
int duration = (int) Math.max(10, Math.min(40, dist * 3));
double arcH = Math.max(ARC_MIN, Math.min(ARC_MAX, dist * ARC_PER_BLOCK));
Location spawn = eye.clone().add(dir.clone().multiply(1)).add(0, 0.2, 0);
Vector throwDir = target.clone().subtract(spawn).toVector().normalize();
BlockDisplay nade = p.getWorld().spawn(spawn, BlockDisplay.class, d -> {
d.setBlock(Material.TNT.createBlockData());
d.setBrightness(new Display.Brightness(15, 15));
d.setViewRange(64);
d.setTransformation(new org.bukkit.util.Transformation(
new org.joml.Vector3f(-0.25f, -0.25f, -0.25f),
new org.joml.AxisAngle4f(),
new org.joml.Vector3f(0.5f, 0.5f, 0.5f),
new org.joml.AxisAngle4f()));
});
EntityTracker.mark(nade);
p.getWorld().playSound(spawn, Sound.ENTITY_SNOWBALL_THROW, 1f, 0.7f);
BlackKnifePlugin plugin = BlackKnifePlugin.getPlugin(BlackKnifePlugin.class);
new BukkitRunnable() {
Location flightStart = spawn.clone();
Location flightTarget = target.clone();
Vector flightDir = throwDir.clone();
int flightDuration = duration;
int elapsed = 0;
int tick = 0;
double arc = arcH;
boolean targetWasSolid = flightTarget.getBlock().isSolid();
final Deque<Location> trail = new ArrayDeque<>();
@Override public void run() {
if (!p.isOnline() || p.isDead() || !nade.isValid()) {
EntityTracker.untrack(nade);
if (nade.isValid()) nade.remove();
cancel();
return;
}
tick++;
elapsed++;
if (tick % 3 == 0 && needsRetarget(nade.getLocation())) {
retarget(nade.getLocation());
}
double t = Math.min(1.0, (double) elapsed / flightDuration);
Location base = flightStart.clone().add(flightTarget.clone().subtract(flightStart).toVector().multiply(t));
base.add(0, Math.sin(Math.PI * t) * arc, 0);
nade.teleport(base);
base.getWorld().spawnParticle(Particle.SMOKE, base, 1, 0.05, 0.05, 0.05, 0);
trail.addLast(base.clone());
while (trail.size() > 12) trail.pollFirst();
renderTrail();
if (tick % 2 == 0) renderRings(flightTarget);
boolean landed = t >= 1.0 || base.getBlock().isSolid();
if (landed || nade.getTicksLived() > 100) {
Location expl = landed ? flightTarget.clone() : base.clone();
EntityTracker.untrack(nade);
nade.remove();
cancel();
explode(p, expl, plugin);
}
}
private boolean needsRetarget(Location cur) {
boolean nowSolid = flightTarget.getBlock().isSolid();
if (nowSolid != targetWasSolid) return true;
Vector toTarget = flightTarget.clone().subtract(cur).toVector();
double remaining = toTarget.length();
if (remaining < 0.5) return false;
var ray = cur.getWorld().rayTraceBlocks(cur, toTarget.normalize(), remaining);
if (ray != null && ray.getHitBlock() != null) {
double hitDist = ray.getHitBlock().getLocation().distance(cur);
return hitDist + 0.5 < remaining;
}
return false;
}
private void retarget(Location cur) {
var ray = cur.getWorld().rayTraceBlocks(cur, flightDir, MAX_RANGE);
Location nt;
if (ray != null && ray.getHitPosition() != null) {
nt = new Location(cur.getWorld(), ray.getHitPosition().getX(), ray.getHitPosition().getY(), ray.getHitPosition().getZ());
if (ray.getHitBlockFace() != null) nt.add(0, 0.1, 0);
} else {
nt = cur.clone().add(flightDir.clone().multiply(12));
nt.setY(cur.getWorld().getHighestBlockYAt(nt) + 1);
}
flightStart = cur.clone();
flightTarget = nt.clone();
double remaining = Math.max(1.0, cur.distance(nt));
flightDuration = (int) Math.max(6, Math.min(40, remaining * 3));
elapsed = 0;
arc = Math.max(ARC_MIN, Math.min(ARC_MAX, remaining * ARC_PER_BLOCK));
targetWasSolid = flightTarget.getBlock().isSolid();
}
private void renderTrail() {
int size = trail.size();
int i = 0;
for (Location tp : trail) {
float frac = size <= 1 ? 1f : (float) i / (size - 1);
float dustSize = 0.3f + frac * 1.1f;
p.spawnParticle(Particle.DUST, tp, 1, 0.02, 0.02, 0.02, 0,
new Particle.DustOptions(Color.fromRGB(180, 120, 40), dustSize));
i++;
}
}
private void renderRings(Location ground) {
Location g = ground.clone().add(0, 0.15, 0);
for (int k = 0; k < 16; k++) {
double a = (double) k / 16 * Math.PI * 2;
Location ring = g.clone().add(Math.cos(a) * LETHAL_RADIUS, 0, Math.sin(a) * LETHAL_RADIUS);
p.spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0,
new Particle.DustOptions(Color.fromRGB(255, 40, 40), 1.2f));
}
for (int k = 0; k < 24; k++) {
double a = (double) k / 24 * Math.PI * 2;
Location ring = g.clone().add(Math.cos(a) * CHIP_RADIUS, 0, Math.sin(a) * CHIP_RADIUS);
p.spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0,
new Particle.DustOptions(Color.fromRGB(255, 200, 60), 1.0f));
}
}
}.runTaskTimer(plugin, 1L, 1L);
return true;
}
private static Location computeTarget(Player p, Location eye, Vector dir) {
var ray = p.getWorld().rayTraceBlocks(eye, dir, MAX_RANGE);
if (ray != null && ray.getHitPosition() != null) {
Location loc = new Location(p.getWorld(), ray.getHitPosition().getX(), ray.getHitPosition().getY(), ray.getHitPosition().getZ());
if (ray.getHitBlockFace() != null) loc.add(0, 0.1, 0);
return loc;
}
Location fallback = eye.clone().add(dir.clone().multiply(20));
fallback.setY(p.getWorld().getHighestBlockYAt(fallback) + 1);
return fallback;
}
private static void explode(Player p, Location expl, BlackKnifePlugin plugin) {
expl.getWorld().spawnParticle(Particle.EXPLOSION, expl, 2);
expl.getWorld().spawnParticle(Particle.SMOKE, expl, 12, 0.3, 0.3, 0.3, 0.05);
expl.getWorld().spawnParticle(Particle.DUST, expl, 10, 0.3, 0.2, 0.3, 0,
new Particle.DustOptions(Color.fromRGB(255, 120, 30), 1.2f));
expl.getWorld().playSound(expl, Sound.ENTITY_GENERIC_EXPLODE, 1f, 1.1f);
TensionManager tm = plugin.getTension();
for (LivingEntity le : expl.getWorld().getNearbyLivingEntities(expl, CHIP_RADIUS + 0.5)) {
if (le == p) continue;
if (!DamageUtil.canHurt(p, le)) continue;
double d = le.getLocation().add(0, 1, 0).distance(expl);
if (d > CHIP_RADIUS) continue;
if (d <= LETHAL_RADIUS) {
DamageUtil.apply(p, le, LETHAL_DAMAGE);
tm.startBattle(p, le);
} else {
DamageUtil.apply(p, le, CHIP_DAMAGE);
tm.startBattle(p, le);
}
le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0, 1, 0), 8, 0.3, 0.3, 0.3, 0.1);
}
for (Player pl : expl.getWorld().getNearbyPlayers(expl, 0.9)) {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(expl);
if (d2 > 0.25 && d2 < 0.81) tm.tryGiveDodge(pl);
}
}
}
@@ -65,7 +65,7 @@ public class KillMessageHandler implements Listener {
private boolean isKnifeMagic(String magic) {
if (magic == null) return false;
String m = magic.toLowerCase();
return m.contains("swoon") || m.contains("sword") || m.contains("diamond") || m.contains("lunge") || m.contains("snipe") || m.contains("tri") || m.contains("overhead") || m.contains("stars") || m.contains("black knife") || m.contains("knife");
return m.contains("swoon") || m.contains("sword") || m.contains("diamond") || m.contains("lunge") || m.contains("snipe") || m.contains("tri") || m.contains("overhead") || m.contains("stars") || m.contains("black knife") || m.contains("knife") || m.contains("grenade") || m.contains("dive");
}
private String obf(String name) {
@@ -0,0 +1,220 @@
package me.sashegdev.blackknife;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Color;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SkyDiveAttack implements Listener {
private static final float COST = 87.5f;
private static final double DAMAGE = 25.0;
private static final double LAUNCH_UP = 1.3;
private static final double LAUNCH_FORWARD = 0.4;
private static final double DASH_SPEED = 2.8;
private static final long FREEZE_TICKS = 30L;
private static final long FALL_IMMUNE_MS = 8000L;
private static final Map<UUID, Location> frozen = new ConcurrentHashMap<>();
private static final Map<UUID, Long> immuneUntil = new ConcurrentHashMap<>();
private static final Map<UUID, Boolean> diving = new ConcurrentHashMap<>();
public static boolean tryUse(Player p, TensionManager tension) {
if (SwoonHandler.isSwooned(p)) return false;
if (p.getGameMode() == GameMode.CREATIVE || p.getGameMode() == GameMode.SPECTATOR) return false;
if (Boolean.TRUE.equals(diving.get(p.getUniqueId()))) {
p.sendActionBar(Component.text("Already diving!").color(NamedTextColor.RED));
return false;
}
if (tension.getTP(p) < COST) {
p.sendActionBar(Component.text("Need 35% TP").color(NamedTextColor.RED));
return false;
}
tension.setTP(p, tension.getTP(p) - COST);
BlackKnifePlugin plugin = BlackKnifePlugin.getPlugin(BlackKnifePlugin.class);
Vector look = p.getEyeLocation().getDirection();
Vector horizontal = new Vector(look.getX(), 0, look.getZ());
if (horizontal.lengthSquared() > 0.0001) horizontal.normalize().multiply(LAUNCH_FORWARD);
else horizontal = new Vector(0, 0, 0);
p.setVelocity(new Vector(horizontal.getX(), LAUNCH_UP, horizontal.getZ()));
p.setFallDistance(0);
immuneUntil.put(p.getUniqueId(), System.currentTimeMillis() + FALL_IMMUNE_MS);
diving.put(p.getUniqueId(), true);
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1f, 0.8f);
p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation(), 10, 0.3, 0.2, 0.3, 0.05);
new BukkitRunnable() {
final double startY = p.getLocation().getY();
int ticks = 0;
@Override public void run() {
if (!p.isOnline() || p.isDead()) { cleanup(p); cancel(); return; }
p.setFallDistance(0);
ticks++;
double gained = p.getLocation().getY() - startY;
Vector v = p.getVelocity();
if (v.getY() <= 0.25 || gained >= 5.5 || ticks >= 14) {
cancel();
freeze(p, plugin);
}
if (ticks > 40) { cancel(); freeze(p, plugin); }
}
}.runTaskTimer(plugin, 1L, 1L);
return true;
}
private static void freeze(Player p, BlackKnifePlugin plugin) {
if (!p.isOnline() || p.isDead()) { cleanup(p); return; }
Location top = p.getLocation().clone();
p.setVelocity(new Vector(0, 0, 0));
p.setFallDistance(0);
frozen.put(p.getUniqueId(), top);
p.getWorld().playSound(top, Sound.BLOCK_BEACON_ACTIVATE, 0.8f, 1.4f);
new BukkitRunnable() {
long left = FREEZE_TICKS;
@Override public void run() {
if (!p.isOnline() || p.isDead()) { cleanup(p); cancel(); return; }
Location anchor = frozen.get(p.getUniqueId());
if (anchor == null) { cancel(); return; }
Location cur = p.getLocation();
anchor.setYaw(cur.getYaw());
anchor.setPitch(cur.getPitch());
p.teleport(anchor);
p.setVelocity(new Vector(0, 0, 0));
p.setFallDistance(0);
for (int k = 0; k < 10; k++) {
double a = (double) k / 10 * Math.PI * 2;
Location ring = anchor.clone().add(Math.cos(a) * 1.2, 0.1, Math.sin(a) * 1.2);
p.getWorld().spawnParticle(Particle.DUST, ring, 1, 0, 0, 0, 0,
new Particle.DustOptions(Color.fromRGB(80, 160, 255), 1.0f));
}
left--;
if (left <= 0) {
frozen.remove(p.getUniqueId());
cancel();
dash(p, plugin);
}
}
}.runTaskTimer(plugin, 1L, 1L);
}
private static void dash(Player p, BlackKnifePlugin plugin) {
if (!p.isOnline() || p.isDead()) { cleanup(p); return; }
Vector dir = p.getEyeLocation().getDirection().normalize();
p.setVelocity(dir.clone().multiply(DASH_SPEED));
p.setFallDistance(0);
immuneUntil.put(p.getUniqueId(), System.currentTimeMillis() + FALL_IMMUNE_MS);
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1f, 1.2f);
new BukkitRunnable() {
int ticks = 0;
boolean hit = false;
@Override public void run() {
if (!p.isOnline() || p.isDead()) { cleanup(p); cancel(); return; }
p.setFallDistance(0);
ticks++;
Location cur = p.getLocation().add(0, 1, 0);
cur.getWorld().spawnParticle(Particle.DUST, cur, 2, 0.15, 0.15, 0.15, 0,
new Particle.DustOptions(Color.fromRGB(80, 160, 255), 1.4f));
cur.getWorld().spawnParticle(Particle.CLOUD, cur, 1, 0.1, 0.1, 0.1, 0.02);
for (LivingEntity le : cur.getWorld().getNearbyLivingEntities(cur, 1.2)) {
if (le == p) continue;
if (!DamageUtil.canHurt(p, le)) continue;
DamageUtil.apply(p, le, DAMAGE);
Vector kb = dir.clone().multiply(1.2);
kb.setY(kb.getY() + 0.4);
le.setVelocity(kb);
le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0, 1, 0), 12, 0.4, 0.4, 0.4, 0.1);
le.getWorld().playSound(le.getLocation(), Sound.ENTITY_PLAYER_ATTACK_CRIT, 1f, 0.8f);
plugin.getTension().startBattle(p, le);
hit = true;
break;
}
if (hit) { finish(); return; }
Location ahead = p.getEyeLocation().add(dir.clone().multiply(1.0));
if (ahead.getBlock().isSolid()) { finish(); return; }
if (ticks > 40) { finish(); return; }
}
private void finish() {
immuneUntil.put(p.getUniqueId(), System.currentTimeMillis() + 3000L);
cleanup(p);
cancel();
}
}.runTaskTimer(plugin, 1L, 1L);
}
private static void cleanup(Player p) {
frozen.remove(p.getUniqueId());
diving.remove(p.getUniqueId());
}
public static boolean isImmune(Player p) {
Long until = immuneUntil.get(p.getUniqueId());
return until != null && System.currentTimeMillis() < until;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onFall(EntityDamageEvent e) {
if (e.getCause() != EntityDamageEvent.DamageCause.FALL) return;
if (!(e.getEntity() instanceof Player p)) return;
if (isImmune(p) || frozen.containsKey(p.getUniqueId()) || Boolean.TRUE.equals(diving.get(p.getUniqueId()))) {
e.setCancelled(true);
p.setFallDistance(0);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onMove(PlayerMoveEvent e) {
Player p = e.getPlayer();
Location anchor = frozen.get(p.getUniqueId());
if (anchor == null) return;
if (e.getTo() == null) return;
double dx = e.getTo().getX() - anchor.getX();
double dy = e.getTo().getY() - anchor.getY();
double dz = e.getTo().getZ() - anchor.getZ();
if (Math.abs(dx) > 0.05 || Math.abs(dy) > 0.05 || Math.abs(dz) > 0.05) {
Location fixed = anchor.clone();
fixed.setYaw(e.getTo().getYaw());
fixed.setPitch(e.getTo().getPitch());
e.setTo(fixed);
p.sendActionBar(Component.text("Aiming...").color(NamedTextColor.AQUA));
}
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
frozen.remove(e.getPlayer().getUniqueId());
diving.remove(e.getPlayer().getUniqueId());
immuneUntil.remove(e.getPlayer().getUniqueId());
}
@EventHandler
public void onDeath(PlayerDeathEvent e) {
frozen.remove(e.getEntity().getUniqueId());
diving.remove(e.getEntity().getUniqueId());
immuneUntil.remove(e.getEntity().getUniqueId());
}
}