fix: 10 bug fixes — healing dead players, kick bypass, gate race, swimming ghost, regression config, dead code cleanup, gate zone detection
This commit is contained in:
parent
b666f1b0fa
commit
d2b174b94c
35 changed files with 587 additions and 773 deletions
|
|
@ -78,6 +78,15 @@ public class EndgamePhase implements Phase {
|
|||
gateManager = session.getGateManager();
|
||||
}
|
||||
|
||||
// Если ворота уже открыты — игра завершилась по таймеру эндгейма,
|
||||
// показываем результаты сразу
|
||||
if (gateManager != null && gateManager.getOpenGateCount() > 0) {
|
||||
this.complete = true;
|
||||
announceEndgameEnd(session);
|
||||
teleportAllToLobby(session);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up sidebar
|
||||
sidebar = new Sidebar();
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
|
|
@ -101,7 +110,6 @@ public class EndgamePhase implements Phase {
|
|||
}
|
||||
}
|
||||
|
||||
updateGates(session);
|
||||
updateHatch(session);
|
||||
checkWinConditions(session);
|
||||
}
|
||||
|
|
@ -123,6 +131,10 @@ public class EndgamePhase implements Phase {
|
|||
hatch.reset();
|
||||
}
|
||||
|
||||
if (gateManager != null) {
|
||||
gateManager.resetAll();
|
||||
}
|
||||
|
||||
// exit() НЕ вызывает session.stop() — это делает transitionToNextPhase()
|
||||
// Телепорт и результаты уже обработаны в enter() или будут в teleportAllToLobby
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public class GameplayDamageSystem {
|
|||
private Listener vanillaDamageBlocker;
|
||||
private ItemUseListener itemUseListener;
|
||||
private PlayerJumpListener jumpListener;
|
||||
private org.bukkit.scheduler.BukkitTask swimmingTask;
|
||||
|
||||
/**
|
||||
* Initialize damage and combat system.
|
||||
|
|
@ -83,18 +84,21 @@ public class GameplayDamageSystem {
|
|||
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.5f);
|
||||
p.setHealth(20.0);
|
||||
p.setSwimming(true);
|
||||
Bukkit.getScheduler().runTaskTimer(plugin, task -> {
|
||||
if (!p.isOnline()) {
|
||||
task.cancel();
|
||||
return;
|
||||
swimmingTask = new org.bukkit.scheduler.BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!p.isOnline()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (survivor.getState() != PlayerState.DOWNED) {
|
||||
p.setSwimming(false);
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (!p.isSwimming()) p.setSwimming(true);
|
||||
}
|
||||
if (survivor.getState() != PlayerState.DOWNED) {
|
||||
p.setSwimming(false);
|
||||
task.cancel();
|
||||
return;
|
||||
}
|
||||
if (!p.isSwimming()) p.setSwimming(true);
|
||||
}, 1L, 5L);
|
||||
}.runTaskTimer(plugin, 1L, 5L);
|
||||
});
|
||||
|
||||
killerController = new KillerController(plugin, playerManager, damageHandler, cfg);
|
||||
|
|
@ -138,6 +142,10 @@ public class GameplayDamageSystem {
|
|||
* Clear all damage system resources.
|
||||
*/
|
||||
public void clear() {
|
||||
if (swimmingTask != null) {
|
||||
swimmingTask.cancel();
|
||||
swimmingTask = null;
|
||||
}
|
||||
if (jumpListener != null) {
|
||||
jumpListener.clearInGamePlayers();
|
||||
jumpListener.unregister();
|
||||
|
|
@ -156,7 +164,15 @@ public class GameplayDamageSystem {
|
|||
*/
|
||||
public void clearManagers() {
|
||||
if (killerController != null) killerController.shutdown();
|
||||
if (playerManager != null) playerManager.clear();
|
||||
if (playerManager != null) {
|
||||
for (BasePlayer bp : playerManager.getAllPlayers()) {
|
||||
Player p = bp.getPlayer();
|
||||
if (p != null && p.isOnline()) {
|
||||
p.setSwimming(false);
|
||||
}
|
||||
}
|
||||
playerManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import dev.loki.config.GameConfig;
|
|||
import dev.loki.core.GameSession;
|
||||
import dev.loki.hook.*;
|
||||
import dev.loki.player.BasePlayer;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
|
|
@ -18,6 +20,10 @@ public class GameplayHookHandler {
|
|||
private final HookSacrifice hookSacrifice;
|
||||
private final HookStruggle hookStruggle;
|
||||
private final HookRecovery hookRecovery;
|
||||
private GameSession session;
|
||||
|
||||
private static final double CAMP_REGRESS_RANGE = 7.0;
|
||||
private static final double CAMP_STALL_RANGE = 13.0;
|
||||
|
||||
public GameplayHookHandler(HookManager hookManager, HookSacrifice hookSacrifice,
|
||||
HookStruggle hookStruggle, HookRecovery hookRecovery) {
|
||||
|
|
@ -27,12 +33,17 @@ public class GameplayHookHandler {
|
|||
this.hookRecovery = hookRecovery;
|
||||
}
|
||||
|
||||
public void setSession(GameSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
private int hookTpTick;
|
||||
|
||||
/**
|
||||
* Called every tick - checks hook stage progression and teleports hooked players back.
|
||||
* Called every tick - checks hook stage progression, killer proximity, and teleports hooked players back.
|
||||
*/
|
||||
public void tick() {
|
||||
applyKillerProximity();
|
||||
checkHookStages();
|
||||
hookTpTick++;
|
||||
if (hookTpTick >= 20) {
|
||||
|
|
@ -41,6 +52,45 @@ public class GameplayHookHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the killer is near a hook, slow or regress the hook progression.
|
||||
* < 7 blocks → regress (goes negative)
|
||||
* 7-13 blocks → stall (no progression)
|
||||
* > 13 blocks → normal
|
||||
*/
|
||||
private void applyKillerProximity() {
|
||||
if (session == null) return;
|
||||
|
||||
Location killerLoc = null;
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
if (bp.isKiller()) {
|
||||
Player p = bp.getPlayer();
|
||||
if (p != null && p.isOnline()) {
|
||||
killerLoc = p.getLocation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (killerLoc == null) return;
|
||||
|
||||
for (Hook hook : hookManager.getAllHooks()) {
|
||||
if (!hook.isOccupied()) continue;
|
||||
if (!killerLoc.getWorld().equals(hook.getLocation().getWorld())) continue;
|
||||
|
||||
double dist = killerLoc.distance(hook.getLocation());
|
||||
|
||||
if (dist < CAMP_REGRESS_RANGE) {
|
||||
// Regress: -2 per tick (faster than normal +1)
|
||||
hook.addStageProgress(-2);
|
||||
// If STAGE_1 and progress goes to 0, that's as low as it gets
|
||||
} else if (dist < CAMP_STALL_RANGE) {
|
||||
// Stall: cancel the normal +1 by adding -1
|
||||
hook.addStageProgress(-1);
|
||||
}
|
||||
// else: normal progression (Hook.tick already adds +1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teleport hooked survivors back to their hook if they've moved too far.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -110,21 +110,7 @@ public class GameplayInteraction {
|
|||
}
|
||||
|
||||
private void wireMoveListener(GameSession session) {
|
||||
if (moveListener == null) return;
|
||||
moveListener.setOnSurvivorMove((survivor, loc) -> {
|
||||
if (!allGensDone) return;
|
||||
if (survivor.getState() == PlayerState.ESCAPED) return;
|
||||
ExitGate gate = gateManager.getGateAt(loc, 3.0);
|
||||
if (gate != null && gate.isOpen()) {
|
||||
survivor.setState(PlayerState.ESCAPED);
|
||||
Player p = survivor.getPlayer();
|
||||
if (p != null) {
|
||||
p.playSound(loc, Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);
|
||||
Bukkit.broadcast(Component.text(
|
||||
"[DBD] " + p.getName() + " сбежал через ворота!", NamedTextColor.GOLD));
|
||||
}
|
||||
}
|
||||
});
|
||||
// Escape check is handled in GameplayPhase.checkEscapes() every tick during POSTGAME
|
||||
}
|
||||
|
||||
private void wireInteractListener(GameSession session) {
|
||||
|
|
@ -156,10 +142,13 @@ public class GameplayInteraction {
|
|||
Survivor carried = (Survivor) other;
|
||||
if (hookManager.tryHookSurvivor(carried, player.getLocation())) {
|
||||
killer.setCarrying(false);
|
||||
Hook nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0);
|
||||
if (nearest != null) {
|
||||
Player vp = carried.getPlayer();
|
||||
if (vp != null) vp.teleport(nearest.getLocation());
|
||||
// Only teleport if actually hooked (not dead from exceeding hook limit)
|
||||
if (carried.getState() == PlayerState.HOOKED) {
|
||||
Hook nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0);
|
||||
if (nearest != null) {
|
||||
Player vp = carried.getPlayer();
|
||||
if (vp != null) vp.teleport(nearest.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -190,10 +179,12 @@ public class GameplayInteraction {
|
|||
Survivor carried = (Survivor) other;
|
||||
if (hookManager.tryHookSurvivor(carried, player.getLocation())) {
|
||||
killer.setCarrying(false);
|
||||
Hook nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0);
|
||||
if (nearest != null) {
|
||||
Player vp = carried.getPlayer();
|
||||
if (vp != null) vp.teleport(nearest.getLocation());
|
||||
if (carried.getState() == PlayerState.HOOKED) {
|
||||
Hook nearest = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0);
|
||||
if (nearest != null) {
|
||||
Player vp = carried.getPlayer();
|
||||
if (vp != null) vp.teleport(nearest.getLocation());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import dev.loki.player.Survivor;
|
|||
import dev.loki.ui.ActionBar;
|
||||
import dev.loki.ui.TitleUI;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
|
|
@ -33,15 +35,20 @@ import org.bukkit.Sound;
|
|||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GameplayPhase implements Phase {
|
||||
|
||||
private enum EndgameState { NONE, PREGAME, POSTGAME }
|
||||
|
||||
private final GeneratorManager generatorManager;
|
||||
private int tickCounter;
|
||||
private boolean shouldEnd;
|
||||
private boolean allGensDone;
|
||||
private EndgameState endgameState = EndgameState.NONE;
|
||||
private int endgameTimer; // секунд до смены состояния
|
||||
|
||||
// Sub-handlers
|
||||
private GameplayHookHandler hookHandler;
|
||||
|
|
@ -81,6 +88,8 @@ public class GameplayPhase implements Phase {
|
|||
tickCounter = 0;
|
||||
shouldEnd = false;
|
||||
allGensDone = false;
|
||||
endgameState = EndgameState.NONE;
|
||||
endgameTimer = 0;
|
||||
winConditions = new GameplayWinConditions();
|
||||
|
||||
initManagers(session, plugin, cfg);
|
||||
|
|
@ -104,7 +113,6 @@ public class GameplayPhase implements Phase {
|
|||
if (generatorRepair != null) generatorRepair.tick();
|
||||
if (interaction != null) interaction.tickSurvivors();
|
||||
hookHandler.tick();
|
||||
boolean timeExpired = ui.tickMatchTimer(session);
|
||||
|
||||
// Particle effects around generators (every 10 ticks)
|
||||
if (tickCounter % 10 == 0 && generatorManager != null) {
|
||||
|
|
@ -119,19 +127,29 @@ public class GameplayPhase implements Phase {
|
|||
}
|
||||
|
||||
if (tickCounter % 20 == 0) {
|
||||
tickEndgame(session);
|
||||
checkGeneratorProgress(session);
|
||||
winConditions.checkWinConditions(session);
|
||||
if (winConditions.isShouldEnd() || timeExpired) {
|
||||
shouldEnd = true;
|
||||
if (endgameState == EndgameState.NONE) {
|
||||
winConditions.checkWinConditions(session);
|
||||
if (winConditions.isShouldEnd()) {
|
||||
shouldEnd = true;
|
||||
}
|
||||
} else {
|
||||
// Во время эндгейма игра заканчивается когда все выжившие мертвы/сбежали
|
||||
if (allSurvivorsIncapacitated(session)) {
|
||||
shouldEnd = true;
|
||||
}
|
||||
// Или когда истёк таймер после открытия ворот
|
||||
if (endgameState == EndgameState.POSTGAME && endgameTimer <= 0) {
|
||||
shouldEnd = true;
|
||||
}
|
||||
}
|
||||
ui.updateSidebar(session, generatorManager);
|
||||
}
|
||||
|
||||
if (timeExpired) {
|
||||
showGameResults(session);
|
||||
teleportAllToLobby(session);
|
||||
session.stop();
|
||||
return;
|
||||
// Escape check — каждый тик для мгновенной реакции
|
||||
if (endgameState == EndgameState.POSTGAME) {
|
||||
checkEscapes(session);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +211,7 @@ public class GameplayPhase implements Phase {
|
|||
|
||||
hookRescue = new HookRescue(plugin, ab, cfg);
|
||||
hookHandler = new GameplayHookHandler(hookManager, hookSacrifice, hookStruggle, hookRecovery);
|
||||
hookHandler.setSession(session);
|
||||
hookRescue.setOnRescueComplete((rescuer, victim) -> hookHandler.onRescued(victim));
|
||||
|
||||
chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations(), cfg);
|
||||
|
|
@ -267,12 +286,30 @@ public class GameplayPhase implements Phase {
|
|||
// Tick helpers
|
||||
// =========================================================================
|
||||
|
||||
/** Все выжившие мертвы или сбежали? */
|
||||
private boolean allSurvivorsIncapacitated(GameSession session) {
|
||||
boolean hasSurvivor = false;
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
if (bp.isKiller()) continue;
|
||||
hasSurvivor = true;
|
||||
PlayerState st = bp.getState();
|
||||
if (st != PlayerState.DEAD && st != PlayerState.ESCAPED
|
||||
&& st != PlayerState.DOWNED && st != PlayerState.HOOKED
|
||||
&& st != PlayerState.CARRIED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return hasSurvivor;
|
||||
}
|
||||
|
||||
private void checkGeneratorProgress(GameSession session) {
|
||||
if (allGensDone) return;
|
||||
if (generatorManager.areAllRequiredCompleted()) {
|
||||
allGensDone = true;
|
||||
interaction.setAllGensDone(true);
|
||||
gateManager.activateEndgame();
|
||||
endgameState = EndgameState.PREGAME;
|
||||
endgameTimer = session.getConfig().getPregameSeconds();
|
||||
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("generator.repair.all-done"));
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
Player p = bp.getPlayer();
|
||||
|
|
@ -281,6 +318,63 @@ public class GameplayPhase implements Phase {
|
|||
}
|
||||
}
|
||||
|
||||
private void tickEndgame(GameSession session) {
|
||||
if (endgameState == EndgameState.NONE) return;
|
||||
|
||||
endgameTimer--;
|
||||
|
||||
if (endgameState == EndgameState.PREGAME) {
|
||||
// Каждую секунду шлём сообщение о начале эндгейма
|
||||
if (endgameTimer > 0) {
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
Player p = bp.getPlayer();
|
||||
if (p == null) continue;
|
||||
p.sendActionBar(net.kyori.adventure.text.Component.text(
|
||||
"§c⚡ Эндгейм! Ворота откроются через §e" + endgameTimer + "с"));
|
||||
}
|
||||
} else {
|
||||
// Таймер истёк — открываем ворота
|
||||
gateManager.openAllGates();
|
||||
endgameState = EndgameState.POSTGAME;
|
||||
endgameTimer = session.getConfig().getPostgameSeconds();
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
Player p = bp.getPlayer();
|
||||
if (p == null) continue;
|
||||
Title title = Title.title(
|
||||
Component.text("ВОРОТА ОТКРЫТЫ!").color(NamedTextColor.GREEN),
|
||||
Component.text("Бегите к выходу! (" + endgameTimer + "с)").color(NamedTextColor.YELLOW),
|
||||
Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))
|
||||
);
|
||||
p.showTitle(title);
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_IRON_DOOR_OPEN, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
} else if (endgameState == EndgameState.POSTGAME) {
|
||||
if (endgameTimer > 0 && endgameTimer % 10 == 0 && endgameTimer <= 30) {
|
||||
Bukkit.broadcast(Component.text("§c⚠ До конца игры: §e" + endgameTimer + "с"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEscapes(GameSession session) {
|
||||
double escapeRange = session.getConfig().getEscapeRange();
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
if (bp.isKiller()) continue;
|
||||
if (bp.getState() == PlayerState.ESCAPED) continue;
|
||||
if (bp.getState() != PlayerState.HEALTHY && bp.getState() != PlayerState.INJURED) continue;
|
||||
Survivor survivor = (Survivor) bp;
|
||||
Player p = survivor.getPlayer();
|
||||
if (p == null || !p.isOnline()) continue;
|
||||
ExitGate gate = gateManager.getGateAt(p.getLocation(), escapeRange);
|
||||
if (gate != null && gate.isOpen()) {
|
||||
survivor.setState(PlayerState.ESCAPED);
|
||||
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);
|
||||
Bukkit.broadcast(Component.text(
|
||||
"§6[DBD] §a" + p.getName() + " §6сбежал через ворота!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnGameBlocks(mdbd plugin, GameConfig cfg) {
|
||||
// Спавн блоков на местах генераторов
|
||||
Material genMat = Material.matchMaterial(cfg.getGeneratorBlock());
|
||||
|
|
@ -333,35 +427,6 @@ public class GameplayPhase implements Phase {
|
|||
// Cleanup
|
||||
// =========================================================================
|
||||
|
||||
private void showGameResults(GameSession session) {
|
||||
List<String> results = new ArrayList<>();
|
||||
results.add("§6§l=== РЕЗУЛЬТАТЫ ===");
|
||||
int escaped = 0, died = 0;
|
||||
for (BasePlayer player : session.getPlayers().values()) {
|
||||
if (player.isKiller()) {
|
||||
Player p = player.getPlayer();
|
||||
String name = p != null ? p.getName() : "Unknown";
|
||||
results.add("§c☠ " + name + " — Маньяк");
|
||||
continue;
|
||||
}
|
||||
Player p = player.getPlayer();
|
||||
String name = p != null ? p.getName() : "Unknown";
|
||||
if (player.getState() == PlayerState.ESCAPED) {
|
||||
results.add("§a✔ " + name + " — Сбежал");
|
||||
escaped++;
|
||||
} else {
|
||||
results.add("§c✘ " + name + " — Погиб");
|
||||
died++;
|
||||
}
|
||||
}
|
||||
results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died);
|
||||
String msg = String.join("\n", results);
|
||||
for (BasePlayer player : session.getPlayers().values()) {
|
||||
Player p = player.getPlayer();
|
||||
if (p != null) p.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void teleportAllToLobby(GameSession session) {
|
||||
for (BasePlayer bp : session.getPlayers().values()) {
|
||||
Player p = bp.getPlayer();
|
||||
|
|
@ -395,12 +460,11 @@ public class GameplayPhase implements Phase {
|
|||
if (hookStruggle != null) hookStruggle.clear();
|
||||
if (chestManager != null) chestManager.stopLootResetTimer();
|
||||
if (chestHologram != null) chestHologram.stop();
|
||||
// Clean up gate blocks
|
||||
// Clean up gate blocks — don't reset gate state (EndgamePhase needs to know gates were open)
|
||||
if (gateManager != null) {
|
||||
for (ExitGate gate : gateManager.getAllGates()) {
|
||||
gate.removeGateBlock();
|
||||
}
|
||||
gateManager.resetAll();
|
||||
}
|
||||
session.getPlugin().getInteractListener().setGeneratorManager(null);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue