mdbd/src/main/java/dev/loki/survivor/SurvivorHealing.java
2026-06-10 23:00:12 +02:00

196 lines
7.2 KiB
Java

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.entity.Player;
import java.util.UUID;
import java.util.function.Function;
/**
* 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;
// Per-survivor heal counter (tracks heals used this match)
private int healsUsed = 0;
/** Lookup function to find ANY player's Survivor (for healing others). */
private Function<Player, Survivor> survivorLookup;
public void setSurvivorLookup(Function<Player, Survivor> lookup) {
this.survivorLookup = lookup;
}
public boolean hasSurvivorLookup() {
return survivorLookup != null;
}
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;
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;
PlayerState targetState = targetSurvivor.getState();
if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) return;
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
? config.getSelfHealSeconds() * 20
: config.getOtherHealSeconds() * 20;
this.healingTicks = totalHealingTicks;
sendProgressMessage();
}
public void tick() {
if (!isHealing()) return;
Player healer = controller.getPlayer();
if (healer == null || healingTarget == null) { cancelHealing(); return; }
if (!healingTarget.isOnline()) { cancelHealing(); return; }
Survivor targetSurvivor = getSurvivorFromPlayer(healingTarget);
if (targetSurvivor == null) { cancelHealing(); return; }
PlayerState targetState = targetSurvivor.getState();
if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) {
completeHealing(targetSurvivor);
return;
}
// Must keep sneaking to heal
if (!healer.isSneaking()) { cancelHealing(); return; }
// Range interrupt
if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > config.getHealRange()) {
cancelHealing();
return;
}
healingTicks--;
sendProgressMessage();
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;
}
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(
isSelfHeal ? "<gray>Лечение себя: " + bar
: "<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;
return "<green>" + "█".repeat(filled) + "<gray>" + "░".repeat(empty)
+ " <white>" + (int)(progress * 100) + "%";
}
private Survivor getSurvivorFromPlayer(Player player) {
// Self: return controller's survivor
if (controller.getPlayer() != null && controller.getPlayer().equals(player)) {
return controller.getSurvivor();
}
// Other survivor: use lookup function
if (survivorLookup != null) {
return survivorLookup.apply(player);
}
return null;
}
}