diff --git a/src/main/java/me/sashegdev/blackknife/MagicListener.java b/src/main/java/me/sashegdev/blackknife/MagicListener.java index 124d3b6..f0bdea6 100644 --- a/src/main/java/me/sashegdev/blackknife/MagicListener.java +++ b/src/main/java/me/sashegdev/blackknife/MagicListener.java @@ -225,17 +225,29 @@ public class MagicListener implements Listener { return dmg * 0.7; } + /** + * Забыть всё состояние игрока. Вызывается и на выходе, и на смерти. + * + *

Про lastCast: эти две карты писались на каждом применении магии и + * НИКОГДА не удалялись. Каждый игрок, хоть раз применивший магию, навсегда + * оставлял две записи — на весь срок жизни сервера. Данные там копеечные, + * но карты растут без границы, и их приходится обходить каждый тик. + */ + private void forget(UUID id) { + var task = shieldTasks.remove(id); + if (task != null) task.cancel(); + shieldEnd.remove(id); + lastCast.remove(id); + lastCastTime.remove(id); + } + @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()); + forget(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()); + forget(e.getEntity().getUniqueId()); } } diff --git a/src/main/java/me/sashegdev/blackknife/TensionManager.java b/src/main/java/me/sashegdev/blackknife/TensionManager.java index 22ca05d..b8ddd3c 100644 --- a/src/main/java/me/sashegdev/blackknife/TensionManager.java +++ b/src/main/java/me/sashegdev/blackknife/TensionManager.java @@ -212,7 +212,11 @@ public class TensionManager implements Listener { || (ent instanceof org.bukkit.entity.LivingEntity le && le.isDead()); }); final Long last = lastCombat.get(pid); - if (set.isEmpty() || (last != null && now - last > BATTLE_EXPIRE_MS)) { + // lastCombat == null означает, что бой не начинался или уже вышел из + // состояния. Множество при этом может быть непустым (противник жив), + // и тогда условие истечения по времени не проверяется вовсе — запись + // остаётся навсегда. Без боя и без отметки времени бой не бой. + if (last == null || set.isEmpty() || (now - last > BATTLE_EXPIRE_MS)) { if (drop == null) { drop = new java.util.ArrayList<>(4); } @@ -281,6 +285,14 @@ public class TensionManager implements Listener { tp.remove(id); lastCombat.remove(id); lastPct.remove(id); + // Утечка: battleEnemies на выходе не чистился. Расчёт на то, что запись + // исчезнет сама через BATTLE_EXPIRE_MS, но этот путь требует непустого + // lastCombat — а его мы только что удалили строкой выше. В итоге запись + // могла жить вечно: пока в бою есть противник, который не умирает + // (босс на ферме, запертый моб), множество не пустеет, lastCombat равен + // null, и условие истечения не проверяется. Дальше tickBattle обходит + // эту запись каждый тик до конца жизни сервера. + battleEnemies.remove(id); AbilityCooldown.clearAll(e.getPlayer()); }