diff --git a/src/main/java/me/sashegdev/blackknife/AttackTpListener.java b/src/main/java/me/sashegdev/blackknife/AttackTpListener.java index a65398d..0664115 100644 --- a/src/main/java/me/sashegdev/blackknife/AttackTpListener.java +++ b/src/main/java/me/sashegdev/blackknife/AttackTpListener.java @@ -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)); } } diff --git a/src/main/java/me/sashegdev/blackknife/BlackKnifeListener.java b/src/main/java/me/sashegdev/blackknife/BlackKnifeListener.java index 7039b62..6a54e2a 100644 --- a/src/main/java/me/sashegdev/blackknife/BlackKnifeListener.java +++ b/src/main/java/me/sashegdev/blackknife/BlackKnifeListener.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java b/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java index 2505183..b663fa8 100644 --- a/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java +++ b/src/main/java/me/sashegdev/blackknife/BlackKnifePlugin.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/DamageUtil.java b/src/main/java/me/sashegdev/blackknife/DamageUtil.java index 8690cc5..9fb3798 100644 --- a/src/main/java/me/sashegdev/blackknife/DamageUtil.java +++ b/src/main/java/me/sashegdev/blackknife/DamageUtil.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/DiamondAttack.java b/src/main/java/me/sashegdev/blackknife/DiamondAttack.java index 382523e..f0222a2 100644 --- a/src/main/java/me/sashegdev/blackknife/DiamondAttack.java +++ b/src/main/java/me/sashegdev/blackknife/DiamondAttack.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/DiamondTripleAttack.java b/src/main/java/me/sashegdev/blackknife/DiamondTripleAttack.java index a00dd00..602531d 100644 --- a/src/main/java/me/sashegdev/blackknife/DiamondTripleAttack.java +++ b/src/main/java/me/sashegdev/blackknife/DiamondTripleAttack.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/EntityTracker.java b/src/main/java/me/sashegdev/blackknife/EntityTracker.java index 36d9055..4abc4fb 100644 --- a/src/main/java/me/sashegdev/blackknife/EntityTracker.java +++ b/src/main/java/me/sashegdev/blackknife/EntityTracker.java @@ -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)) { diff --git a/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java b/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java index eaa429e..260d800 100644 --- a/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java +++ b/src/main/java/me/sashegdev/blackknife/KillMessageHandler.java @@ -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() { diff --git a/src/main/java/me/sashegdev/blackknife/KnockedHandler.java b/src/main/java/me/sashegdev/blackknife/KnockedHandler.java deleted file mode 100644 index 64dc874..0000000 --- a/src/main/java/me/sashegdev/blackknife/KnockedHandler.java +++ /dev/null @@ -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 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()); - } -} diff --git a/src/main/java/me/sashegdev/blackknife/LungeAttack.java b/src/main/java/me/sashegdev/blackknife/LungeAttack.java index a1d5c4f..e2fbd57 100644 --- a/src/main/java/me/sashegdev/blackknife/LungeAttack.java +++ b/src/main/java/me/sashegdev/blackknife/LungeAttack.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/MagicListener.java b/src/main/java/me/sashegdev/blackknife/MagicListener.java index 8da79ab..d03f70d 100644 --- a/src/main/java/me/sashegdev/blackknife/MagicListener.java +++ b/src/main/java/me/sashegdev/blackknife/MagicListener.java @@ -28,9 +28,24 @@ public class MagicListener implements Listener { private final MagicManager mgr; private final Map shieldEnd = new HashMap<>(); private final Map shieldTasks = new HashMap<>(); + private final Map lastCast = new HashMap<>(); + private final Map 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()); + } } diff --git a/src/main/java/me/sashegdev/blackknife/MagicManager.java b/src/main/java/me/sashegdev/blackknife/MagicManager.java index a90fba9..48fd47b 100644 --- a/src/main/java/me/sashegdev/blackknife/MagicManager.java +++ b/src/main/java/me/sashegdev/blackknife/MagicManager.java @@ -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 map = new HashMap<>(); + private final Map 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 common; + final java.util.List rare; + final java.util.List 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); } } diff --git a/src/main/java/me/sashegdev/blackknife/ParryHandler.java b/src/main/java/me/sashegdev/blackknife/ParryHandler.java index 19f9f7f..23eba10 100644 --- a/src/main/java/me/sashegdev/blackknife/ParryHandler.java +++ b/src/main/java/me/sashegdev/blackknife/ParryHandler.java @@ -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; diff --git a/src/main/java/me/sashegdev/blackknife/PhantomBarrageAttack.java b/src/main/java/me/sashegdev/blackknife/PhantomBarrageAttack.java index 3749da8..06b836c 100644 --- a/src/main/java/me/sashegdev/blackknife/PhantomBarrageAttack.java +++ b/src/main/java/me/sashegdev/blackknife/PhantomBarrageAttack.java @@ -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++; } diff --git a/src/main/java/me/sashegdev/blackknife/SwoonHandler.java b/src/main/java/me/sashegdev/blackknife/SwoonHandler.java index d61fa28..6bbd6fd 100644 --- a/src/main/java/me/sashegdev/blackknife/SwoonHandler.java +++ b/src/main/java/me/sashegdev/blackknife/SwoonHandler.java @@ -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) { diff --git a/src/main/java/me/sashegdev/blackknife/SwordAttack.java b/src/main/java/me/sashegdev/blackknife/SwordAttack.java index fd4ff0f..95299d7 100644 --- a/src/main/java/me/sashegdev/blackknife/SwordAttack.java +++ b/src/main/java/me/sashegdev/blackknife/SwordAttack.java @@ -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); diff --git a/src/main/java/me/sashegdev/blackknife/TensionManager.java b/src/main/java/me/sashegdev/blackknife/TensionManager.java index ac40ef8..ca34bc6 100644 --- a/src/main/java/me/sashegdev/blackknife/TensionManager.java +++ b/src/main/java/me/sashegdev/blackknife/TensionManager.java @@ -20,6 +20,20 @@ public class TensionManager implements Listener { private final Map blockStart = new HashMap<>(); private final Map lastCombat = new HashMap<>(); private final Map> battleEnemies = new HashMap<>(); + private final Map lastDodge = new HashMap<>(); + private final Map ownerCache = new HashMap<>(); + private final Map 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