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:
parent
3d6204cc83
commit
89dc9eb00d
78 changed files with 2293 additions and 1210 deletions
|
|
@ -1,29 +1,40 @@
|
|||
package dev.loki.survivor;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handles healing another survivor (shift + proximity, 15 sec by default)
|
||||
* or self-healing with a medkit (configurable).
|
||||
* Limits: max-heals-per-match heals per survivor.
|
||||
*/
|
||||
public class SurvivorHealing {
|
||||
private final SurvivorController controller;
|
||||
private final GameConfig config;
|
||||
|
||||
private Player healingTarget;
|
||||
private int healingTicks;
|
||||
private int totalHealingTicks;
|
||||
private boolean isSelfHeal;
|
||||
|
||||
private static final int SELF_HEAL_TICKS = 600;
|
||||
private static final int OTHER_HEAL_TICKS = 320;
|
||||
private static final double HEAL_RANGE = 3.0;
|
||||
// Per-survivor heal counter (tracks heals used this match)
|
||||
private int healsUsed = 0;
|
||||
|
||||
public SurvivorHealing(SurvivorController controller) {
|
||||
public SurvivorHealing(SurvivorController controller, GameConfig config) {
|
||||
this.controller = controller;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public void startHealing(Player target) {
|
||||
if (isHealing()) return;
|
||||
if (controller.isDowned()) return;
|
||||
|
|
@ -31,6 +42,12 @@ public class SurvivorHealing {
|
|||
Player healer = controller.getPlayer();
|
||||
if (healer == null) return;
|
||||
|
||||
// Check heal limit
|
||||
if (healsUsed >= config.getMaxHealsPerMatch()) {
|
||||
healer.sendMessage(Component.text("[DBD] Лимит лечений исчерпан!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
UUID targetUuid = target.getUniqueId();
|
||||
Survivor targetSurvivor = getSurvivorFromPlayer(target);
|
||||
if (targetSurvivor == null) return;
|
||||
|
|
@ -41,10 +58,18 @@ public class SurvivorHealing {
|
|||
boolean selfHeal = healer.getUniqueId().equals(targetUuid);
|
||||
if (selfHeal && targetState == PlayerState.DOWNED) return;
|
||||
|
||||
// Range check for other-heal
|
||||
if (!selfHeal && healer.getLocation().distance(target.getLocation()) > config.getHealRange()) {
|
||||
healer.sendMessage(Component.text("[DBD] Слишком далеко!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
this.healingTarget = target;
|
||||
this.isSelfHeal = selfHeal;
|
||||
this.totalHealingTicks = selfHeal ? SELF_HEAL_TICKS : OTHER_HEAL_TICKS;
|
||||
this.healingTicks = totalHealingTicks;
|
||||
this.isSelfHeal = selfHeal;
|
||||
this.totalHealingTicks = selfHeal
|
||||
? config.getSelfHealSeconds() * 20
|
||||
: config.getOtherHealSeconds() * 20;
|
||||
this.healingTicks = totalHealingTicks;
|
||||
|
||||
sendProgressMessage();
|
||||
}
|
||||
|
|
@ -53,21 +78,11 @@ public class SurvivorHealing {
|
|||
if (!isHealing()) return;
|
||||
|
||||
Player healer = controller.getPlayer();
|
||||
if (healer == null || healingTarget == null) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!healingTarget.isOnline()) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
if (healer == null || healingTarget == null) { cancelHealing(); return; }
|
||||
if (!healingTarget.isOnline()) { cancelHealing(); return; }
|
||||
|
||||
Survivor targetSurvivor = getSurvivorFromPlayer(healingTarget);
|
||||
if (targetSurvivor == null) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
if (targetSurvivor == null) { cancelHealing(); return; }
|
||||
|
||||
PlayerState targetState = targetSurvivor.getState();
|
||||
if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) {
|
||||
|
|
@ -75,7 +90,11 @@ public class SurvivorHealing {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > HEAL_RANGE) {
|
||||
// Must keep sneaking to heal
|
||||
if (!healer.isSneaking()) { cancelHealing(); return; }
|
||||
|
||||
// Range interrupt
|
||||
if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > config.getHealRange()) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
|
@ -83,37 +102,53 @@ public class SurvivorHealing {
|
|||
healingTicks--;
|
||||
sendProgressMessage();
|
||||
|
||||
if (healingTicks <= 0) {
|
||||
completeHealing(targetSurvivor);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeHealing(Survivor target) {
|
||||
PlayerState currentState = target.getState();
|
||||
PlayerState newState = (currentState == PlayerState.DOWNED) ? PlayerState.INJURED : PlayerState.HEALTHY;
|
||||
target.setState(newState);
|
||||
|
||||
Player targetPlayer = healingTarget;
|
||||
if (targetPlayer != null && targetPlayer.isOnline()) {
|
||||
String message = newState == PlayerState.HEALTHY ? "<green>Вы полностью вылечились!"
|
||||
: "<yellow>Вы встали на ноги!";
|
||||
targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(message));
|
||||
}
|
||||
|
||||
cancelHealing();
|
||||
if (healingTicks <= 0) completeHealing(targetSurvivor);
|
||||
}
|
||||
|
||||
public void cancelHealing() {
|
||||
if (healingTarget != null && healingTarget.isOnline() && isHealing()) {
|
||||
healingTarget.sendActionBar(Component.empty());
|
||||
healingTarget.sendMessage(Component.text("[DBD] Лечение прервано!", net.kyori.adventure.text.format.NamedTextColor.YELLOW));
|
||||
}
|
||||
healingTarget = null;
|
||||
healingTicks = 0;
|
||||
healingTicks = 0;
|
||||
}
|
||||
|
||||
public boolean isHealing() { return healingTicks > 0; }
|
||||
public Player getHealingTarget() { return healingTarget; }
|
||||
public double getProgress() {
|
||||
if (!isHealing()) return 0;
|
||||
return 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void completeHealing(Survivor target) {
|
||||
PlayerState current = target.getState();
|
||||
PlayerState newState = (current == PlayerState.DOWNED) ? PlayerState.INJURED : PlayerState.HEALTHY;
|
||||
target.setState(newState);
|
||||
healsUsed++;
|
||||
|
||||
Player targetPlayer = healingTarget;
|
||||
if (targetPlayer != null && targetPlayer.isOnline()) {
|
||||
String msg = newState == PlayerState.HEALTHY
|
||||
? "<green>Вы полностью вылечились!"
|
||||
: "<yellow>Вы встали на ноги!";
|
||||
targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(msg));
|
||||
}
|
||||
|
||||
Player healer = controller.getPlayer();
|
||||
if (healer != null && !healer.getUniqueId().equals(targetPlayer != null ? targetPlayer.getUniqueId() : null)) {
|
||||
healer.sendMessage(Component.text("[DBD] Союзник вылечен!", net.kyori.adventure.text.format.NamedTextColor.GREEN));
|
||||
}
|
||||
|
||||
cancelHealing();
|
||||
}
|
||||
|
||||
private void sendProgressMessage() {
|
||||
if (healingTarget == null) return;
|
||||
|
||||
double progress = 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||
String bar = renderProgressBar(progress);
|
||||
Component message = MiniMessage.miniMessage().deserialize(
|
||||
|
|
@ -121,36 +156,27 @@ public class SurvivorHealing {
|
|||
: "<gray>Лечение союзника: " + bar
|
||||
);
|
||||
healingTarget.sendActionBar(message);
|
||||
// Also show on healer
|
||||
Player healer = controller.getPlayer();
|
||||
if (healer != null && !healer.equals(healingTarget)) {
|
||||
healer.sendActionBar(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String renderProgressBar(double progress) {
|
||||
int filled = (int) (10 * progress);
|
||||
int empty = 10 - filled;
|
||||
StringBuilder bar = new StringBuilder("<green>");
|
||||
bar.append("█".repeat(filled));
|
||||
bar.append("<gray>");
|
||||
bar.append("░".repeat(empty));
|
||||
bar.append(" <white>").append((int)(progress * 100)).append("%");
|
||||
return bar.toString();
|
||||
int filled = (int)(10 * progress);
|
||||
int empty = 10 - filled;
|
||||
return "<green>" + "█".repeat(filled) + "<gray>" + "░".repeat(empty)
|
||||
+ " <white>" + (int)(progress * 100) + "%";
|
||||
}
|
||||
|
||||
private Survivor getSurvivorFromPlayer(Player player) {
|
||||
if (controller.getPlayer() != null && controller.getPlayer().equals(player)) {
|
||||
return controller.getSurvivor();
|
||||
}
|
||||
// For healing other survivors the target won't be this controller's survivor.
|
||||
// The target state check is done via the Survivor object that must be passed in.
|
||||
// This method is used for both self and other.
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isHealing() {
|
||||
return healingTicks > 0;
|
||||
}
|
||||
|
||||
public Player getHealingTarget() {
|
||||
return healingTarget;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
if (!isHealing()) return 0;
|
||||
return 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue