refactor: generator minigame system (gui + text), fix color codes, holograms, messages, & config

This commit is contained in:
loki5512344 2026-06-14 16:04:25 +02:00
parent 957ab4a5a2
commit 7c7562a8d4
48 changed files with 1628 additions and 733 deletions

View file

@ -59,7 +59,8 @@ public class EndgamePhase implements Phase {
// показываем результаты сразу, без геймплея эндгейма
int aliveCount = 0;
for (BasePlayer bp : session.getPlayers().values()) {
if (!bp.isKiller() && bp.getState() != PlayerState.DEAD && bp.getState() != PlayerState.ESCAPED) {
if (!bp.isKiller() && bp.getState() != PlayerState.DEAD && bp.getState() != PlayerState.ESCAPED
&& bp.getState() != PlayerState.DOWNED && bp.getState() != PlayerState.HOOKED) {
aliveCount++;
}
}
@ -122,20 +123,12 @@ public class EndgamePhase implements Phase {
hatch.reset();
}
// Для killer_win: результаты уже показаны и телепорт выполнен в enter(),
// здесь просто останавливаем сессию
if (killerWin) {
session.stop();
return;
}
// Нормальный эндгейм: показываем результаты и телепортируем всех в лобби
announceEndgameEnd(session);
teleportAllToLobby(session);
// exit() НЕ вызывает session.stop() — это делает transitionToNextPhase()
// Телепорт и результаты уже обработаны в enter() или будут в teleportAllToLobby
}
/**
* Телепортирует всех игроков в лобби и останавливает сессию.
* Телепортирует всех игроков в лобби, сбрасывает атрибуты и останавливает сессию.
*/
private void teleportAllToLobby(GameSession session) {
List<BasePlayer> playersToTeleport = new ArrayList<>(session.getPlayers().values());
@ -146,12 +139,7 @@ public class EndgamePhase implements Phase {
for (BasePlayer bp : playersToTeleport) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) {
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
p.getInventory().clear();
try {
var attr = p.getAttribute(org.bukkit.attribute.Attribute.SCALE);
if (attr != null) attr.setBaseValue(1.0);
} catch (Exception ignored) {}
resetPlayerAttributes(p);
session.getPlugin().getLobbyManager().teleportToLobby(p);
}
}
@ -160,6 +148,34 @@ public class EndgamePhase implements Phase {
}.runTaskLater(session.getPlugin(), 60L);
}
/**
* Сбрасывает все атрибуты игрока на значения по умолчанию.
*/
private void resetPlayerAttributes(Player p) {
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
p.getInventory().clear();
p.setWalkSpeed(0.2f);
p.setFlySpeed(0.1f);
p.setSwimming(false);
p.setSprinting(false);
p.setSneaking(false);
p.setHealth(20.0);
p.setFoodLevel(20);
p.setSaturation(5);
try {
var scale = p.getAttribute(org.bukkit.attribute.Attribute.SCALE);
if (scale != null) scale.setBaseValue(1.0);
} catch (Exception ignored) {}
try {
var maxHp = p.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH);
if (maxHp != null) maxHp.setBaseValue(20.0);
} catch (Exception ignored) {}
try {
var moveSpeed = p.getAttribute(org.bukkit.attribute.Attribute.MOVEMENT_SPEED);
if (moveSpeed != null) moveSpeed.setBaseValue(0.1);
} catch (Exception ignored) {}
}
@Override
public boolean isComplete() {
return complete;
@ -233,7 +249,8 @@ public class EndgamePhase implements Phase {
for (BasePlayer player : session.getPlayers().values()) {
if (player.isKiller()) continue;
PlayerState st = player.getState();
if (st != PlayerState.DEAD && st != PlayerState.ESCAPED) {
if (st != PlayerState.DEAD && st != PlayerState.ESCAPED
&& st != PlayerState.DOWNED && st != PlayerState.HOOKED) {
survivorsAlive++;
}
}

View file

@ -16,11 +16,13 @@ import dev.loki.player.Killer;
import dev.loki.player.PlayerState;
import dev.loki.player.Survivor;
import dev.loki.survivor.SurvivorController;
import dev.loki.item.DBDItems;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.Map;
@ -168,8 +170,8 @@ public class GameplayInteraction {
player, PlayerState.DOWNED, 3.0);
if (target != null) {
boolean picked = damageSystem.getKillerController().tryPickup(killer, target);
if (!picked) player.sendMessage(Component.text(
"[DBD] Не удалось подобрать выжившего.", NamedTextColor.RED));
if (!picked) player.sendMessage(net.kyori.adventure.text.Component.text(
"[DBD] Не удалось подобрать выжившего.", net.kyori.adventure.text.format.NamedTextColor.RED));
}
}
});
@ -217,21 +219,62 @@ public class GameplayInteraction {
}
} else {
Survivor surv = (Survivor) bp;
if (surv.getState() == PlayerState.HOOKED) {
PlayerState st = surv.getState();
if (st == PlayerState.HOOKED) {
// Hook recovery is handled elsewhere
} else {
if (isSneaking) hookSabotage.startSabotage(surv);
else hookSabotage.stopSabotage(surv.getUuid());
} else if (st == PlayerState.HEALTHY || st == PlayerState.INJURED) {
if (isSneaking) {
double range = session.getConfig().getHookInteractionRange();
// Try rescue first (if someone is on a hook nearby)
Hook occupied = hookManager.findNearestOccupiedHook(player.getLocation(), range);
if (occupied != null) {
hookRescue.startRescue(surv, occupied);
return;
}
// Then try sabotage (only if holding hook cutter AND near a hook)
Hook free = hookManager.findNearestUnoccupiedHook(player.getLocation(), range);
if (free != null) {
ItemStack hand = player.getInventory().getItemInMainHand();
if (!DBDItems.isHookCutter(hand)) {
player.sendMessage(session.getPlugin().getConfigManager().getMessages().get("hook.sabotage.need-cutter"));
return;
}
hookSabotage.startSabotage(surv);
}
} else {
hookSabotage.stopSabotage(surv.getUuid());
}
}
}
});
// Generator repair
// Consume Hook Cutter after successful sabotage
hookSabotage.setOnSabotageComplete(uuid -> {
Player p = Bukkit.getPlayer(uuid);
if (p != null && p.isOnline()) {
p.getInventory().setItemInMainHand(null);
}
});
// Text skill check: complete on sneak release
interactListener.setOnGeneratorSkillCheckSneak((player, ignored) -> {
if (generatorRepair == null) return;
if (generatorRepair.isMinigameActive(player.getUniqueId())) {
generatorRepair.handleMinigameSneak(player);
}
});
// Generator repair + right‑click for text skill check
interactListener.setOnGeneratorInteract((player, action) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
if (generatorRepair == null) return;
if (generatorRepair.isMinigameActive(player.getUniqueId())) {
generatorRepair.handleMinigameClick(player);
return;
}
var genManager = session.getGeneratorManager();
if (genManager == null) return;
double range = session.getConfig().getGeneratorInteractionRange();
@ -301,6 +344,7 @@ public class GameplayInteraction {
interactListener.setOnKillerSneak(null);
interactListener.setOnHealSneak(null);
interactListener.setOnCarriedShift(null);
interactListener.setOnGeneratorSkillCheckSneak(null);
interactListener.setOnGeneratorInteract(null);
interactListener.setOnHatchInteract(null);
}

View file

@ -4,6 +4,7 @@ import dev.loki.chest.ChestManager;
import dev.loki.chest.ChestOpening;
import dev.loki.config.GameConfig;
import dev.loki.core.GameSession;
import dev.loki.gate.ExitGate;
import dev.loki.gate.GateManager;
import dev.loki.gate.GateOpening;
import dev.loki.hook.HookManager;
@ -15,6 +16,8 @@ import dev.loki.hook.HookStruggle;
import dev.loki.item.ChestLoot;
import dev.loki.killer.GeneratorKick;
import dev.loki.mdbd;
import dev.loki.minigames.GeneratorMinigame;
import dev.loki.minigames.generator.GuiRouletteMinigame;
import dev.loki.objective.GeneratorManager;
import dev.loki.player.BasePlayer;
import dev.loki.player.PlayerState;
@ -22,7 +25,6 @@ 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 org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@ -91,8 +93,7 @@ public class GameplayPhase implements Phase {
world.setStorm(false);
}
Bukkit.broadcast(Component.text(
"[DBD] Игра началась! Выжившие, чините генераторы!", NamedTextColor.GREEN));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.start"));
}
@Override
@ -133,7 +134,7 @@ public class GameplayPhase implements Phase {
damageSystem.clear();
damageSystem.clearManagers();
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.end"));
}
@Override
@ -164,6 +165,18 @@ public class GameplayPhase implements Phase {
ActionBar ab = new ActionBar();
HookRecovery hookRecovery = new HookRecovery(plugin, cfg);
titleUI = new TitleUI(plugin);
String genMinigameType = cfg.getGeneratorMinigame();
GeneratorMinigame genMinigame;
if ("gui".equalsIgnoreCase(genMinigameType)) {
genMinigame = new GuiRouletteMinigame(plugin);
} else if ("text".equalsIgnoreCase(genMinigameType)) {
genMinigame = new dev.loki.minigames.generator.TextSkillCheckMinigame(plugin);
} else {
plugin.getLogger().warning("Unknown generator minigame: " + genMinigameType + ", using gui");
genMinigame = new GuiRouletteMinigame(plugin);
}
hookStruggle = new HookStruggle(plugin, titleUI, cfg);
HookSacrifice hookSacrifice = new HookSacrifice(plugin, null);
hookSabotage = new HookSabotage(plugin, cfg, hookManager);
@ -176,11 +189,19 @@ public class GameplayPhase implements Phase {
chestManager.startLootResetTimer();
chestOpening = new ChestOpening(plugin);
chestOpening.setChestLoot(new ChestLoot(cfg));
var gameWorld = plugin.getLobbyManager().getGameWorld();
if (gameWorld != null) {
for (var h : hookManager.getAllHooks()) h.getLocation().setWorld(gameWorld);
for (var c : chestManager.getAllChests()) c.getLocation().setWorld(gameWorld);
for (var g : generatorManager.getAllGenerators()) g.getLocation().setWorld(gameWorld);
}
spawnGameBlocks(plugin, cfg);
generatorKick = new GeneratorKick(plugin, cfg, generatorManager);
generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager);
generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager, genMinigame);
// Голограммы ВСЕГДА включены
generatorHologram = new dev.loki.objective.GeneratorHologram(plugin, generatorManager);
generatorHologram.start();
@ -222,11 +243,12 @@ public class GameplayPhase implements Phase {
if (p != null && p.isOnline()) p.getInventory().clear();
}
// Register skill check GUI listener
dev.loki.listener.SkillCheckListener skillCheckListener =
new dev.loki.listener.SkillCheckListener(plugin,
generatorRepair.getSkillCheckGUI());
skillCheckListener.register();
GeneratorMinigame gm = generatorRepair.getMinigame();
if (gm instanceof GuiRouletteMinigame gui) {
dev.loki.listener.SkillCheckListener listener =
new dev.loki.listener.SkillCheckListener(plugin, gui);
listener.register();
}
interaction.wire(session);
}
@ -241,8 +263,7 @@ public class GameplayPhase implements Phase {
allGensDone = true;
interaction.setAllGensDone(true);
gateManager.activateEndgame();
Bukkit.broadcast(Component.text(
"[DBD] Все генераторы починены! Открывайте ворота!", NamedTextColor.GREEN));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("generator.repair.all-done"));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0f, 1.0f);
@ -289,6 +310,12 @@ public class GameplayPhase implements Phase {
if (b.getType() != hookMat) b.setType(hookMat, false);
}
plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + hookManager.getAllHooks().size() + " hooks");
// Спавн блоков на местах ворот (Hopper — для открытия через ПКМ)
for (ExitGate gate : gateManager.getAllGates()) {
gate.spawnGateBlock();
}
plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + gateManager.getAllGates().size() + " gates");
}
// =========================================================================
@ -342,6 +369,13 @@ public class GameplayPhase implements Phase {
if (hookStruggle != null) hookStruggle.clear();
if (chestManager != null) chestManager.stopLootResetTimer();
if (chestHologram != null) chestHologram.stop();
// Clean up gate blocks
if (gateManager != null) {
for (ExitGate gate : gateManager.getAllGates()) {
gate.removeGateBlock();
}
gateManager.resetAll();
}
session.getPlugin().getInteractListener().setGeneratorManager(null);
}
}

