feat: killer/survivor win conditions with title announcements, fix forcestart reset bug
This commit is contained in:
parent
78fc8bfb55
commit
d59b965407
4 changed files with 95 additions and 24 deletions
|
|
@ -30,7 +30,8 @@ public class GameSession {
|
||||||
private RoleAssigner roleAssigner;
|
private RoleAssigner roleAssigner;
|
||||||
private GeneratorManager generatorManager;
|
private GeneratorManager generatorManager;
|
||||||
|
|
||||||
// Optional managers set by GameplayPhase
|
private boolean forceStarted = false;
|
||||||
|
|
||||||
private PalletManager palletManager;
|
private PalletManager palletManager;
|
||||||
private HookManager hookManager;
|
private HookManager hookManager;
|
||||||
private GateManager gateManager;
|
private GateManager gateManager;
|
||||||
|
|
@ -85,7 +86,9 @@ public class GameSession {
|
||||||
|
|
||||||
private Phase determineNextPhase() {
|
private Phase determineNextPhase() {
|
||||||
if (currentPhase instanceof LobbyPhase) {
|
if (currentPhase instanceof LobbyPhase) {
|
||||||
return new RoleAssignPhase(roleAssigner);
|
boolean skip = forceStarted;
|
||||||
|
forceStarted = false;
|
||||||
|
return new RoleAssignPhase(roleAssigner, skip);
|
||||||
}
|
}
|
||||||
if (currentPhase instanceof RoleAssignPhase) {
|
if (currentPhase instanceof RoleAssignPhase) {
|
||||||
return new GameplayPhase(generatorManager);
|
return new GameplayPhase(generatorManager);
|
||||||
|
|
@ -109,7 +112,6 @@ public class GameSession {
|
||||||
eventBus.emit(new PlayerJoinEvent(player));
|
eventBus.emit(new PlayerJoinEvent(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Replaces an existing player entry with a new role-assigned instance (Killer/Survivor). */
|
|
||||||
public void replacePlayer(UUID uuid, BasePlayer newPlayer) {
|
public void replacePlayer(UUID uuid, BasePlayer newPlayer) {
|
||||||
players.put(uuid, newPlayer);
|
players.put(uuid, newPlayer);
|
||||||
}
|
}
|
||||||
|
|
@ -154,10 +156,6 @@ public class GameSession {
|
||||||
return generatorManager;
|
return generatorManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Gameplay-phase managers (set by GameplayPhase.enter())
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
public PalletManager getPalletManager() { return palletManager; }
|
public PalletManager getPalletManager() { return palletManager; }
|
||||||
public void setPalletManager(PalletManager pm) { this.palletManager = pm; }
|
public void setPalletManager(PalletManager pm) { this.palletManager = pm; }
|
||||||
|
|
||||||
|
|
@ -169,6 +167,7 @@ public class GameSession {
|
||||||
|
|
||||||
public void forceStart() {
|
public void forceStart() {
|
||||||
if (currentPhase instanceof LobbyPhase lobby) {
|
if (currentPhase instanceof LobbyPhase lobby) {
|
||||||
|
forceStarted = true;
|
||||||
lobby.forceStart();
|
lobby.forceStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +181,6 @@ public class GameSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyPlayerJoined(org.bukkit.entity.Player player) {
|
public void notifyPlayerJoined(org.bukkit.entity.Player player) {
|
||||||
// Called when player joins from command, updates UI
|
|
||||||
if (currentPhase != null) {
|
if (currentPhase != null) {
|
||||||
currentPhase.onPlayerJoined(this, player);
|
currentPhase.onPlayerJoined(this, player);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,16 +209,75 @@ public class GameplayPhase implements Phase {
|
||||||
PlayerState st = bp.getState();
|
PlayerState st = bp.getState();
|
||||||
if (st == PlayerState.ESCAPED) {
|
if (st == PlayerState.ESCAPED) {
|
||||||
escapedSurvivors++;
|
escapedSurvivors++;
|
||||||
} else if (st != PlayerState.DEAD && st != PlayerState.HOOKED) {
|
} else if (st == PlayerState.DEAD) {
|
||||||
|
// dead, counted below
|
||||||
|
} else {
|
||||||
|
// HEALTHY, INJURED, DOWNED, CARRIED, HOOKED — still in game
|
||||||
aliveSurvivors++;
|
aliveSurvivors++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean allDead = (aliveSurvivors == 0 && escapedSurvivors == 0 && totalSurvivors > 0);
|
if (totalSurvivors == 0) return; // no survivors registered yet
|
||||||
boolean allEscaped = (escapedSurvivors == totalSurvivors && totalSurvivors > 0);
|
|
||||||
|
|
||||||
if (allDead || allEscaped) {
|
boolean killerWin = (aliveSurvivors == 0 && escapedSurvivors == 0);
|
||||||
|
boolean survivorsWin = (escapedSurvivors == totalSurvivors);
|
||||||
|
|
||||||
|
if (killerWin) {
|
||||||
|
announceKillerWin(session);
|
||||||
shouldEnd = true;
|
shouldEnd = true;
|
||||||
|
} else if (survivorsWin) {
|
||||||
|
announceSurvivorWin(session);
|
||||||
|
shouldEnd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void announceKillerWin(GameSession session) {
|
||||||
|
Bukkit.broadcast(Component.text("", NamedTextColor.RED));
|
||||||
|
Bukkit.broadcast(Component.text("☠ КИЛЛЕР ПОБЕДИЛ! ☠", NamedTextColor.DARK_RED));
|
||||||
|
Bukkit.broadcast(Component.text("Все выжившие уничтожены.", NamedTextColor.RED));
|
||||||
|
Bukkit.broadcast(Component.text("", NamedTextColor.RED));
|
||||||
|
|
||||||
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
Player p = bp.getPlayer();
|
||||||
|
if (p == null || !p.isOnline()) continue;
|
||||||
|
if (bp.isKiller()) {
|
||||||
|
p.showTitle(net.kyori.adventure.title.Title.title(
|
||||||
|
Component.text("ПОБЕДА!", net.kyori.adventure.text.format.TextColor.color(0xFF2222)),
|
||||||
|
Component.text("Ты уничтожил всех выживших", NamedTextColor.RED)
|
||||||
|
));
|
||||||
|
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.0f);
|
||||||
|
} else {
|
||||||
|
p.showTitle(net.kyori.adventure.title.Title.title(
|
||||||
|
Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)),
|
||||||
|
Component.text("Киллер победил", NamedTextColor.GRAY)
|
||||||
|
));
|
||||||
|
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void announceSurvivorWin(GameSession session) {
|
||||||
|
Bukkit.broadcast(Component.text("", NamedTextColor.GREEN));
|
||||||
|
Bukkit.broadcast(Component.text("✔ ВЫЖИВШИЕ ПОБЕДИЛИ! ✔", NamedTextColor.GREEN));
|
||||||
|
Bukkit.broadcast(Component.text("Все выжившие сбежали!", NamedTextColor.YELLOW));
|
||||||
|
Bukkit.broadcast(Component.text("", NamedTextColor.GREEN));
|
||||||
|
|
||||||
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
Player p = bp.getPlayer();
|
||||||
|
if (p == null || !p.isOnline()) continue;
|
||||||
|
if (!bp.isKiller()) {
|
||||||
|
p.showTitle(net.kyori.adventure.title.Title.title(
|
||||||
|
Component.text("ПОБЕГ!", net.kyori.adventure.text.format.TextColor.color(0x00FF00)),
|
||||||
|
Component.text("Вы выбрались живыми!", NamedTextColor.YELLOW)
|
||||||
|
));
|
||||||
|
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.2f);
|
||||||
|
} else {
|
||||||
|
p.showTitle(net.kyori.adventure.title.Title.title(
|
||||||
|
Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)),
|
||||||
|
Component.text("Все выжившие сбежали", NamedTextColor.GRAY)
|
||||||
|
));
|
||||||
|
p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,19 @@ public class LobbyPhase implements Phase {
|
||||||
private int countdown;
|
private int countdown;
|
||||||
private int secondsLeft;
|
private int secondsLeft;
|
||||||
private boolean started;
|
private boolean started;
|
||||||
|
private boolean forced;
|
||||||
|
|
||||||
public LobbyPhase(GameConfig config) {
|
public LobbyPhase(GameConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.secondsLeft = config.getLobbyCountdown();
|
this.secondsLeft = config.getLobbyCountdown();
|
||||||
this.countdown = 0;
|
this.countdown = 0;
|
||||||
this.started = false;
|
this.started = false;
|
||||||
|
this.forced = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enter(GameSession session) {
|
public void enter(GameSession session) {
|
||||||
Bukkit.broadcast(Component.text("[DBD] Лобби открыто! Используйте /dbd join", NamedTextColor.GREEN));
|
Bukkit.broadcast(Component.text("[DBD] Лобби открыто! Используйте /dbd join", NamedTextColor.GREEN));
|
||||||
// Teleport already-in-session players to lobby (e.g. on restart)
|
|
||||||
var lobbyManager = session.getPlugin().getLobbyManager();
|
var lobbyManager = session.getPlugin().getLobbyManager();
|
||||||
for (var player : session.getPlayers().values()) {
|
for (var player : session.getPlayers().values()) {
|
||||||
var bukkitPlayer = player.getPlayer();
|
var bukkitPlayer = player.getPlayer();
|
||||||
|
|
@ -36,14 +37,17 @@ public class LobbyPhase implements Phase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(GameSession session) {
|
public void tick(GameSession session) {
|
||||||
|
if (forced) {
|
||||||
|
started = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int playerCount = session.getPlayerCount();
|
int playerCount = session.getPlayerCount();
|
||||||
int minPlayers = config.getMinPlayers();
|
int minPlayers = config.getMinPlayers();
|
||||||
|
|
||||||
if (playerCount < minPlayers) {
|
if (playerCount < minPlayers) {
|
||||||
secondsLeft = config.getLobbyCountdown();
|
secondsLeft = config.getLobbyCountdown();
|
||||||
countdown = 0;
|
countdown = 0;
|
||||||
started = false;
|
|
||||||
// Show waiting message in action bar to all players in lobby
|
|
||||||
sendActionBarToAll(session,
|
sendActionBarToAll(session,
|
||||||
Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers, NamedTextColor.YELLOW));
|
Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers, NamedTextColor.YELLOW));
|
||||||
return;
|
return;
|
||||||
|
|
@ -55,13 +59,11 @@ public class LobbyPhase implements Phase {
|
||||||
secondsLeft--;
|
secondsLeft--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action bar: показываем каждый тик (обновляется плавно)
|
|
||||||
String bar = buildCountdownBar(secondsLeft, config.getLobbyCountdown());
|
String bar = buildCountdownBar(secondsLeft, config.getLobbyCountdown());
|
||||||
sendActionBarToAll(session,
|
sendActionBarToAll(session,
|
||||||
Component.text("▶ Старт через: " + secondsLeft + "с " + bar +
|
Component.text("▶ Старт через: " + secondsLeft + "с " + bar +
|
||||||
" (" + playerCount + "/" + minPlayers + ")", NamedTextColor.GREEN));
|
" (" + playerCount + "/" + minPlayers + ")", NamedTextColor.GREEN));
|
||||||
|
|
||||||
// Chat-announce на ключевых отметках
|
|
||||||
if (countdown == 0 && (secondsLeft <= 5 || secondsLeft == 10 || secondsLeft == 30)) {
|
if (countdown == 0 && (secondsLeft <= 5 || secondsLeft == 10 || secondsLeft == 30)) {
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(Component.text(
|
||||||
"[DBD] До начала: " + secondsLeft + " сек.", NamedTextColor.YELLOW));
|
"[DBD] До начала: " + secondsLeft + " сек.", NamedTextColor.YELLOW));
|
||||||
|
|
@ -89,7 +91,6 @@ public class LobbyPhase implements Phase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit(GameSession session) {
|
public void exit(GameSession session) {
|
||||||
// Clear action bar for all
|
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
|
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
|
||||||
|
|
@ -113,8 +114,7 @@ public class LobbyPhase implements Phase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceStart() {
|
public void forceStart() {
|
||||||
secondsLeft = 0;
|
this.forced = true;
|
||||||
started = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSecondsLeft() {
|
public int getSecondsLeft() {
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,34 @@ public class RoleAssignPhase implements Phase {
|
||||||
private final RoleAssigner assigner;
|
private final RoleAssigner assigner;
|
||||||
private int ticksLeft;
|
private int ticksLeft;
|
||||||
private boolean complete;
|
private boolean complete;
|
||||||
|
private final boolean skipDelay;
|
||||||
|
|
||||||
public RoleAssignPhase(RoleAssigner assigner) {
|
public RoleAssignPhase(RoleAssigner assigner) {
|
||||||
|
this(assigner, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleAssignPhase(RoleAssigner assigner, boolean skipDelay) {
|
||||||
this.assigner = assigner;
|
this.assigner = assigner;
|
||||||
this.complete = false;
|
this.complete = false;
|
||||||
|
this.skipDelay = skipDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enter(GameSession session) {
|
public void enter(GameSession session) {
|
||||||
ticksLeft = session.getConfig().getRoleAssignDelay() * 20;
|
if (skipDelay) {
|
||||||
|
ticksLeft = 0;
|
||||||
|
} else {
|
||||||
|
ticksLeft = session.getConfig().getRoleAssignDelay() * 20;
|
||||||
|
}
|
||||||
assigner.assignRoles(session);
|
assigner.assignRoles(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(GameSession session) {
|
public void tick(GameSession session) {
|
||||||
|
if (skipDelay) {
|
||||||
|
complete = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
ticksLeft--;
|
ticksLeft--;
|
||||||
if (ticksLeft <= 0) {
|
if (ticksLeft <= 0) {
|
||||||
complete = true;
|
complete = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue