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
@@ -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());
}
}