package dev.loki.killer; import dev.loki.config.GameConfig; import dev.loki.config.MessagesConfig; import dev.loki.mdbd; import dev.loki.objective.Generator; import dev.loki.objective.GeneratorManager; import dev.loki.player.Killer; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** * Killer generator kick: Killer holds Shift near a generator for * kick-charge-seconds (default 3), then the generator loses * kick-regression-percent (default 20%) progress. * * A shared cooldown of kick-cooldown-seconds applies to ALL generators. */ public class GeneratorKick { private final Plugin plugin; private final GameConfig config; private final GeneratorManager generatorManager; private final MessagesConfig messages; /** UUID → ongoing charging task */ private final Map chargingTasks = new HashMap<>(); /** Shared cooldown across all generators: remaining ticks */ private int globalCooldownTicks = 0; private BukkitTask cooldownTask; /** Pending unblock tasks — cancelled on shutdown to avoid ghost tasks */ private final List pendingUnblockTasks = new ArrayList<>(); public GeneratorKick(Plugin plugin, GameConfig config, GeneratorManager generatorManager) { this.plugin = plugin; this.config = config; this.generatorManager = generatorManager; this.messages = mdbd.getInstance().getConfigManager().getMessages(); } // ------------------------------------------------------------------------- // Public API — called when killer starts/stops sneaking near a generator // ------------------------------------------------------------------------- /** Call when killer starts holding Shift near a generator. */ public boolean startCharging(Killer killer) { UUID uuid = killer.getUuid(); if (chargingTasks.containsKey(uuid)) return false; // already charging if (globalCooldownTicks > 0) { Player p = killer.getPlayer(); if (p != null) { int secsLeft = (int) Math.ceil(globalCooldownTicks / 20.0); p.sendMessage(messages.get("generator.kicked.cooldown", secsLeft)); } return false; } Player p = killer.getPlayer(); if (p == null) return false; Generator gen = generatorManager.getNearestGenerator(p.getLocation(), config.getGeneratorInteractionRange()); if (gen == null || !gen.canBeRepaired()) return false; if (gen.getProgress() <= 0) { if (p.isOnline()) { p.sendMessage(messages.get("generator.kicked.already-broken")); } return false; } int chargeTicks = config.getKickChargeSeconds() * 20; BukkitTask task = new BukkitRunnable() { int elapsed = 0; @Override public void run() { Player player = killer.getPlayer(); if (player == null || !player.isOnline() || !player.isSneaking()) { stopCharging(killer.getUuid()); cancel(); return; } // Still near the same generator? Generator g = generatorManager.getNearestGenerator(player.getLocation(), config.getGeneratorInteractionRange()); if (g == null || g != gen) { stopCharging(killer.getUuid()); cancel(); return; } elapsed++; double pct = (double) elapsed / chargeTicks * 100; player.sendActionBar(messages.get("generator.kicked.actionbar", (int) pct)); if (elapsed >= chargeTicks) { executeKick(killer, gen); cancel(); } } }.runTaskTimer(plugin, 1L, 1L); chargingTasks.put(uuid, task); if (p.isOnline()) p.sendMessage(messages.get("generator.kicked.begin")); return true; } /** Call when killer releases Shift or moves away. */ public void stopCharging(UUID killerUuid) { BukkitTask task = chargingTasks.remove(killerUuid); if (task != null) { task.cancel(); Player p = Bukkit.getPlayer(killerUuid); if (p != null && p.isOnline()) { p.sendActionBar(Component.empty()); } } } public boolean isCharging(UUID killerUuid) { return chargingTasks.containsKey(killerUuid); } public void shutdown() { for (BukkitTask t : chargingTasks.values()) t.cancel(); chargingTasks.clear(); if (cooldownTask != null) { cooldownTask.cancel(); cooldownTask = null; } for (BukkitTask t : pendingUnblockTasks) t.cancel(); pendingUnblockTasks.clear(); } // ------------------------------------------------------------------------- // Private // ------------------------------------------------------------------------- private void executeKick(Killer killer, Generator gen) { chargingTasks.remove(killer.getUuid()); // Remove 20% progress (or configured percent) and STOP — no continuous regression double regPct = config.getKickRegressionPercent(); double current = gen.getProgress(); double newProg = Math.max(0, current - regPct); gen.setProgress(newProg); // Block generator from repair for the cooldown duration gen.setBlocked(true); Player p = killer.getPlayer(); if (p != null && p.isOnline()) { p.sendActionBar(Component.empty()); p.sendMessage(messages.get("generator.kicked.success", (int) regPct)); p.getWorld().playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.6f, 1.3f); } // Notify all players Bukkit.broadcast(messages.get("generator.kicked.broadcast", (int) regPct)); // Unblock generator after kick-duration (separate from global cooldown) int unblockTicks = config.getKickDuration() * 20; BukkitTask unblockTask = plugin.getServer().getScheduler().runTaskLater(plugin, () -> { gen.setBlocked(false); }, unblockTicks); pendingUnblockTasks.add(unblockTask); // Start global kick cooldown startGlobalCooldown(); } private void startGlobalCooldown() { globalCooldownTicks = config.getKickCooldownSeconds() * 20; if (cooldownTask != null) { cooldownTask.cancel(); } cooldownTask = new BukkitRunnable() { @Override public void run() { if (globalCooldownTicks > 0) { globalCooldownTicks -= 20; } else { globalCooldownTicks = 0; cancel(); cooldownTask = null; } } }.runTaskTimer(plugin, 20L, 20L); } }