fix multiplayer: remove KNOCKED, swoon softlock, combat checks, dodge TP gate, inv cache, killfeed, threads/leaks

This commit is contained in:
SashegDev
2026-09-14 05:07:09 +00:00
parent e227ad63bd
commit 39ffe477a5
17 changed files with 419 additions and 230 deletions
@@ -17,12 +17,16 @@ public class AttackTpListener implements Listener {
return null;
}
@EventHandler
@EventHandler(ignoreCancelled = true)
public void onAttack(EntityDamageByEntityEvent e) {
Player p = getAttacker(e.getDamager());
if (p == null) return;
if (p.getGameMode() == org.bukkit.GameMode.SPECTATOR) return;
if (!(e.getEntity() instanceof LivingEntity target)) return;
if (e.isCancelled()) return;
if (target instanceof Player tp) {
if (tp.getGameMode() == org.bukkit.GameMode.CREATIVE || tp.getGameMode() == org.bukkit.GameMode.SPECTATOR) return;
if (tp.isInvulnerable()) return;
}
tension.startBattle(p, target);
boolean crit = false;
if (e.getDamager() instanceof org.bukkit.entity.AbstractArrow arrow) crit = arrow.isCritical();
@@ -46,7 +50,7 @@ public class AttackTpListener implements Listener {
}
public void onDodge(Player p) {
if (tension.isInBattle(p)) tension.addTP(p, 2.5f, "DODGE +1%");
tension.tryGiveDodge(p);
p.getWorld().spawnParticle(org.bukkit.Particle.DUST, p.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new org.bukkit.Particle.DustOptions(org.bukkit.Color.fromRGB(255, 255, 255), 0.15f));
}
}
@@ -29,7 +29,14 @@ public class BlackKnifeListener implements Listener {
public BlackKnifeListener(BlackKnifePlugin plugin) { this.plugin = plugin; }
private String getMode(Player p) { return mode.getOrDefault(p.getUniqueId(), "STARS"); }
public String getMode(Player p) { return mode.getOrDefault(p.getUniqueId(), "STARS"); }
public void cleanupPlayer(Player p) {
mode.remove(p.getUniqueId());
swordCombo.remove(p.getUniqueId());
clearPreview(p);
clearHighlight(p);
}
public Location getGazePoint(Player p) {
var ray = p.getWorld().rayTraceBlocks(p.getEyeLocation(), p.getEyeLocation().getDirection(), 120);
@@ -153,6 +160,16 @@ public class BlackKnifeListener implements Listener {
}
}
@EventHandler
public void onQuit(org.bukkit.event.player.PlayerQuitEvent e) {
cleanupPlayer(e.getPlayer());
}
@EventHandler
public void onDeath(org.bukkit.event.entity.PlayerDeathEvent e) {
swordCombo.remove(e.getEntity().getUniqueId());
}
private void clearPreview(Player p) {
var task = previewTasks.remove(p.getUniqueId());
if (task != null) task.cancel();
@@ -169,7 +186,8 @@ public class BlackKnifeListener implements Listener {
if (m.equals("SWORD")) {
BukkitRunnable r = new BukkitRunnable() {
@Override public void run() {
if (!p.isOnline() || !getMode(p).equals("SWORD") || p.hasCooldown(Material.NETHERITE_SWORD)) return;
if (!p.isOnline() || !getMode(p).equals("SWORD")) { previewTasks.remove(p.getUniqueId()); cancel(); return; }
if (p.hasCooldown(Material.NETHERITE_SWORD)) return;
if (!BlackKnifeItem.is(p.getInventory().getItemInMainHand()) && !BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return;
Vector dir = p.getEyeLocation().getDirection().normalize();
Vector right = dir.clone().crossProduct(new Vector(0, 1, 0));
@@ -290,6 +308,7 @@ public class BlackKnifeListener implements Listener {
d.setBrightness(new Display.Brightness(15, 15));
d.setViewRange(64);
});
EntityTracker.mark(disp);
Vector vel = new Vector(0, -0.35 - Math.random() * 0.1, 0);
vel.add(new Vector((Math.random() - 0.5) * 0.08, 0, (Math.random() - 0.5) * 0.08));
new BukkitRunnable() {
@@ -301,6 +320,7 @@ public class BlackKnifeListener implements Listener {
if (next.getBlock().isSolid() || nl.getY() <= p.getWorld().getHighestBlockYAt(nl) + 0.5) {
Location expl = cur.clone().add(vel.clone().normalize().multiply(-0.1));
disp.teleport(expl);
EntityTracker.untrack(disp);
disp.remove();
cancel();
p.getWorld().spawnParticle(Particle.EXPLOSION, expl, 1);
@@ -313,6 +333,7 @@ public class BlackKnifeListener implements Listener {
d.setBrightness(new Display.Brightness(15, 15));
d.setViewRange(64);
});
EntityTracker.mark(shard);
new BukkitRunnable() {
@Override public void run() {
if (!shard.isValid()) { cancel(); return; }
@@ -321,6 +342,7 @@ public class BlackKnifeListener implements Listener {
if (n2.getBlock().isSolid()) {
Location expl2 = cur2.clone().add(sv.clone().normalize().multiply(-0.1));
shard.teleport(expl2);
EntityTracker.untrack(shard);
shard.remove();
cancel();
return;
@@ -331,6 +353,7 @@ public class BlackKnifeListener implements Listener {
if (ent instanceof Player pl && pl != p) continue;
if (ent instanceof org.bukkit.entity.LivingEntity le && le != p) {
DamageUtil.apply(p, le, 3.0);
EntityTracker.untrack(shard);
shard.remove();
cancel();
break;
@@ -340,10 +363,11 @@ public class BlackKnifeListener implements Listener {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(n2);
if (d2 > 0.25 && d2 < 0.81) {
plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
plugin.getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
EntityTracker.untrack(shard);
if (shard.getTicksLived() > 40) { shard.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -356,10 +380,11 @@ public class BlackKnifeListener implements Listener {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(nl);
if (d2 > 0.25 && d2 < 0.81) {
plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
plugin.getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
EntityTracker.untrack(disp);
if (disp.getTicksLived() > 120) { disp.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -10,6 +10,8 @@ public class BlackKnifePlugin extends JavaPlugin {
private ChatConfig chatConfig;
private ChatHandler chatHandler;
private AwakeningManager awakeningManager;
private BlackKnifeListener knifeListener;
private SwoonHandler swoonHandler;
private String lang = "ru";
@Override
@@ -24,10 +26,11 @@ public class BlackKnifePlugin extends JavaPlugin {
chatHandler = new ChatHandler(this, chatConfig);
awakeningManager = new AwakeningManager(this, magicManager);
getServer().getPluginManager().registerEvents(tension, this);
getServer().getPluginManager().registerEvents(new BlackKnifeListener(this), this);
getServer().getPluginManager().registerEvents(new SwoonHandler(this), this);
knifeListener = new BlackKnifeListener(this);
swoonHandler = new SwoonHandler(this);
getServer().getPluginManager().registerEvents(knifeListener, this);
getServer().getPluginManager().registerEvents(swoonHandler, this);
getServer().getPluginManager().registerEvents(new AttackTpListener(tension), this);
getServer().getPluginManager().registerEvents(new KnockedHandler(this), this);
getServer().getPluginManager().registerEvents(new ParryHandler(this), this);
getServer().getPluginManager().registerEvents(magicListener, this);
getServer().getPluginManager().registerEvents(chatHandler, this);
@@ -36,6 +39,7 @@ public class BlackKnifePlugin extends JavaPlugin {
getCommand("blackknife").setExecutor(new BlackKnifeCommand(this, chatConfig, chatHandler));
getCommand("magic").setExecutor(new MagicCommand(magicManager, this));
getServer().getScheduler().runTaskTimer(this, () -> tension.tickShield(), 20L, 1L);
cleanupLegacyKnocked();
getLogger().info("BlackKnife 1.0.0 enabled - Paper 1.21.1 lang=" + lang);
}
@@ -43,9 +47,30 @@ public class BlackKnifePlugin extends JavaPlugin {
public TensionManager getTension() { return tension; }
public MagicManager getMagicManager() { return magicManager; }
public MagicListener getMagicListener() { return magicListener; }
public BlackKnifeListener getKnifeListener() { return knifeListener; }
public SwoonHandler getSwoonHandler() { return swoonHandler; }
public String getLang() { return lang; }
public void reloadLang() { lang = getConfig().getString("lang", "ru"); if (awakeningManager != null) awakeningManager.reloadChances(); }
private void cleanupLegacyKnocked() {
try {
var knocked = new org.bukkit.NamespacedKey("blackknife", "knocked");
var vhp = new org.bukkit.NamespacedKey("blackknife", "vhp");
for (var p : getServer().getOnlinePlayers()) {
var pdc = p.getPersistentDataContainer();
if (pdc.has(knocked, org.bukkit.persistence.PersistentDataType.BYTE)) {
pdc.remove(knocked);
pdc.remove(vhp);
try { p.setWalkSpeed(0.2f); } catch (Exception ignored) {}
p.removePotionEffect(org.bukkit.potion.PotionEffectType.WEAKNESS);
p.removePotionEffect(org.bukkit.potion.PotionEffectType.SLOWNESS);
}
}
} catch (Exception e) {
getLogger().warning("Legacy knocked cleanup failed: " + e.getMessage());
}
}
@Override
public void onDisable() {
getServer().getScheduler().cancelTasks(this);
@@ -5,7 +5,19 @@ import org.bukkit.entity.Player;
import org.bukkit.persistence.PersistentDataType;
public class DamageUtil {
public static boolean canHurt(Player attacker, LivingEntity victim) {
if (victim == null || !victim.isValid() || victim.isDead()) return false;
if (attacker != null && attacker.getGameMode() == org.bukkit.GameMode.SPECTATOR) return false;
if (victim instanceof Player pl) {
if (pl.getGameMode() == org.bukkit.GameMode.CREATIVE || pl.getGameMode() == org.bukkit.GameMode.SPECTATOR) return false;
if (pl.isInvulnerable()) return false;
if (pl.getPersistentDataContainer().has(SwoonHandler.SWOONED, PersistentDataType.BYTE)) return false;
}
return true;
}
public static void apply(Player attacker, LivingEntity victim, double amount) {
if (!canHurt(attacker, victim)) return;
victim.getPersistentDataContainer().set(SwoonHandler.MAGIC_DAMAGE, PersistentDataType.BYTE, (byte) 1);
victim.damage(amount, attacker);
victim.getPersistentDataContainer().remove(SwoonHandler.MAGIC_DAMAGE);
@@ -25,6 +25,7 @@ public class DiamondAttack {
d.setBrightness(new Display.Brightness(15, 15));
d.setViewRange(64);
});
EntityTracker.mark(crystal);
p.getWorld().playSound(spawn, Sound.BLOCK_AMETHYST_BLOCK_PLACE, 1f, 0.9f);
p.getWorld().playSound(spawn, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 1f, 1.5f);
Vector vel = dir.clone().multiply(1.5);
@@ -39,6 +40,7 @@ public class DiamondAttack {
if (hitBlock) {
Location expl = cur.clone().add(vel.clone().normalize().multiply(-0.1));
crystal.teleport(expl);
EntityTracker.untrack(crystal);
crystal.remove();
cancel();
expl.getWorld().spawnParticle(Particle.EXPLOSION, expl, 1);
@@ -50,6 +52,7 @@ public class DiamondAttack {
s.setBrightness(new Display.Brightness(15, 15));
s.setViewRange(64);
});
EntityTracker.mark(shard);
Vector sv = d.clone().multiply(0.4);
new BukkitRunnable() {
@Override public void run() {
@@ -59,6 +62,7 @@ public class DiamondAttack {
if (n2.getBlock().isSolid()) {
Location expl2 = cur2.clone().add(sv.clone().normalize().multiply(-0.1));
shard.teleport(expl2);
EntityTracker.untrack(shard);
shard.remove();
cancel();
return;
@@ -68,6 +72,7 @@ public class DiamondAttack {
for (var ent : n2.getWorld().getNearbyEntities(n2, 0.7, 0.7, 0.7, e -> e instanceof LivingEntity && e != p)) {
LivingEntity le = (LivingEntity) ent;
DamageUtil.apply(p, le, 2.0);
EntityTracker.untrack(shard);
shard.remove();
cancel();
break;
@@ -76,10 +81,11 @@ public class DiamondAttack {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(n2);
if (d2 > 0.25 && d2 < 0.81) {
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().addTP(pl, 2.5f, "DODGE +1%");
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
EntityTracker.untrack(shard);
if (shard.getTicksLived() > 40) { shard.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -100,12 +106,13 @@ public class DiamondAttack {
if (pl == p) continue;
double d = pl.getLocation().add(0, 1, 0).distanceSquared(nl);
if (d > 0.25 && d < 0.81) {
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().addTP(pl, 2.5f, "DODGE +1%");
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
if (hit) {
Location expl = nl.clone();
EntityTracker.untrack(crystal);
crystal.remove();
cancel();
expl.getWorld().spawnParticle(Particle.EXPLOSION, expl, 1);
@@ -117,6 +124,7 @@ public class DiamondAttack {
s.setBrightness(new Display.Brightness(15, 15));
s.setViewRange(64);
});
EntityTracker.mark(shard);
Vector sv = d.clone().multiply(0.4);
new BukkitRunnable() {
@Override public void run() {
@@ -126,6 +134,7 @@ public class DiamondAttack {
if (n2.getBlock().isSolid()) {
Location expl2 = cur2.clone().add(sv.clone().normalize().multiply(-0.1));
shard.teleport(expl2);
EntityTracker.untrack(shard);
shard.remove();
cancel();
return;
@@ -134,20 +143,23 @@ public class DiamondAttack {
shard.getWorld().spawnParticle(Particle.CRIT, n2, 1, 0.05, 0.05, 0.05, 0);
for (var ent : n2.getWorld().getNearbyEntities(n2, 0.7, 0.7, 0.7, e -> e instanceof LivingEntity && e != p)) {
LivingEntity le = (LivingEntity) ent;
EntityTracker.untrack(shard);
DamageUtil.apply(p, le, 2.0); shard.remove(); cancel(); break;
}
for (Player pl : n2.getWorld().getNearbyPlayers(n2, 0.9)) {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(n2);
if (d2 > 0.25 && d2 < 0.81) {
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().addTP(pl, 2.5f, "DODGE +1%");
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
EntityTracker.untrack(shard);
if (shard.getTicksLived() > 40) { shard.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
}
EntityTracker.untrack(crystal);
} else if (crystal.getTicksLived() > 60) { crystal.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -61,6 +61,7 @@ public class DiamondTripleAttack {
int steps = Math.max(1, (int)Math.ceil(vel.length()/0.5));
for (int s=0;s<=steps;s++) {
Location check = cur.clone().add(vel.clone().multiply((double)s/steps));
EntityTracker.untrack(shard);
if (check.getBlock().isSolid()) { shard.remove(); cancel(); return; }
}
shard.teleport(nl);
@@ -71,13 +72,15 @@ public class DiamondTripleAttack {
DamageUtil.apply(p, le, 7.0);
le.addPotionEffect(new org.bukkit.potion.PotionEffect(org.bukkit.potion.PotionEffectType.SLOWNESS,20,0,false,false,true));
nl.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0,1,0),8,0.3,0.3,0.3,0.1);
EntityTracker.untrack(shard);
shard.remove(); cancel(); return;
}
for (Player pl : nl.getWorld().getNearbyPlayers(nl,0.9)) {
if (pl==p) continue;
double d2 = pl.getLocation().add(0,1,0).distanceSquared(nl);
if (d2>0.25 && d2<0.81) plugin.getTension().addTP(pl,2.5f,"DODGE +1%");
if (d2>0.25 && d2<0.81) plugin.getTension().tryGiveDodge(pl);
}
EntityTracker.untrack(shard);
if (nl.distanceSquared(spawn) > 1600 || shard.getTicksLived() > 80) { shard.remove(); cancel(); }
}
}.runTaskTimer(plugin,1L,1L);
@@ -27,10 +27,7 @@ public class EntityTracker {
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 (e instanceof org.bukkit.entity.TextDisplay td) {
if (td.getPersistentDataContainer().has(new NamespacedKey("blackknife", "knocked"), PersistentDataType.BYTE)) isBk = true;
}
if (isBk) { e.remove(); count++; }
if (isBk) { untrack(e); e.remove(); count++; }
}
}
for (var uuid : new java.util.HashSet<>(tracked)) {
@@ -7,7 +7,6 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.persistence.PersistentDataType;
import java.util.Random;
public class KillMessageHandler implements Listener {
@@ -49,18 +48,17 @@ public class KillMessageHandler implements Listener {
}
private String detectMagic(Player killer, Player victim) {
var lastDamage = victim.getLastDamageCause();
if (lastDamage != null && lastDamage.getCause() != null) {
Component killerName = Component.text(killer.getName());
if (SwoonHandler.isSwooned(victim)) return "SWOON";
var ml = plugin.getMagicListener();
if (ml != null) {
MagicType last = ml.getLastCast(killer, 10000L);
if (last != null) return last.display;
}
if (killer.getPersistentDataContainer().has(SwoonHandler.SWOONED, PersistentDataType.BYTE)) return "SWOON";
if (BlackKnifeItem.is(killer.getInventory().getItemInMainHand()) || BlackKnifeItem.is(killer.getInventory().getItemInOffHand())) {
String mode = killer.getPersistentDataContainer().has(BlackKnifeItem.MODE, PersistentDataType.STRING) ? killer.getPersistentDataContainer().get(BlackKnifeItem.MODE, PersistentDataType.STRING) : "Black Knife";
var kl = plugin.getKnifeListener();
String mode = kl != null ? kl.getMode(killer) : "Black Knife";
return mode != null ? mode : "Black Knife";
}
for (MagicType mt : MagicType.values()) {
if (killer.hasCooldown(org.bukkit.Material.AMETHYST_SHARD)) return mt.display;
}
return null;
}
@@ -79,13 +77,16 @@ public class KillMessageHandler implements Listener {
}
private Component replaceFirst(Component in, String target, Component repl) {
String plain = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(in);
int idx = plain.indexOf(target);
if (idx < 0) return in;
String before = plain.substring(0, idx);
String after = plain.substring(idx + target.length());
Component out = Component.text(before).append(repl).append(Component.text(after));
return out;
try {
return in.replaceText(b -> b.matchLiteral(target).once().replacement(repl));
} catch (Exception e) {
String plain = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(in);
int idx = plain.indexOf(target);
if (idx < 0) return in;
String before = plain.substring(0, idx);
String after = plain.substring(idx + target.length());
return Component.text(before).append(repl).append(Component.text(after));
}
}
private boolean isRu() {
@@ -1,118 +0,0 @@
package me.sashegdev.blackknife;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Display;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TextDisplay;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.potion.PotionEffectType;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class KnockedHandler implements Listener {
private final BlackKnifePlugin plugin;
private final NamespacedKey KNOCKED = new NamespacedKey("blackknife", "knocked");
private final NamespacedKey VHP = new NamespacedKey("blackknife", "vhp");
private final Map<UUID, TextDisplay> displays = new HashMap<>();
public KnockedHandler(BlackKnifePlugin plugin) { this.plugin = plugin; }
@EventHandler
public void onDamage(EntityDamageEvent e) {
if (!(e.getEntity() instanceof Player p)) return;
if (p.getPersistentDataContainer().has(KNOCKED, PersistentDataType.BYTE)) {
e.setCancelled(true);
return;
}
double dmg = e.getFinalDamage();
double health = p.getHealth();
if (dmg < health) return;
var nearby = p.getWorld().getNearbyEntities(p.getLocation(), 8, 8, 8, en -> en instanceof Player && en != p && !((Player)en).getPersistentDataContainer().has(KNOCKED, PersistentDataType.BYTE));
long attackers = 0;
if (e instanceof org.bukkit.event.entity.EntityDamageByEntityEvent ev && ev.getDamager() instanceof Player) attackers = 1;
boolean hasAlly = !nearby.isEmpty();
if (!hasAlly) {
return;
}
e.setCancelled(true);
double vhp = health - dmg;
p.getPersistentDataContainer().set(KNOCKED, PersistentDataType.BYTE, (byte) 1);
p.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, vhp);
p.setHealth(1.0);
p.setWalkSpeed(0f);
p.addPotionEffect(new org.bukkit.potion.PotionEffect(PotionEffectType.WEAKNESS, 100000, 10, false, false));
TextDisplay td = p.getWorld().spawn(p.getLocation().add(0, 2.4, 0), TextDisplay.class, d -> {
d.text(Component.text(String.format("%.0f HP", vhp)).color(vhp < 0 ? NamedTextColor.RED : NamedTextColor.YELLOW));
d.setBillboard(Display.Billboard.CENTER);
d.setSeeThrough(true);
d.setShadowed(true);
d.setBackgroundColor(org.bukkit.Color.fromARGB(64, 0, 0, 0));
d.setAlignment(TextDisplay.TextAlignment.CENTER);
});
displays.put(p.getUniqueId(), td);
p.sendActionBar(Component.text(String.format("KNOCKED %.0f HP - regen potion to revive", vhp)).color(NamedTextColor.RED));
plugin.getServer().getScheduler().runTaskTimer(plugin, task -> {
TextDisplay d = displays.get(p.getUniqueId());
if (d == null || !p.isOnline() || !p.getPersistentDataContainer().has(KNOCKED, PersistentDataType.BYTE)) { task.cancel(); return; }
d.teleport(p.getLocation().add(0, 2.4, 0));
if (p.isDead()) { d.remove(); task.cancel(); }
}, 2L, 2L);
}
@EventHandler
public void onMove(PlayerMoveEvent e) {
Player p = e.getPlayer();
if (!p.getPersistentDataContainer().has(KNOCKED, PersistentDataType.BYTE)) return;
if (e.getFrom().distanceSquared(e.getTo()) < 0.01) return;
double dx = e.getTo().getX() - e.getFrom().getX();
double dz = e.getTo().getZ() - e.getFrom().getZ();
if (Math.abs(dx) > 0.1 || Math.abs(dz) > 0.1) {
e.setCancelled(true);
p.sendActionBar(Component.text("Knocked - can't move, regen potion to revive").color(NamedTextColor.RED));
}
}
@EventHandler
public void onPotionSplash(org.bukkit.event.entity.PotionSplashEvent e) {
if (e.getPotion().getEffects().stream().noneMatch(ef -> ef.getType().equals(PotionEffectType.REGENERATION))) return;
for (LivingEntity ent : e.getAffectedEntities()) {
if (!(ent instanceof Player p)) continue;
if (!p.getPersistentDataContainer().has(KNOCKED, PersistentDataType.BYTE)) continue;
double intensity = e.getIntensity(p);
double heal = 4 * intensity;
Double vhp = p.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
if (vhp == null) vhp = -10.0;
double newVhp = vhp + heal;
p.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, newVhp);
TextDisplay td = displays.get(p.getUniqueId());
if (td != null) td.text(Component.text(String.format("%.0f HP", newVhp)).color(newVhp < 0 ? NamedTextColor.RED : NamedTextColor.GREEN));
if (newVhp >= 1) {
p.getPersistentDataContainer().remove(KNOCKED);
p.getPersistentDataContainer().remove(VHP);
p.setHealth(1.0);
p.setWalkSpeed(0.2f);
p.removePotionEffect(PotionEffectType.WEAKNESS);
p.removePotionEffect(PotionEffectType.SLOWNESS);
if (td != null) { td.remove(); displays.remove(p.getUniqueId()); }
p.sendActionBar(Component.text("Revived!").color(NamedTextColor.GREEN));
}
}
}
@EventHandler
public void onDeath(PlayerDeathEvent e) {
displays.remove(e.getEntity().getUniqueId());
}
}
@@ -39,11 +39,13 @@ public class LungeAttack {
d.setBrightness(new Display.Brightness(15, 15));
d.setViewRange(64);
});
EntityTracker.mark(catalyst);
p.getWorld().playSound(start, Sound.BLOCK_SCULK_CATALYST_BLOOM, 1f, 0.7f);
double catSpeed = 3.0;
new BukkitRunnable() {
@Override public void run() {
EntityTracker.untrack(catalyst);
if (!target.isValid() || target.isDead()) { if (catalyst.isValid()) catalyst.remove(); cancel(); return; }
if (!catalyst.isValid()) { cancel(); return; }
Location cur = catalyst.getLocation();
@@ -55,6 +57,7 @@ public class LungeAttack {
nl.getWorld().spawnParticle(Particle.DUST, nl, 1, 0, 0, 0, 0, new Particle.DustOptions(Color.fromRGB(20, 60, 80), 1f));
if (nl.distanceSquared(tpos) < 4) {
catalyst.teleport(tpos);
EntityTracker.untrack(catalyst);
catalyst.remove();
cancel();
tpos.getWorld().spawnParticle(Particle.EXPLOSION, tpos, 1);
@@ -62,6 +65,7 @@ public class LungeAttack {
tpos.getWorld().playSound(tpos, Sound.ENTITY_WARDEN_SONIC_BOOM, 1f, 0.6f);
startPhase1(p, tpos, target, plugin);
}
EntityTracker.untrack(catalyst);
if (catalyst.getTicksLived() > 80) { catalyst.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -149,6 +153,7 @@ public class LungeAttack {
new org.joml.Vector3f(1.5f, 1.5f, 1.5f),
new org.joml.AxisAngle4f()));
});
EntityTracker.mark(sword);
new BukkitRunnable() {
long hold = 5;
boolean launched = false;
@@ -172,6 +177,7 @@ public class LungeAttack {
int steps = Math.max(1, (int) Math.ceil(aim.length() / 0.5));
for (int s = 0; s <= steps; s++) {
Location check = cur.clone().add(aim.clone().multiply((double) s / steps));
EntityTracker.untrack(sword);
if (check.getBlock().isSolid()) { sword.remove(); cancel(); return; }
}
sword.teleport(nl);
@@ -179,13 +185,15 @@ public class LungeAttack {
if (le == p) continue;
applyLungeDamage(le, p, damage);
le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0, 1, 0), 10, 0.4, 0.4, 0.4, 0.1);
EntityTracker.untrack(sword);
sword.remove(); cancel(); return;
}
for (Player pl : nl.getWorld().getNearbyPlayers(nl, 0.9)) {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(nl);
if (d2 > 0.25 && d2 < 0.81) plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
if (d2 > 0.25 && d2 < 0.81) plugin.getTension().tryGiveDodge(pl);
}
EntityTracker.untrack(sword);
if (sword.getTicksLived() > 80) { sword.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -235,6 +243,7 @@ public class LungeAttack {
nl.getWorld().spawnParticle(Particle.SCULK_SOUL, nl, 1, 0.05, 0.05, 0.05, 0);
if (nl.getY() <= gazePoint.getY() + 0.5 || nl.getBlock().isSolid()) {
Location expl = gazePoint.clone();
EntityTracker.untrack(sword);
sword.remove(); cancel();
expl.getWorld().spawnParticle(Particle.EXPLOSION, expl, 1);
expl.getWorld().spawnParticle(Particle.SMOKE, expl, 10, 0.3, 0.1, 0.3, 0.02);
@@ -254,7 +263,7 @@ public class LungeAttack {
for (Player pl : expl.getWorld().getNearbyPlayers(expl, 2.5)) {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(expl);
if (d2 > 0.25 && d2 < 6.5) plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
if (d2 > 0.25 && d2 < 6.5) plugin.getTension().tryGiveDodge(pl);
}
expl.getWorld().playSound(expl, Sound.ENTITY_GENERIC_EXPLODE, 0.8f, 1.2f);
return;
@@ -262,8 +271,10 @@ public class LungeAttack {
for (LivingEntity le : nl.getWorld().getNearbyLivingEntities(nl, 1.0)) {
if (le == p) continue;
applyLungeDamage(le, p, 20);
EntityTracker.untrack(sword);
sword.remove(); cancel(); return;
}
EntityTracker.untrack(sword);
if (sword.getTicksLived() > 60) { sword.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -271,6 +282,7 @@ public class LungeAttack {
}
private static void applyLungeDamage(LivingEntity ent, Player attacker, double amount) {
if (!DamageUtil.canHurt(attacker, ent)) return;
if (ent instanceof Player pl && pl.isBlocking()) amount /= 2.5;
ent.getPersistentDataContainer().set(SwoonHandler.MAGIC_DAMAGE, PersistentDataType.BYTE, (byte) 1);
ent.damage(amount, attacker);
@@ -357,6 +369,7 @@ public class LungeAttack {
new org.joml.Vector3f(0.7f, 0.7f, 0.7f),
new org.joml.AxisAngle4f()));
});
EntityTracker.mark(sword);
Vector vel = new Vector(0, -fallSpeed, 0);
p.getWorld().playSound(spawn, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.6f, 0.7f);
new BukkitRunnable() {
@@ -369,6 +382,7 @@ public class LungeAttack {
nl.getWorld().spawnParticle(Particle.SCULK_SOUL, nl, 1, 0.05, 0.05, 0.05, 0);
if (nl.getY() <= target.getY() + 0.5 || nl.getBlock().isSolid()) {
Location expl = nl.clone();
EntityTracker.untrack(sword);
sword.remove();
cancel();
expl.getWorld().spawnParticle(Particle.SMOKE, expl, 6, 0.2, 0.1, 0.2, 0.02);
@@ -387,7 +401,7 @@ public class LungeAttack {
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) plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
if (d2 > 0.25 && d2 < 0.81) plugin.getTension().tryGiveDodge(pl);
}
return;
}
@@ -395,8 +409,10 @@ public class LungeAttack {
if (le == p) continue;
applyLungeDamage(le, p, damage);
le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0, 1, 0), 8, 0.3, 0.3, 0.3, 0.1);
EntityTracker.untrack(sword);
sword.remove(); cancel(); return;
}
EntityTracker.untrack(sword);
if (sword.getTicksLived() > 80) { sword.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -446,6 +462,7 @@ public class LungeAttack {
new org.joml.Vector3f(1.5f, 1.5f, 1.5f),
new org.joml.AxisAngle4f()));
});
EntityTracker.mark(sword);
spawn.getWorld().playSound(spawn, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.8f, 0.5f);
new BukkitRunnable() {
@@ -472,6 +489,7 @@ public class LungeAttack {
int steps = Math.max(1, (int) Math.ceil(aim.length() / 0.5));
for (int s = 0; s <= steps; s++) {
Location check = cur.clone().add(aim.clone().multiply((double) s / steps));
EntityTracker.untrack(sword);
if (check.getBlock().isSolid()) { sword.remove(); cancel(); return; }
}
sword.teleport(nl);
@@ -479,13 +497,15 @@ public class LungeAttack {
if (le == p) continue;
applyLungeDamage(le, p, 20);
le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0, 1, 0), 10, 0.4, 0.4, 0.4, 0.1);
EntityTracker.untrack(sword);
sword.remove(); cancel(); return;
}
for (Player pl : nl.getWorld().getNearbyPlayers(nl, 0.9)) {
if (pl == p) continue;
double d2 = pl.getLocation().add(0, 1, 0).distanceSquared(nl);
if (d2 > 0.25 && d2 < 0.81) plugin.getTension().addTP(pl, 2.5f, "DODGE +1%");
if (d2 > 0.25 && d2 < 0.81) plugin.getTension().tryGiveDodge(pl);
}
EntityTracker.untrack(sword);
if (sword.getTicksLived() > 80) { sword.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -28,9 +28,24 @@ public class MagicListener implements Listener {
private final MagicManager mgr;
private final Map<UUID, Long> shieldEnd = new HashMap<>();
private final Map<UUID, BukkitRunnable> shieldTasks = new HashMap<>();
private final Map<UUID, MagicType> lastCast = new HashMap<>();
private final Map<UUID, Long> lastCastTime = new HashMap<>();
public MagicListener(BlackKnifePlugin plugin, MagicManager mgr) { this.plugin = plugin; this.mgr = mgr; }
public void recordCast(Player p, MagicType mt) {
lastCast.put(p.getUniqueId(), mt);
lastCastTime.put(p.getUniqueId(), System.currentTimeMillis());
}
public MagicType getLastCast(Player p, long withinMs) {
MagicType mt = lastCast.get(p.getUniqueId());
Long t = lastCastTime.get(p.getUniqueId());
if (mt == null || t == null) return null;
if (System.currentTimeMillis() - t > withinMs) return null;
return mt;
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
Player p = e.getPlayer();
@@ -133,9 +148,10 @@ public class MagicListener implements Listener {
else if (mt == MagicType.SHIELDO) ok = castShieldo(p, tm);
if (ok) {
tm.setTP(p, tm.getTP(p) - mt.cost);
prof.casts++; prof.tpSpent += mt.cost; mgr.save(prof);
mgr.recordCast(prof, mt.cost); mgr.save(prof);
p.setCooldown(Material.AMETHYST_SHARD, mt.cd);
MagicItem.updateLore(item, prof);
recordCast(p, mt);
}
e.setCancelled(true);
}
@@ -159,6 +175,7 @@ public class MagicListener implements Listener {
if (check.getBlock().isSolid()) {
Location expl = cur.clone().add(vel.clone().normalize().multiply(Math.max(0, (double)(s-1)/steps)));
for (int k=0;k<12;k++){ double a=(double)k/12*Math.PI*2; for(int h=-1;h<=1;h++){ Location sphere=expl.clone().add(Math.cos(a)*0.5, h*0.25, Math.sin(a)*0.5); sphere.getWorld().spawnParticle(Particle.DUST, sphere,1,0,0,0,0,new Particle.DustOptions(Color.fromRGB(180,40,40),1f)); sphere.getWorld().spawnParticle(Particle.SMOKE,sphere,1,0.02,0.02,0.02,0);} } expl.getWorld().spawnParticle(Particle.CRIT,expl,6,0.2,0.2,0.2,0.05); expl.getWorld().playSound(expl,Sound.BLOCK_STONE_BREAK,1f,0.8f);
EntityTracker.untrack(marker);
marker.remove(); cancel(); return;
}
}
@@ -170,8 +187,10 @@ public class MagicListener implements Listener {
Location hit=le.getLocation().add(0,1,0);
for(int k=0;k<12;k++){ double a=(double)k/12*Math.PI*2; for(int h=-1;h<=1;h++){ Location s=hit.clone().add(Math.cos(a)*0.5,h*0.25,Math.sin(a)*0.5); s.getWorld().spawnParticle(Particle.DUST,s,1,0,0,0,0,new Particle.DustOptions(Color.fromRGB(200,40,40),0.9f)); } } hit.getWorld().spawnParticle(Particle.CRIT,hit,10,0.3,0.3,0.3,0.1); hit.getWorld().playSound(hit,Sound.ENTITY_PLAYER_ATTACK_CRIT,1f,0.9f);
le.addPotionEffect(new org.bukkit.potion.PotionEffect(org.bukkit.potion.PotionEffectType.SLOWNESS,40,1,false,false,true));
EntityTracker.untrack(marker);
marker.remove(); cancel(); return;
}
EntityTracker.untrack(marker);
if (marker.getTicksLived()>80){ marker.remove(); cancel(); }
}
}.runTaskTimer(plugin,1L,1L);
@@ -197,6 +216,7 @@ public class MagicListener implements Listener {
if (check.getBlock().isSolid()) {
Location expl = cur.clone().add(vel.clone().normalize().multiply(Math.max(0, (double)(s-1)/steps)));
for(int k=0;k<12;k++){ double a=(double)k/12*Math.PI*2; for(int h=-1;h<=1;h++){ Location sphere=expl.clone().add(Math.cos(a)*0.5,h*0.25,Math.sin(a)*0.5); sphere.getWorld().spawnParticle(Particle.SNOWFLAKE,sphere,1,0.02,0.02,0.02,0); sphere.getWorld().spawnParticle(Particle.DUST,sphere,1,0,0,0,0,new Particle.DustOptions(Color.fromRGB(136,204,255),0.9f)); } } expl.getWorld().spawnParticle(Particle.SNOWFLAKE,expl,10,0.3,0.3,0.3,0.1); expl.getWorld().playSound(expl,Sound.BLOCK_GLASS_BREAK,1f,1.2f);
EntityTracker.untrack(marker);
marker.remove(); cancel(); return;
}
}
@@ -208,8 +228,10 @@ public class MagicListener implements Listener {
Location hit=le.getLocation().add(0,1,0);
for(int k=0;k<12;k++){ double a=(double)k/12*Math.PI*2; for(int h=-1;h<=1;h++){ Location s=hit.clone().add(Math.cos(a)*0.5,h*0.25,Math.sin(a)*0.5); s.getWorld().spawnParticle(Particle.SNOWFLAKE,s,1,0.02,0.02,0.02,0); } } hit.getWorld().spawnParticle(Particle.SNOWFLAKE,hit,8,0.3,0.3,0.3,0.1); hit.getWorld().playSound(hit,Sound.BLOCK_GLASS_BREAK,1f,0.7f);
le.addPotionEffect(new org.bukkit.potion.PotionEffect(org.bukkit.potion.PotionEffectType.SLOWNESS,40,1,false,false,true));
EntityTracker.untrack(marker);
marker.remove(); cancel(); return;
}
EntityTracker.untrack(marker);
if (marker.getTicksLived()>80){ marker.remove(); cancel(); }
}
}.runTaskTimer(plugin,1L,1L);
@@ -271,4 +293,18 @@ public class MagicListener implements Listener {
p.getWorld().playSound(p.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.8f, 1.3f);
return dmg * 0.7;
}
@EventHandler
public void onQuit(org.bukkit.event.player.PlayerQuitEvent e) {
var task = shieldTasks.remove(e.getPlayer().getUniqueId());
if (task != null) task.cancel();
shieldEnd.remove(e.getPlayer().getUniqueId());
}
@EventHandler
public void onDeath(org.bukkit.event.entity.PlayerDeathEvent e) {
var task = shieldTasks.remove(e.getEntity().getUniqueId());
if (task != null) task.cancel();
shieldEnd.remove(e.getEntity().getUniqueId());
}
}
@@ -3,13 +3,12 @@ package me.sashegdev.blackknife;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MagicManager {
private final BlackKnifePlugin plugin;
private final Map<UUID, MagicProfile> map = new HashMap<>();
private final Map<UUID, MagicProfile> map = new java.util.concurrent.ConcurrentHashMap<>();
private File folder;
public MagicManager(BlackKnifePlugin plugin) {
@@ -51,7 +50,7 @@ public class MagicManager {
return p != null && p.awakened;
}
public MagicProfile awaken(Player p, String cause) {
public synchronized MagicProfile awaken(Player p, String cause) {
MagicProfile prof = map.get(p.getUniqueId());
if (prof == null) prof = new MagicProfile(p.getName(), p.getUniqueId());
prof.nick = p.getName();
@@ -71,7 +70,7 @@ public class MagicManager {
return prof;
}
public MagicProfile getOrCreate(Player p) {
public synchronized MagicProfile getOrCreate(Player p) {
MagicProfile prof = map.get(p.getUniqueId());
if (prof == null) {
prof = new MagicProfile(p.getName(), p.getUniqueId());
@@ -87,26 +86,59 @@ public class MagicManager {
public MagicProfile get(UUID uuid) { return map.get(uuid); }
public void save(MagicProfile prof) {
final String nick;
final long firstSeen;
final int casts;
final double tpSpent;
final int heals;
final int selectedIdx;
final java.util.List<String> common;
final java.util.List<String> rare;
final java.util.List<String> story;
final boolean rareRolled;
final boolean awakened;
final String cause;
final long at;
synchronized (prof) {
nick = prof.nick;
firstSeen = prof.firstSeen;
casts = prof.casts;
tpSpent = prof.tpSpent;
heals = prof.heals;
selectedIdx = prof.selectedIdx;
common = new java.util.ArrayList<>(prof.common);
rare = new java.util.ArrayList<>(prof.rare);
story = new java.util.ArrayList<>(prof.story);
rareRolled = prof.rareRolled;
awakened = prof.awakened;
cause = prof.awakeningCause;
at = prof.awakenedAt;
}
try {
File f = new File(folder, prof.uuid.toString() + ".yml");
YamlConfiguration y = new YamlConfiguration();
y.set("nick", prof.nick);
y.set("nick", nick);
y.set("uuid", prof.uuid.toString());
y.set("firstSeen", prof.firstSeen);
y.set("stats.casts", prof.casts);
y.set("stats.tpSpent", prof.tpSpent);
y.set("stats.heals", prof.heals);
y.set("pack.common", prof.common);
y.set("pack.rare", prof.rare);
y.set("pack.story", prof.story);
y.set("pack.selected", prof.selectedIdx);
y.set("flags.rareRolled", prof.rareRolled);
y.set("flags.awakened", prof.awakened);
y.set("flags.cause", prof.awakeningCause);
y.set("flags.at", prof.awakenedAt);
y.set("firstSeen", firstSeen);
y.set("stats.casts", casts);
y.set("stats.tpSpent", tpSpent);
y.set("stats.heals", heals);
y.set("pack.common", common);
y.set("pack.rare", rare);
y.set("pack.story", story);
y.set("pack.selected", selectedIdx);
y.set("flags.rareRolled", rareRolled);
y.set("flags.awakened", awakened);
y.set("flags.cause", cause);
y.set("flags.at", at);
y.save(f);
} catch (Exception e) { plugin.getLogger().warning("Failed save magic " + prof.uuid + ": " + e.getMessage()); }
}
public void saveAll() { for (MagicProfile p : map.values()) save(p); }
public synchronized void recordCast(MagicProfile prof, double cost) {
prof.casts++;
prof.tpSpent += cost;
}
public void saveAll() { for (MagicProfile p : new java.util.ArrayList<>(map.values())) save(p); }
}
@@ -14,10 +14,12 @@ public class ParryHandler implements Listener {
private final BlackKnifePlugin plugin;
public ParryHandler(BlackKnifePlugin plugin) { this.plugin = plugin; }
@EventHandler(priority = EventPriority.HIGH)
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onDamage(EntityDamageByEntityEvent e) {
if (!(e.getEntity() instanceof Player victim)) return;
if (e.isCancelled()) return;
if (victim.getGameMode() == org.bukkit.GameMode.CREATIVE || victim.getGameMode() == org.bukkit.GameMode.SPECTATOR) return;
if (victim.isInvulnerable()) return;
if (victim.getPersistentDataContainer().has(SwoonHandler.SWOONED, PersistentDataType.BYTE)) return;
if (BlackKnifeItem.is(victim.getInventory().getItemInMainHand()) || BlackKnifeItem.is(victim.getInventory().getItemInOffHand())) {
TensionManager tm = plugin.getTension();
if (tm.getTP(victim) < 37.5f) return;
@@ -57,13 +57,16 @@ public class PhantomBarrageAttack {
Vector toTarget = target.clone().subtract(cur).toVector().normalize().multiply(1.6);
Location nl = cur.clone().add(toTarget);
int steps = Math.max(1,(int)Math.ceil(toTarget.length()/0.5));
EntityTracker.untrack(orb);
for(int s=0;s<=steps;s++){ Location check=cur.clone().add(toTarget.clone().multiply((double)s/steps)); if(check.getBlock().isSolid()){orb.remove();cancel();return;} }
orb.teleport(nl);
cur = nl;
nl.getWorld().spawnParticle(Particle.DUST, nl,1,0,0,0,0,new Particle.DustOptions(Color.fromRGB(30,10,50),1.2f));
nl.getWorld().spawnParticle(Particle.SCULK_SOUL, nl,1,0.05,0.05,0.05,0);
EntityTracker.untrack(orb);
for(LivingEntity le: nl.getWorld().getNearbyLivingEntities(nl,0.9)){ if(le==p) continue; DamageUtil.apply(p, le, 6.0); le.getWorld().spawnParticle(Particle.CRIT, le.getLocation().add(0,1,0),8,0.3,0.3,0.3,0.1); orb.remove(); cancel(); return; }
for(Player pl: nl.getWorld().getNearbyPlayers(nl,0.9)){ if(pl==p) continue; double d2=pl.getLocation().add(0,1,0).distanceSquared(nl); if(d2>0.25 && d2<0.81) plugin.getTension().addTP(pl,2.5f,"DODGE +1%"); }
for(Player pl: nl.getWorld().getNearbyPlayers(nl,0.9)){ if(pl==p) continue; double d2=pl.getLocation().add(0,1,0).distanceSquared(nl); if(d2>0.25 && d2<0.81) plugin.getTension().tryGiveDodge(pl); }
EntityTracker.untrack(orb);
if(cur.distanceSquared(target) < 1 || orb.getTicksLived() > 100){ orb.remove(); cancel(); }
ticks++;
}
@@ -34,43 +34,31 @@ public class SwoonHandler implements Listener {
public SwoonHandler(BlackKnifePlugin plugin) { this.plugin = plugin; }
@EventHandler(priority = EventPriority.HIGHEST)
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onHit(org.bukkit.event.entity.EntityDamageByEntityEvent e) {
if (!(e.getDamager() instanceof Player p)) return;
if (p.getGameMode() == org.bukkit.GameMode.SPECTATOR) return;
if (!BlackKnifeItem.is(p.getInventory().getItemInMainHand()) && !BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return;
if (!(e.getEntity() instanceof LivingEntity target)) return;
if (target.getPersistentDataContainer().has(MAGIC_DAMAGE, PersistentDataType.BYTE)) {
target.getPersistentDataContainer().remove(MAGIC_DAMAGE);
return;
}
if (target.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return;
if (target instanceof Player vt) {
if (vt.getGameMode() == org.bukkit.GameMode.CREATIVE || vt.getGameMode() == org.bukkit.GameMode.SPECTATOR) return;
if (vt.isInvulnerable()) return;
}
if (p.hasCooldown(Material.NETHERITE_SWORD)) return;
e.setCancelled(true);
plugin.getTension().setTP(p, Math.max(0, plugin.getTension().getTP(p) - 125f));
if (target instanceof Player tp) {
double maxHp = tp.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue();
var attr = tp.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH);
double maxHp = attr != null ? attr.getValue() : 20.0;
double vhpTmp = tp.getHealth() - 999;
double vhp = Math.max(-2147483648.0, Math.min(maxHp, vhpTmp));
final double finalVhp = vhp;
tp.setHealth(1.0);
tp.setWalkSpeed(0f);
tp.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte) 1);
tp.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, vhp);
tp.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 100000, 10, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 100000, 128, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, 100000, 10, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, 100000, 0, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 100, 0, false, false, true));
TextDisplay td = tp.getWorld().spawn(tp.getLocation().add(0, 2.4, 0), TextDisplay.class, d -> {
d.text(Component.text(String.format("%.0f HP", finalVhp)).color(NamedTextColor.DARK_RED));
d.setBillboard(Display.Billboard.CENTER);
d.setSeeThrough(true);
d.setShadowed(true);
d.setBackgroundColor(org.bukkit.Color.fromARGB(64, 40, 0, 0));
d.setAlignment(TextDisplay.TextAlignment.CENTER);
d.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte)1);
});
EntityTracker.mark(td);
displays.put(tp.getUniqueId(), td);
applySwoon(tp, vhp);
tp.getWorld().spawnParticle(org.bukkit.Particle.SOUL, tp.getLocation().add(0,1,0), 40, 0.5,0.5,0.5,0.05);
tp.getWorld().spawnParticle(org.bukkit.Particle.SCULK_SOUL, tp.getLocation().add(0,1,0), 15, 0.5,0.5,0.5,0.02);
tp.getWorld().spawnParticle(org.bukkit.Particle.ASH, tp.getLocation().add(0,1,0), 20, 0.5,0.5,0.5,0);
@@ -80,14 +68,7 @@ public class SwoonHandler implements Listener {
tp.getWorld().playSound(tp.getLocation(), Sound.ENTITY_WARDEN_SONIC_BOOM, 1f, 0.5f);
tp.getWorld().playSound(tp.getLocation(), Sound.ENTITY_ELDER_GUARDIAN_CURSE, 1f, 0.7f);
if (Math.random() < 0.1) tp.getWorld().dropItemNaturally(tp.getLocation(), BlackKnifeItem.create());
plugin.getServer().getScheduler().runTaskTimer(plugin, task -> {
TextDisplay d = displays.get(tp.getUniqueId());
if (d == null || !tp.isOnline() || !tp.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) { task.cancel(); return; }
d.teleport(tp.getLocation().add(0, 2.4, 0));
Double cur = tp.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
if (cur != null) d.text(Component.text(String.format("%.0f HP", cur)).color(cur < 0 ? NamedTextColor.DARK_RED : NamedTextColor.YELLOW));
if (tp.isDead()) { d.remove(); task.cancel(); }
}, 2L, 2L);
startDisplayTask(tp);
} else {
target.getPersistentDataContainer().set(MAGIC_DAMAGE, PersistentDataType.BYTE, (byte) 1);
target.damage(999.0, p);
@@ -154,31 +135,120 @@ public class SwoonHandler implements Listener {
Double vhp = p.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
if (vhp == null) vhp = -10.0;
double newVhp = vhp + heal;
double maxHp = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH).getValue();
var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH);
double maxHp = attr != null ? attr.getValue() : 20.0;
newVhp = Math.min(maxHp, newVhp);
p.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, newVhp);
TextDisplay td = displays.get(p.getUniqueId());
if (td != null) td.text(Component.text(String.format("%.0f HP", newVhp)).color(newVhp < 0 ? NamedTextColor.DARK_RED : newVhp < 1 ? NamedTextColor.YELLOW : NamedTextColor.GREEN));
if (td != null && td.isValid()) td.text(Component.text(String.format("%.0f HP", newVhp)).color(newVhp < 0 ? NamedTextColor.DARK_RED : newVhp < 1 ? NamedTextColor.YELLOW : NamedTextColor.GREEN));
if (newVhp >= 1) {
p.getPersistentDataContainer().remove(SWOONED);
p.getPersistentDataContainer().remove(VHP);
p.setHealth(Math.min(newVhp, maxHp));
p.setWalkSpeed(0.2f);
p.removePotionEffect(PotionEffectType.SLOWNESS);
p.removePotionEffect(PotionEffectType.JUMP_BOOST);
p.removePotionEffect(PotionEffectType.WEAKNESS);
p.removePotionEffect(PotionEffectType.DARKNESS);
if (td != null) { td.remove(); displays.remove(p.getUniqueId()); }
p.sendActionBar(Component.text("Revived from SWOON!").color(NamedTextColor.GREEN));
p.getWorld().playSound(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 1.2f);
revive(p);
}
}
}
private void applySwoon(Player tp, double vhp) {
tp.setHealth(1.0);
try { tp.setWalkSpeed(0f); } catch (Exception ignored) {}
tp.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte) 1);
tp.getPersistentDataContainer().set(VHP, PersistentDataType.DOUBLE, vhp);
tp.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 100000, 10, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 100000, 128, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, 100000, 10, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, 100000, 0, false, false, true));
tp.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 100, 0, false, false, true));
TextDisplay old = displays.remove(tp.getUniqueId());
if (old != null && old.isValid()) { EntityTracker.untrack(old); old.remove(); }
TextDisplay td = tp.getWorld().spawn(tp.getLocation().add(0, 2.4, 0), TextDisplay.class, d -> {
d.text(Component.text(String.format("%.0f HP", vhp)).color(NamedTextColor.DARK_RED));
d.setBillboard(Display.Billboard.CENTER);
d.setSeeThrough(true);
d.setShadowed(true);
d.setBackgroundColor(org.bukkit.Color.fromARGB(64, 40, 0, 0));
d.setAlignment(TextDisplay.TextAlignment.CENTER);
d.getPersistentDataContainer().set(SWOONED, PersistentDataType.BYTE, (byte) 1);
});
EntityTracker.mark(td);
displays.put(tp.getUniqueId(), td);
}
private void startDisplayTask(Player tp) {
plugin.getServer().getScheduler().runTaskTimer(plugin, task -> {
TextDisplay d = displays.get(tp.getUniqueId());
if (d == null || !d.isValid() || !tp.isOnline() || !tp.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) {
if (d != null && !d.isValid()) displays.remove(tp.getUniqueId());
task.cancel();
return;
}
d.teleport(tp.getLocation().add(0, 2.4, 0));
Double cur = tp.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
if (cur != null) d.text(Component.text(String.format("%.0f HP", cur)).color(cur < 0 ? NamedTextColor.DARK_RED : NamedTextColor.YELLOW));
if (tp.isDead()) { EntityTracker.untrack(d); d.remove(); displays.remove(tp.getUniqueId()); task.cancel(); }
}, 2L, 2L);
}
public void revive(Player p) {
Double stored = p.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
p.getPersistentDataContainer().remove(SWOONED);
p.getPersistentDataContainer().remove(VHP);
var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH);
double maxHp = attr != null ? attr.getValue() : 20.0;
double hp = stored != null ? Math.min(Math.max(stored, 1.0), maxHp) : 1.0;
try { p.setHealth(hp); } catch (Exception ignored) {}
try { p.setWalkSpeed(0.2f); } catch (Exception ignored) {}
p.removePotionEffect(PotionEffectType.SLOWNESS);
p.removePotionEffect(PotionEffectType.JUMP_BOOST);
p.removePotionEffect(PotionEffectType.WEAKNESS);
p.removePotionEffect(PotionEffectType.DARKNESS);
p.removePotionEffect(PotionEffectType.BLINDNESS);
TextDisplay td = displays.remove(p.getUniqueId());
if (td != null && td.isValid()) { EntityTracker.untrack(td); td.remove(); }
p.sendActionBar(Component.text("Revived from SWOON!").color(NamedTextColor.GREEN));
p.getWorld().playSound(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 1.2f);
}
public void cleanupOffline(Player p) {
TextDisplay td = displays.remove(p.getUniqueId());
if (td != null && td.isValid()) { EntityTracker.untrack(td); td.remove(); }
}
@EventHandler
public void onQuit(org.bukkit.event.player.PlayerQuitEvent e) {
cleanupOffline(e.getPlayer());
}
@EventHandler
public void onJoin(org.bukkit.event.player.PlayerJoinEvent e) {
Player p = e.getPlayer();
if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return;
Double vhp = p.getPersistentDataContainer().get(VHP, PersistentDataType.DOUBLE);
if (vhp == null) { p.getPersistentDataContainer().remove(SWOONED); try { p.setWalkSpeed(0.2f); } catch (Exception ignored) {} return; }
applySwoon(p, vhp);
startDisplayTask(p);
}
@EventHandler
public void onRespawn(org.bukkit.event.player.PlayerRespawnEvent e) {
Player p = e.getPlayer();
if (!p.getPersistentDataContainer().has(SWOONED, PersistentDataType.BYTE)) return;
p.getPersistentDataContainer().remove(SWOONED);
p.getPersistentDataContainer().remove(VHP);
try { p.setWalkSpeed(0.2f); } catch (Exception ignored) {}
p.removePotionEffect(PotionEffectType.SLOWNESS);
p.removePotionEffect(PotionEffectType.JUMP_BOOST);
p.removePotionEffect(PotionEffectType.WEAKNESS);
p.removePotionEffect(PotionEffectType.DARKNESS);
p.removePotionEffect(PotionEffectType.BLINDNESS);
cleanupOffline(p);
}
@EventHandler
public void onDeath(PlayerDeathEvent e) {
displays.remove(e.getEntity().getUniqueId());
e.getEntity().getPersistentDataContainer().remove(SWOONED);
Player p = e.getEntity();
TextDisplay td = displays.remove(p.getUniqueId());
if (td != null && td.isValid()) { EntityTracker.untrack(td); td.remove(); }
p.getPersistentDataContainer().remove(SWOONED);
p.getPersistentDataContainer().remove(VHP);
}
public static boolean isSwooned(LivingEntity e) {
@@ -57,6 +57,7 @@ public class SwordAttack {
}
expl.getWorld().spawnParticle(Particle.CRIT, expl, 6, 0.2, 0.2, 0.2, 0.05);
expl.getWorld().playSound(expl, Sound.BLOCK_STONE_BREAK, 1f, 0.8f);
EntityTracker.untrack(marker);
marker.remove();
cancel();
return;
@@ -84,6 +85,7 @@ public class SwordAttack {
hit.getWorld().playSound(hit, Sound.ENTITY_PLAYER_ATTACK_CRIT, 1f, 0.9f);
le.addPotionEffect(new org.bukkit.potion.PotionEffect(org.bukkit.potion.PotionEffectType.SLOWNESS, 40, 1, false, false, true));
le.addPotionEffect(new org.bukkit.potion.PotionEffect(org.bukkit.potion.PotionEffectType.WEAKNESS, 30, 0, false, false, true));
EntityTracker.untrack(marker);
marker.remove();
cancel();
return;
@@ -92,11 +94,13 @@ public class SwordAttack {
if (pl == p) continue;
double d = pl.getLocation().add(0, 1, 0).distanceSquared(nl);
if (d > 0.25 && d < 0.81) {
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().addTP(pl, 2.5f, "DODGE +1%");
BlackKnifePlugin.getPlugin(BlackKnifePlugin.class).getTension().tryGiveDodge(pl);
pl.getWorld().spawnParticle(Particle.DUST, pl.getLocation().add(0, 0.25, 0), 25, 0.5, 0.05, 0.5, 0, new Particle.DustOptions(Color.fromRGB(255, 255, 255), 0.15f));
}
}
EntityTracker.untrack(marker);
if (nl.distanceSquared(p.getEyeLocation()) > 14400) { marker.remove(); cancel(); return; }
EntityTracker.untrack(marker);
if (marker.getTicksLived() > 80) { marker.remove(); cancel(); }
}
}.runTaskTimer(plugin, 1L, 1L);
@@ -20,6 +20,20 @@ public class TensionManager implements Listener {
private final Map<UUID, Long> blockStart = new HashMap<>();
private final Map<UUID, Long> lastCombat = new HashMap<>();
private final Map<UUID, java.util.Set<UUID>> battleEnemies = new HashMap<>();
private final Map<UUID, Long> lastDodge = new HashMap<>();
private final Map<UUID, Boolean> ownerCache = new HashMap<>();
private final Map<UUID, Long> ownerCacheTime = new HashMap<>();
private static final long OWNER_CACHE_TTL = 2000L;
public boolean tryGiveDodge(Player pl) {
if (!isInBattle(pl)) return false;
long now = System.currentTimeMillis();
Long last = lastDodge.get(pl.getUniqueId());
if (last != null && now - last < 1000) return false;
lastDodge.put(pl.getUniqueId(), now);
addTP(pl, 2.5f, "DODGE +1%");
return true;
}
public float getTP(Player p) { return tp.getOrDefault(p.getUniqueId(), 0f); }
@@ -39,7 +53,21 @@ public class TensionManager implements Listener {
}
public boolean isOwner(Player p) {
return BlackKnifeItem.is(p.getInventory().getItemInMainHand()) || BlackKnifeItem.is(p.getInventory().getItemInOffHand()) || hasAnyBlackKnife(p);
if (BlackKnifeItem.is(p.getInventory().getItemInMainHand()) || BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return true;
long now = System.currentTimeMillis();
UUID id = p.getUniqueId();
Long t = ownerCacheTime.get(id);
Boolean cached = ownerCache.get(id);
if (cached != null && t != null && now - t < OWNER_CACHE_TTL) return cached;
boolean has = hasAnyBlackKnife(p);
ownerCache.put(id, has);
ownerCacheTime.put(id, now);
return has;
}
public void invalidateOwnerCache(Player p) {
ownerCache.remove(p.getUniqueId());
ownerCacheTime.remove(p.getUniqueId());
}
private boolean hasAnyBlackKnife(Player p) {
@@ -48,7 +76,8 @@ public class TensionManager implements Listener {
}
public boolean hasMagic(Player p) {
if (hasAnyBlackKnife(p)) return true;
if (BlackKnifeItem.is(p.getInventory().getItemInMainHand()) || BlackKnifeItem.is(p.getInventory().getItemInOffHand())) return true;
if (isOwner(p)) return true;
var mgr = BlackKnifePlugin.getInstance() != null ? BlackKnifePlugin.getInstance().getMagicManager() : null;
return mgr != null && mgr.hasAwakened(p.getUniqueId());
}
@@ -115,7 +144,8 @@ public class TensionManager implements Listener {
public void tickShield() {
long now = System.currentTimeMillis();
for (Player p : org.bukkit.Bukkit.getOnlinePlayers()) {
if (hasHeldShield(p)) {
boolean blocking = hasHeldShield(p);
if (blocking) {
long start = blockStart.getOrDefault(p.getUniqueId(), now);
if (!blockStart.containsKey(p.getUniqueId())) blockStart.put(p.getUniqueId(), now);
if (now - start >= 5000) {
@@ -123,7 +153,7 @@ public class TensionManager implements Listener {
blockStart.put(p.getUniqueId(), now);
lastCombat.put(p.getUniqueId(), now);
}
} else if (!hasHeldShield(p)) {
} else {
blockStart.remove(p.getUniqueId());
}
if (isOwner(p)) {
@@ -139,10 +169,38 @@ public class TensionManager implements Listener {
}
@EventHandler
public void onJoin(PlayerJoinEvent e) { updateBar(e.getPlayer(), getTP(e.getPlayer())); }
public void onJoin(PlayerJoinEvent e) { updateBar(e.getPlayer(), getTP(e.getPlayer())); invalidateOwnerCache(e.getPlayer()); }
@EventHandler
public void onQuit(PlayerQuitEvent e) { bars.remove(e.getPlayer().getUniqueId()); }
public void onQuit(PlayerQuitEvent e) {
UUID id = e.getPlayer().getUniqueId();
BossBar bar = bars.remove(id);
if (bar != null) bar.removeViewer(e.getPlayer());
ownerCache.remove(id);
ownerCacheTime.remove(id);
blockStart.remove(id);
lastDodge.remove(id);
}
@EventHandler
public void onInvChange(org.bukkit.event.inventory.InventoryClickEvent e) {
if (e.getWhoClicked() instanceof Player p) invalidateOwnerCache(p);
}
@EventHandler
public void onPickup(org.bukkit.event.entity.EntityPickupItemEvent e) {
if (e.getEntity() instanceof Player p) invalidateOwnerCache(p);
}
@EventHandler
public void onDrop(org.bukkit.event.player.PlayerDropItemEvent e) {
invalidateOwnerCache(e.getPlayer());
}
@EventHandler
public void onHeld(org.bukkit.event.player.PlayerItemHeldEvent e) {
invalidateOwnerCache(e.getPlayer());
}
public void hideAll() {
for (var e : new HashMap<>(bars).entrySet()) {
@@ -153,6 +211,9 @@ public class TensionManager implements Listener {
for (var pl : org.bukkit.Bukkit.getOnlinePlayers()) bar.removeViewer(pl);
}
}
bars.clear();
blockStart.clear();
lastDodge.clear();
}
@EventHandler