View file

@ -104,7 +104,7 @@ public class GameplayUI {
matchTimerBar.color(color);
if (matchTimerTicks <= 0) {
Bukkit.broadcast(Component.text("[DBD] Время вышло! Маньяк победил!", NamedTextColor.DARK_RED));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.time-up"));
if (winConditions != null) winConditions.announceKillerWin(session);
return true;
}

View file

@ -8,8 +8,6 @@ import dev.loki.player.Killer;
import dev.loki.player.PlayerState;
import dev.loki.player.Survivor;
import dev.loki.ui.TitleUI;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Sound;
@ -35,14 +33,26 @@ public class GameplayWinConditions {
* Check all win conditions each second (20 ticks).
*/
public void checkWinConditions(GameSession session) {
int aliveSurvivors = countAliveSurvivors(session);
Killer killer = getKiller(session);
// Killer disconnect: if killer player is null/offline, survivors win
if (killer == null || killer.getPlayer() == null || !killer.getPlayer().isOnline()) {
winReason = "survivors_win";
Bukkit.broadcast(Component.text("[DBD] Маньяк покинул игру! Выжившие победили!", NamedTextColor.GREEN));
announceSurvivorsWin(session);
if (!shouldEnd) {
winReason = "survivors_win";
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.killer-disconnect"));
announceSurvivorsWin(session);
shouldEnd = true;
}
return;
}
// No survivors at all — automatic killer win
long survivorCount = session.getPlayers().values().stream()
.filter(p -> !p.isKiller()).count();
if (survivorCount == 0) {
winReason = "killer_win";
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.no-survivors"));
announceKillerWin(session);
shouldEnd = true;
return;
}
@ -50,7 +60,7 @@ public class GameplayWinConditions {
// Killer wins: all survivors are downed/hooked/carried/dead
if (allSurvivorsIncapacitated(session)) {
winReason = "killer_win";
Bukkit.broadcast(Component.text("[DBD] Все выжившие обезврежены!", NamedTextColor.DARK_RED));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.all-incapacitated"));
announceKillerWin(session);
shouldEnd = true;
return;
@ -64,7 +74,7 @@ public class GameplayWinConditions {
}
}
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, or DEAD). */
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, DEAD, or ESCAPED). */
private boolean allSurvivorsIncapacitated(GameSession session) {
boolean hasSurvivors = false;
for (BasePlayer bp : session.getPlayers().values()) {
@ -72,7 +82,8 @@ public class GameplayWinConditions {
hasSurvivors = true;
PlayerState st = bp.getState();
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
}
}
@ -106,7 +117,7 @@ public class GameplayWinConditions {
public void announceKillerWin(GameSession session) {
winReason = "killer_win";
Bukkit.broadcast(Component.text("[DBD] Маньяк победил! Все выжившие мертвы!", NamedTextColor.DARK_RED));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.killer-win"));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;
@ -122,7 +133,7 @@ public class GameplayWinConditions {
public void announceSurvivorsWin(GameSession session) {
winReason = "survivors_win";
Bukkit.broadcast(Component.text("[DBD] Выжившие победили!", NamedTextColor.GREEN));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.survivors-win"));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p == null || !p.isOnline()) continue;

View file

@ -3,8 +3,8 @@ package dev.loki.phase;
import dev.loki.config.GameConfig;
import dev.loki.core.GameSession;
import dev.loki.player.BasePlayer;
import dev.loki.config.MessagesConfig;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@ -38,7 +38,8 @@ public class LobbyPhase implements Phase {
if (p != null && p.isOnline()) {
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
lobbyManager.teleportToLobby(p);
p.sendMessage(Component.text("[DBD] Лобби открыто! Ожидание игроков...", NamedTextColor.GREEN));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
p.sendMessage(messages.get("lobby.opened"));
}
}
}
@ -59,9 +60,8 @@ public class LobbyPhase implements Phase {
secondsLeft = config.getCountdown4Players();
tickSub = 0;
lastPlayerCount = playerCount;
sendActionBarToAll(session,
Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers,
NamedTextColor.YELLOW));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
sendActionBarToAll(session, messages.get("lobby.waiting", playerCount, maxPlayers));
return;
}
@ -74,9 +74,8 @@ public class LobbyPhase implements Phase {
if (secondsLeft > fast) {
secondsLeft = fast;
tickSub = 0;
Bukkit.broadcast(Component.text(
"[DBD] Все игроки вошли! Старт через " + fast + " сек!",
NamedTextColor.GREEN));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
Bukkit.broadcast(messages.get("lobby.all-ready", fast));
}
}
}
@ -91,17 +90,14 @@ public class LobbyPhase implements Phase {
if (secondsLeft == 30 || secondsLeft == 15 || secondsLeft == 10
|| secondsLeft == 5 || secondsLeft == 3
|| secondsLeft == 2 || secondsLeft == 1) {
Bukkit.broadcast(Component.text(
"[DBD] До начала: " + secondsLeft + " сек.",
NamedTextColor.YELLOW));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
Bukkit.broadcast(messages.get("lobby.countdown", secondsLeft));
}
}
// ActionBar всем (на русском, без прогресс-бара)
NamedTextColor barColor = (playerCount >= maxPlayers)
? NamedTextColor.GREEN : NamedTextColor.YELLOW;
sendActionBarToAll(session,
Component.text(">>> Начало через: " + secondsLeft + "с (" + playerCount + "/" + minPlayers + ")", barColor));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
sendActionBarToAll(session, messages.get("lobby.actionbar-waiting", secondsLeft, playerCount, maxPlayers));
if (secondsLeft <= 0) {
started = true;
@ -129,7 +125,8 @@ public class LobbyPhase implements Phase {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
}
Bukkit.broadcast(Component.text("[DBD] Игра начинается!", NamedTextColor.GREEN));
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
Bukkit.broadcast(messages.get("lobby.starting"));
}
@Override
@ -137,11 +134,10 @@ public class LobbyPhase implements Phase {
player.setGameMode(org.bukkit.GameMode.ADVENTURE);
session.getPlugin().getLobbyManager().teleportToLobby(player);
int playerCount = session.getPlayerCount();
int minPlayers = config.getMinPlayers();
int maxPlayers = config.getMaxPlayers();
// Сообщаем только участникам этой сессии, не всему серверу
Component msg = Component.text(
"[DBD] " + player.getName() + " присоединился! (" + playerCount + "/" + minPlayers + ")",
NamedTextColor.YELLOW);
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
Component msg = messages.get("lobby.player-joined", player.getName(), playerCount, maxPlayers);
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null && p.isOnline()) p.sendMessage(msg);

View file

@ -2,8 +2,6 @@ package dev.loki.phase;
import dev.loki.core.GameSession;
import dev.loki.role.RoleAssigner;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
public class RoleAssignPhase implements Phase {
@ -46,7 +44,7 @@ public class RoleAssignPhase implements Phase {
@Override
public void exit(GameSession session) {
Bukkit.broadcast(Component.text("[DBD] Роли назначены!", NamedTextColor.GREEN));
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.roles-assigned"));
}
@Override