feat(gameplay): implement hook sabotage, generator kick, jump listener, and boss bar UI

- Add HookSabotage system for survivors to disable hooks temporarily
- Add GeneratorKick killer ability to regress generator progress
- Add PlayerJumpListener to track survivor jump actions
- Add BossBarManager for displaying game state and objectives
- Update generator regression logic to support killer kick mechanic
- Update KillerController to integrate generator kick ability
- Update PlayerInteractListener to handle new interaction types
- Refactor game phases to use BossBarManager for UI updates
- Update .gitignore to exclude compiled bin/ directory
- Simplify PLAN.md documentation for easier maintenance
This commit is contained in:
loki5512344 2026-06-04 12:11:04 +02:00
parent 3d6204cc83
commit 89dc9eb00d
78 changed files with 2293 additions and 1210 deletions

View file

@ -0,0 +1,175 @@
package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.objective.Generator;
import dev.loki.objective.GeneratorManager;
import dev.loki.player.Killer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
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.HashMap;
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;
/** UUID → ongoing charging task */
private final Map<UUID, BukkitTask> chargingTasks = new HashMap<>();
/** Shared cooldown across all generators: remaining ticks */
private int globalCooldownTicks = 0;
private BukkitTask cooldownTask;
public GeneratorKick(Plugin plugin, GameConfig config, GeneratorManager generatorManager) {
this.plugin = plugin;
this.config = config;
this.generatorManager = generatorManager;
}
// -------------------------------------------------------------------------
// 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(Component.text("[DBD] Кулдаун удара: " + secsLeft + " сек", NamedTextColor.RED));
}
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;
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(Component.text("§c⚡ Разрушение: " + (int) pct + "%"));
if (elapsed >= chargeTicks) {
executeKick(killer, gen);
cancel();
}
}
}.runTaskTimer(plugin, 1L, 1L);
chargingTasks.put(uuid, task);
if (p.isOnline()) p.sendMessage(Component.text("[DBD] Держите Shift для разрушения генератора...", NamedTextColor.RED));
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; }
}
// -------------------------------------------------------------------------
// Private
// -------------------------------------------------------------------------
private void executeKick(Killer killer, Generator gen) {
chargingTasks.remove(killer.getUuid());
// Remove progress
double regPct = config.getKickRegressionPercent();
double current = gen.getProgress();
double newProg = Math.max(0, current - regPct);
gen.setProgress(newProg);
gen.setUnderRegression(true, config.getKickDuration() * 20);
Player p = killer.getPlayer();
if (p != null && p.isOnline()) {
p.sendActionBar(Component.empty());
p.sendMessage(Component.text("[DBD] Генератор поврежден! -" + (int) regPct + "% прогресса", NamedTextColor.RED));
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.6f, 1.3f);
}
// Notify all players nearby
Bukkit.broadcast(Component.text("[DBD] ⚡ Маньяк разрушил генератор!", NamedTextColor.DARK_RED));
// Start global 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);
}
}