fix: 10 bugs — timer, compass, escape teleport, win conditions, results, tests
This commit is contained in:
parent
2f4de4fbb6
commit
17388f97e7
6 changed files with 113 additions and 40 deletions
|
|
@ -20,6 +20,8 @@ public class GeneratorRepair {
|
||||||
private final GeneratorManager manager;
|
private final GeneratorManager manager;
|
||||||
private final Map<UUID, RepairSession> sessions;
|
private final Map<UUID, RepairSession> sessions;
|
||||||
private final GeneratorMinigame minigame;
|
private final GeneratorMinigame minigame;
|
||||||
|
private final Map<UUID, Integer> compassTaskIds = new HashMap<>();
|
||||||
|
private final Map<UUID, ItemStack> compassPrevItems = new HashMap<>();
|
||||||
|
|
||||||
private static final double REPAIR_PER_TICK = 100.0 / 1600.0;
|
private static final double REPAIR_PER_TICK = 100.0 / 1600.0;
|
||||||
private static final double REPAIR_RANGE = 3.0;
|
private static final double REPAIR_RANGE = 3.0;
|
||||||
|
|
@ -201,14 +203,47 @@ public class GeneratorRepair {
|
||||||
compass.setItemMeta(meta);
|
compass.setItemMeta(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack prev = online.getInventory().getItemInOffHand();
|
UUID killerId = online.getUniqueId();
|
||||||
online.getInventory().setItemInOffHand(compass);
|
|
||||||
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
Integer prevTaskId = compassTaskIds.remove(killerId);
|
||||||
ItemStack current = online.getInventory().getItemInOffHand();
|
if (prevTaskId != null) {
|
||||||
if (current.getType() == Material.COMPASS) {
|
Bukkit.getScheduler().cancelTask(prevTaskId);
|
||||||
online.getInventory().setItemInOffHand(prev);
|
|
||||||
}
|
}
|
||||||
}, durationTicks);
|
|
||||||
|
compassPrevItems.put(killerId, online.getInventory().getItemInOffHand());
|
||||||
|
online.getInventory().setItemInOffHand(compass);
|
||||||
|
|
||||||
|
int taskId = Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||||
|
if (!online.isOnline()) return;
|
||||||
|
|
||||||
|
ItemStack offHand = online.getInventory().getItemInOffHand();
|
||||||
|
if (offHand.getType() == Material.COMPASS) {
|
||||||
|
online.getInventory().setItemInOffHand(compassPrevItems.get(killerId));
|
||||||
|
compassTaskIds.remove(killerId);
|
||||||
|
compassPrevItems.remove(killerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack mainHand = online.getInventory().getItemInMainHand();
|
||||||
|
if (mainHand.getType() == Material.COMPASS) {
|
||||||
|
online.getInventory().setItemInMainHand(compassPrevItems.get(killerId));
|
||||||
|
compassTaskIds.remove(killerId);
|
||||||
|
compassPrevItems.remove(killerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < online.getInventory().getSize(); i++) {
|
||||||
|
ItemStack item = online.getInventory().getItem(i);
|
||||||
|
if (item != null && item.getType() == Material.COMPASS) {
|
||||||
|
online.getInventory().setItem(i, null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compassTaskIds.remove(killerId);
|
||||||
|
compassPrevItems.remove(killerId);
|
||||||
|
}, durationTicks).getTaskId();
|
||||||
|
|
||||||
|
compassTaskIds.put(killerId, taskId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ public class EndgamePhase implements Phase {
|
||||||
private HatchCondition hatchCondition;
|
private HatchCondition hatchCondition;
|
||||||
private HatchEscape hatchEscape;
|
private HatchEscape hatchEscape;
|
||||||
private Sidebar sidebar;
|
private Sidebar sidebar;
|
||||||
|
private int endgameTimer = -1; // секунд до конца эндгейма (-1 = не активно)
|
||||||
|
|
||||||
public EndgamePhase() {
|
public EndgamePhase() {
|
||||||
this.openTicks = 20 * 20;
|
this.openTicks = 20 * 20;
|
||||||
|
|
@ -100,6 +101,15 @@ public class EndgamePhase implements Phase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(GameSession session) {
|
public void tick(GameSession session) {
|
||||||
|
updateGates(session);
|
||||||
|
|
||||||
|
if (endgameTimer > 0 && session.getTicksElapsed() % 20 == 0) {
|
||||||
|
endgameTimer--;
|
||||||
|
if (sidebar != null) {
|
||||||
|
sidebar.setEndgameTimer(endgameTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update sidebar stats every 40 ticks
|
// Update sidebar stats every 40 ticks
|
||||||
if (session.getTicksElapsed() % 40 == 0 && sidebar != null) {
|
if (session.getTicksElapsed() % 40 == 0 && sidebar != null) {
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
|
@ -198,6 +208,12 @@ public class EndgamePhase implements Phase {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
private void activateEndgame(GameSession session) {
|
private void activateEndgame(GameSession session) {
|
||||||
|
if (endgameTimer < 0) {
|
||||||
|
endgameTimer = session.getConfig().getPostgameSeconds();
|
||||||
|
}
|
||||||
|
if (sidebar != null) {
|
||||||
|
sidebar.setEndgameTimer(endgameTimer);
|
||||||
|
}
|
||||||
if (gateManager != null && !gatesActivated) {
|
if (gateManager != null && !gatesActivated) {
|
||||||
gateManager.activateEndgame();
|
gateManager.activateEndgame();
|
||||||
gatesActivated = true;
|
gatesActivated = true;
|
||||||
|
|
@ -268,26 +284,40 @@ public class EndgamePhase implements Phase {
|
||||||
}
|
}
|
||||||
if (survivorsAlive == 0) {
|
if (survivorsAlive == 0) {
|
||||||
complete = true;
|
complete = true;
|
||||||
|
announceEndgameEnd(session);
|
||||||
|
teleportAllToLobby(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void announceEndgameEnd(GameSession session) {
|
private void announceEndgameEnd(GameSession session) {
|
||||||
List<String> results = new ArrayList<>();
|
List<String> results = new ArrayList<>();
|
||||||
results.add("§6§l=== РЕЗУЛЬТАТЫ ===");
|
int escaped = 0, died = 0, sacrificed = 0, leftBehind = 0;
|
||||||
int escaped = 0, died = 0;
|
|
||||||
for (BasePlayer player : session.getPlayers().values()) {
|
for (BasePlayer player : session.getPlayers().values()) {
|
||||||
if (player.isKiller()) continue;
|
if (player.isKiller()) continue;
|
||||||
Player p = player.getPlayer();
|
Player p = player.getPlayer();
|
||||||
String name = p != null ? p.getName() : "Unknown";
|
String name = p != null ? p.getName() : "Unknown";
|
||||||
if (player.getState() == PlayerState.ESCAPED) {
|
switch (player.getState()) {
|
||||||
|
case ESCAPED:
|
||||||
results.add("§a✔ " + name + " — Сбежал");
|
results.add("§a✔ " + name + " — Сбежал");
|
||||||
escaped++;
|
escaped++;
|
||||||
} else {
|
break;
|
||||||
|
case DEAD:
|
||||||
results.add("§c✘ " + name + " — Погиб");
|
results.add("§c✘ " + name + " — Погиб");
|
||||||
died++;
|
died++;
|
||||||
|
break;
|
||||||
|
case HOOKED:
|
||||||
|
case CARRIED:
|
||||||
|
results.add("§4✘ " + name + " — Принесён в жертву");
|
||||||
|
sacrificed++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
results.add("§7✘ " + name + " — Оставлен");
|
||||||
|
leftBehind++;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died);
|
results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died
|
||||||
|
+ " §7/ В жертву: §4" + sacrificed + " §7/ Оставлено: §7" + leftBehind);
|
||||||
String msg = String.join("\n", results);
|
String msg = String.join("\n", results);
|
||||||
for (BasePlayer player : session.getPlayers().values()) {
|
for (BasePlayer player : session.getPlayers().values()) {
|
||||||
Player p = player.getPlayer();
|
Player p = player.getPlayer();
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,9 @@ public class GameplayPhase implements Phase {
|
||||||
if (generatorRepair != null) generatorRepair.tick();
|
if (generatorRepair != null) generatorRepair.tick();
|
||||||
if (interaction != null) interaction.tickSurvivors();
|
if (interaction != null) interaction.tickSurvivors();
|
||||||
hookHandler.tick();
|
hookHandler.tick();
|
||||||
|
if (ui.tickMatchTimer(session)) {
|
||||||
|
shouldEnd = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Particle effects around generators (every 10 ticks)
|
// Particle effects around generators (every 10 ticks)
|
||||||
if (tickCounter % 10 == 0 && generatorManager != null) {
|
if (tickCounter % 10 == 0 && generatorManager != null) {
|
||||||
|
|
@ -161,8 +164,6 @@ public class GameplayPhase implements Phase {
|
||||||
ui.resetPlayers(session);
|
ui.resetPlayers(session);
|
||||||
damageSystem.clear();
|
damageSystem.clear();
|
||||||
damageSystem.clearManagers();
|
damageSystem.clearManagers();
|
||||||
|
|
||||||
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.end"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -371,6 +372,9 @@ public class GameplayPhase implements Phase {
|
||||||
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);
|
p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(Component.text(
|
||||||
"§6[DBD] §a" + p.getName() + " §6сбежал через ворота!"));
|
"§6[DBD] §a" + p.getName() + " §6сбежал через ворота!"));
|
||||||
|
p.setGameMode(GameMode.SPECTATOR);
|
||||||
|
p.getInventory().clear();
|
||||||
|
session.getPlugin().getLobbyManager().teleportToLobby(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,24 +57,24 @@ public class GameplayWinConditions {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Killer wins: all survivors are downed/hooked/carried/dead
|
|
||||||
if (allSurvivorsIncapacitated(session)) {
|
|
||||||
winReason = "killer_win";
|
|
||||||
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.all-incapacitated"));
|
|
||||||
announceKillerWin(session);
|
|
||||||
shouldEnd = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Survivors win: all generators done and at least one escaped
|
// Survivors win: all generators done and at least one escaped
|
||||||
if (hasEscapedSurvivor(session)) {
|
if (hasEscapedSurvivor(session)) {
|
||||||
winReason = "survivors_win";
|
winReason = "survivors_win";
|
||||||
announceSurvivorsWin(session);
|
announceSurvivorsWin(session);
|
||||||
shouldEnd = true;
|
shouldEnd = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Killer wins: all survivors are downed/hooked/carried/dead (ESCAPED excluded)
|
||||||
|
if (allSurvivorsIncapacitated(session)) {
|
||||||
|
winReason = "killer_win";
|
||||||
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.all-incapacitated"));
|
||||||
|
announceKillerWin(session);
|
||||||
|
shouldEnd = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, DEAD, or ESCAPED). */
|
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, DEAD). */
|
||||||
private boolean allSurvivorsIncapacitated(GameSession session) {
|
private boolean allSurvivorsIncapacitated(GameSession session) {
|
||||||
boolean hasSurvivors = false;
|
boolean hasSurvivors = false;
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
|
@ -82,8 +82,7 @@ public class GameplayWinConditions {
|
||||||
hasSurvivors = true;
|
hasSurvivors = true;
|
||||||
PlayerState st = bp.getState();
|
PlayerState st = bp.getState();
|
||||||
if (st != PlayerState.DOWNED && st != PlayerState.HOOKED
|
if (st != PlayerState.DOWNED && st != PlayerState.HOOKED
|
||||||
&& st != PlayerState.CARRIED && st != PlayerState.DEAD
|
&& st != PlayerState.CARRIED && st != PlayerState.DEAD) {
|
||||||
&& st != PlayerState.ESCAPED) {
|
|
||||||
return false; // at least one survivor can still act
|
return false; // at least one survivor can still act
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +120,7 @@ public class GameplayWinConditions {
|
||||||
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()) continue;
|
if (p == null || !p.isOnline()) continue;
|
||||||
if (!bp.isKiller() && bp.getState() == PlayerState.DEAD) {
|
if (!bp.isKiller() && (bp.getState() == PlayerState.DEAD || bp.getState() == PlayerState.ESCAPED)) {
|
||||||
p.setGameMode(GameMode.SPECTATOR);
|
p.setGameMode(GameMode.SPECTATOR);
|
||||||
} else {
|
} else {
|
||||||
p.setGameMode(GameMode.SURVIVAL);
|
p.setGameMode(GameMode.SURVIVAL);
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,17 @@ class ExitGateTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void setPowered_shouldTransitionToOpening() {
|
void setPowered_shouldStayClosed() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
assertEquals(ExitGate.GateState.OPENING, gate.getState());
|
assertEquals(ExitGate.GateState.CLOSED, gate.getState());
|
||||||
assertTrue(gate.isPowered());
|
assertTrue(gate.isPowered());
|
||||||
assertTrue(gate.isOpening());
|
assertTrue(gate.isClosed());
|
||||||
assertFalse(gate.isClosed());
|
|
||||||
assertFalse(gate.isOpen());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void setProgressDirect_shouldIncreaseProgress() {
|
void setProgressDirect_shouldIncreaseProgress() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.setProgressDirect(0.5);
|
gate.setProgressDirect(0.5);
|
||||||
assertEquals(0.5, gate.getProgress());
|
assertEquals(0.5, gate.getProgress());
|
||||||
assertEquals(50, gate.getProgressPercent());
|
assertEquals(50, gate.getProgressPercent());
|
||||||
|
|
@ -47,6 +46,7 @@ class ExitGateTest {
|
||||||
@Test
|
@Test
|
||||||
void setProgressDirect_shouldCompleteWhenReachingOne() {
|
void setProgressDirect_shouldCompleteWhenReachingOne() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.setProgressDirect(1.0);
|
gate.setProgressDirect(1.0);
|
||||||
assertEquals(1.0, gate.getProgress());
|
assertEquals(1.0, gate.getProgress());
|
||||||
assertEquals(ExitGate.GateState.OPEN, gate.getState());
|
assertEquals(ExitGate.GateState.OPEN, gate.getState());
|
||||||
|
|
@ -56,6 +56,7 @@ class ExitGateTest {
|
||||||
@Test
|
@Test
|
||||||
void setProgressDirect_shouldClampToMax() {
|
void setProgressDirect_shouldClampToMax() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.setProgressDirect(2.0);
|
gate.setProgressDirect(2.0);
|
||||||
assertEquals(1.0, gate.getProgress());
|
assertEquals(1.0, gate.getProgress());
|
||||||
assertTrue(gate.isOpen());
|
assertTrue(gate.isOpen());
|
||||||
|
|
@ -64,6 +65,7 @@ class ExitGateTest {
|
||||||
@Test
|
@Test
|
||||||
void setProgressDirect_shouldNotExceedMin() {
|
void setProgressDirect_shouldNotExceedMin() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.setProgressDirect(-0.5);
|
gate.setProgressDirect(-0.5);
|
||||||
assertEquals(0.0, gate.getProgress());
|
assertEquals(0.0, gate.getProgress());
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +81,7 @@ class ExitGateTest {
|
||||||
@Test
|
@Test
|
||||||
void updateProgress_shouldWorkIncrementally() {
|
void updateProgress_shouldWorkIncrementally() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.updateProgress(0.3);
|
gate.updateProgress(0.3);
|
||||||
assertEquals(0.3, gate.getProgress());
|
assertEquals(0.3, gate.getProgress());
|
||||||
gate.updateProgress(0.3);
|
gate.updateProgress(0.3);
|
||||||
|
|
@ -101,6 +104,7 @@ class ExitGateTest {
|
||||||
@Test
|
@Test
|
||||||
void saveAndRestoreProgress_shouldPersist() {
|
void saveAndRestoreProgress_shouldPersist() {
|
||||||
gate.setPowered(true);
|
gate.setPowered(true);
|
||||||
|
gate.startOpening();
|
||||||
gate.setProgressDirect(0.4);
|
gate.setProgressDirect(0.4);
|
||||||
assertEquals(0.4, gate.getProgress());
|
assertEquals(0.4, gate.getProgress());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,14 @@ class GameplayWinConditionsTest {
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void incapacitated_withEscapedAndRestDisabled_shouldReturnTrue() {
|
void incapacitated_withEscapedAndRestDisabled_shouldReturnFalse() {
|
||||||
Map<UUID, BasePlayer> players = new HashMap<>();
|
Map<UUID, BasePlayer> players = new HashMap<>();
|
||||||
players.put(UUID.randomUUID(), onlineKiller());
|
players.put(UUID.randomUUID(), onlineKiller());
|
||||||
players.put(UUID.randomUUID(), survivorWithState(PlayerState.ESCAPED));
|
players.put(UUID.randomUUID(), survivorWithState(PlayerState.ESCAPED));
|
||||||
players.put(UUID.randomUUID(), survivorWithState(PlayerState.DOWNED));
|
players.put(UUID.randomUUID(), survivorWithState(PlayerState.DOWNED));
|
||||||
|
|
||||||
assertTrue(allIncapacitated(sessionWith(players)),
|
assertFalse(allIncapacitated(sessionWith(players)),
|
||||||
"ESCAPED survivors should be counted as incapacitated (bug #3)");
|
"ESCAPED survivors should NOT count as incapacitated for killer win");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -120,13 +120,14 @@ class GameplayWinConditionsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void incapacitated_withAllTerminalStates_shouldReturnTrue() {
|
void incapacitated_withEscapedAndDead_shouldReturnFalse() {
|
||||||
Map<UUID, BasePlayer> players = new HashMap<>();
|
Map<UUID, BasePlayer> players = new HashMap<>();
|
||||||
players.put(UUID.randomUUID(), onlineKiller());
|
players.put(UUID.randomUUID(), onlineKiller());
|
||||||
players.put(UUID.randomUUID(), survivorWithState(PlayerState.DEAD));
|
players.put(UUID.randomUUID(), survivorWithState(PlayerState.DEAD));
|
||||||
players.put(UUID.randomUUID(), survivorWithState(PlayerState.ESCAPED));
|
players.put(UUID.randomUUID(), survivorWithState(PlayerState.ESCAPED));
|
||||||
|
|
||||||
assertTrue(allIncapacitated(sessionWith(players)));
|
assertFalse(allIncapacitated(sessionWith(players)),
|
||||||
|
"ESCAPED should not count as incapacitated");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue