mdbd/src/main/java/dev/loki/killer/KillerCarry.java
loki5512344 89dc9eb00d 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
2026-06-04 12:11:04 +02:00

239 lines
8.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dev.loki.killer;
import dev.loki.config.GameConfig;
import dev.loki.player.Killer;
import dev.loki.player.PlayerState;
import dev.loki.player.Survivor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Manages the Killer carrying a downed Survivor.
*
* Escape mechanic: Survivor spams SPACE (jump) to fill the escape bar.
* When the bar reaches 100% the Survivor breaks free, is teleported
* slightly forward and receives Speed I for carry.escape-speed-seconds.
*/
public class KillerCarry {
private final Plugin plugin;
private final GameConfig config;
private final Map<UUID, CarryData> activeCarries = new HashMap<>();
private BukkitTask carryTask;
// Separate map: killerUUID -> carried survivor UUID (for jump listener)
private final Map<UUID, UUID> killerToSurvivor = new HashMap<>();
private static class CarryData {
final Killer killer;
final Survivor survivor;
double wiggleProgress; // 0..100
CarryData(Killer killer, Survivor survivor) {
this.killer = killer;
this.survivor = survivor;
this.wiggleProgress = 0;
}
}
public KillerCarry(Plugin plugin, GameConfig config) {
this.plugin = plugin;
this.config = config;
}
// -------------------------------------------------------------------------
// Called by PlayerJumpListener when a survivor jumps
// -------------------------------------------------------------------------
public void onSurvivorJump(UUID survivorUuid) {
for (CarryData data : activeCarries.values()) {
if (data.survivor.getUuid().equals(survivorUuid)) {
data.wiggleProgress += config.getCarryJumpFillPercent();
showWiggleProgress(data);
if (data.wiggleProgress >= 100) {
Bukkit.getScheduler().runTask(plugin, () -> forceDrop(data.killer));
}
return;
}
}
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
public boolean tryPickup(Killer killer, Survivor survivor) {
if (!canPickup(killer, survivor)) return false;
Player killerPlayer = killer.getPlayer();
Player survivorPlayer = survivor.getPlayer();
if (killerPlayer == null || survivorPlayer == null) return false;
if (!HitValidator.isValidCarryPosition(killerPlayer, survivorPlayer)) return false;
startCarry(killer, survivor);
return true;
}
public boolean canPickup(Killer killer, Survivor survivor) {
if (killer.isCarrying()) return false;
if (survivor.getState() != PlayerState.DOWNED) return false;
Player kp = killer.getPlayer();
Player sp = survivor.getPlayer();
return kp != null && sp != null && sp.isOnline();
}
public boolean drop(Killer killer) {
CarryData data = activeCarries.remove(killer.getUuid());
killerToSurvivor.remove(killer.getUuid());
if (data == null) return false;
stopCarry(data, false);
return true;
}
public void forceDrop(Killer killer) {
CarryData data = activeCarries.remove(killer.getUuid());
killerToSurvivor.remove(killer.getUuid());
if (data == null) return;
stopCarry(data, true);
}
public boolean isCarrying(Killer killer) {
return activeCarries.containsKey(killer.getUuid());
}
public Survivor getCarried(Killer killer) {
CarryData data = activeCarries.get(killer.getUuid());
return data != null ? data.survivor : null;
}
public double getWiggleProgress(Killer killer) {
CarryData data = activeCarries.get(killer.getUuid());
return data != null ? data.wiggleProgress : 0;
}
public void shutdown() {
if (carryTask != null) { carryTask.cancel(); carryTask = null; }
for (CarryData data : new HashMap<>(activeCarries).values()) {
stopCarry(data, false);
}
activeCarries.clear();
killerToSurvivor.clear();
}
// -------------------------------------------------------------------------
// Private
// -------------------------------------------------------------------------
private void startCarry(Killer killer, Survivor survivor) {
killer.setCarrying(true);
survivor.setState(PlayerState.CARRIED);
Player survivorPlayer = survivor.getPlayer();
if (survivorPlayer != null) {
survivorPlayer.setInvisible(true);
survivorPlayer.sendMessage(Component.text(
"[DBD] Нажимайте ПРОБЕЛ чтобы вырваться!", NamedTextColor.YELLOW));
}
CarryData data = new CarryData(killer, survivor);
activeCarries.put(killer.getUuid(), data);
killerToSurvivor.put(killer.getUuid(), survivor.getUuid());
if (carryTask == null) startCarryTask();
}
private void stopCarry(CarryData data, boolean escaped) {
data.killer.setCarrying(false);
Player killerPlayer = data.killer.getPlayer();
Player survivorPlayer = data.survivor.getPlayer();
if (survivorPlayer != null && survivorPlayer.isOnline()) {
survivorPlayer.setInvisible(false);
if (escaped) {
// Drop survivor slightly forward from killer
Location dropLoc = survivorPlayer.getLocation();
if (killerPlayer != null) {
dropLoc = killerPlayer.getLocation().clone()
.add(killerPlayer.getLocation().getDirection().multiply(2.0));
}
survivorPlayer.teleport(dropLoc);
// Speed I
int speedTicks = config.getCarryEscapeSpeedSeconds() * 20;
survivorPlayer.addPotionEffect(
new PotionEffect(PotionEffectType.SPEED, speedTicks, 0, false, true));
survivorPlayer.sendMessage(Component.text(
"[DBD] Вы вырвались!", NamedTextColor.GREEN));
data.survivor.setState(PlayerState.DOWNED); // still downed, just free
} else {
survivorPlayer.teleport(data.killer.getPlayer() != null
? data.killer.getPlayer().getLocation()
: survivorPlayer.getLocation());
data.survivor.setState(PlayerState.DOWNED);
}
// Clear action bar progress
survivorPlayer.sendActionBar(Component.empty());
}
if (activeCarries.isEmpty() && carryTask != null) {
carryTask.cancel();
carryTask = null;
}
}
private void startCarryTask() {
carryTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (activeCarries.isEmpty()) return;
for (CarryData data : new HashMap<>(activeCarries).values()) {
if (!isValidCarry(data)) {
activeCarries.remove(data.killer.getUuid());
killerToSurvivor.remove(data.killer.getUuid());
stopCarry(data, false);
continue;
}
updateCarryPosition(data);
showWiggleProgress(data); // refresh bar every tick
}
}, 1L, 1L);
}
private boolean isValidCarry(CarryData data) {
Player kp = data.killer.getPlayer();
Player sp = data.survivor.getPlayer();
return kp != null && sp != null && kp.isOnline() && sp.isOnline();
}
private void updateCarryPosition(CarryData data) {
Player killerPlayer = data.killer.getPlayer();
Player survivorPlayer = data.survivor.getPlayer();
if (killerPlayer == null || survivorPlayer == null) return;
Location carryLoc = killerPlayer.getLocation().clone().add(0, -0.5, 0);
survivorPlayer.teleport(carryLoc);
}
private void showWiggleProgress(CarryData data) {
Player survivorPlayer = data.survivor.getPlayer();
if (survivorPlayer == null) return;
double pct = Math.min(100, data.wiggleProgress);
int filled = (int) (pct / 10);
int empty = 10 - filled;
String bar = "§e[" + "§a█".repeat(filled) + "§7░".repeat(empty) + "§e]";
survivorPlayer.sendActionBar(Component.text("§6Побег: " + bar + " " + (int) pct + "%"));
}
}