feat: wire pallet/hook/gate/chest systems into gameplay

This commit is contained in:
loki5512344 2026-06-03 20:29:04 +02:00
parent b339f4c029
commit fcf2debbd4
4 changed files with 383 additions and 75 deletions

View file

@ -1,8 +1,11 @@
package dev.loki.core;
import dev.loki.config.GameConfig;
import dev.loki.gate.GateManager;
import dev.loki.hook.HookManager;
import dev.loki.mdbd;
import dev.loki.objective.GeneratorManager;
import dev.loki.pallet.PalletManager;
import dev.loki.phase.EndgamePhase;
import dev.loki.phase.GameplayPhase;
import dev.loki.phase.LobbyPhase;
@ -27,6 +30,11 @@ public class GameSession {
private RoleAssigner roleAssigner;
private GeneratorManager generatorManager;
// Optional managers set by GameplayPhase
private PalletManager palletManager;
private HookManager hookManager;
private GateManager gateManager;
public GameSession(mdbd plugin) {
this.plugin = plugin;
this.eventBus = new GameEventBus();
@ -146,6 +154,19 @@ public class GameSession {
return generatorManager;
}
// -------------------------------------------------------------------------
// Gameplay-phase managers (set by GameplayPhase.enter())
// -------------------------------------------------------------------------
public PalletManager getPalletManager() { return palletManager; }
public void setPalletManager(PalletManager pm) { this.palletManager = pm; }
public HookManager getHookManager() { return hookManager; }
public void setHookManager(HookManager hm) { this.hookManager = hm; }
public GateManager getGateManager() { return gateManager; }
public void setGateManager(GateManager gm) { this.gateManager = gm; }
public void forceStart() {
if (currentPhase instanceof LobbyPhase lobby) {
lobby.forceStart();

View file

@ -8,6 +8,7 @@ import dev.loki.core.GameManager;
import dev.loki.data.GeneratorDataManager;
import dev.loki.listener.GeneratorSelectListener;
import dev.loki.listener.PlayerInteractListener;
import dev.loki.listener.PlayerMoveListener;
import dev.loki.listener.PlayerQuitListener;
import dev.loki.map.MapBuilder;
import dev.loki.map.TemplateManager;
@ -25,6 +26,8 @@ public final class mdbd extends JavaPlugin {
private DBDAdminCommand dbdAdminCommand;
private SetupCommand setupCommand;
private LobbyManager lobbyManager;
private PlayerInteractListener interactListener;
private PlayerMoveListener moveListener;
@Override
public void onEnable() {
@ -62,12 +65,17 @@ public final class mdbd extends JavaPlugin {
getServer().getPluginManager().registerEvents(
new GeneratorSelectListener(dbdAdminCommand, generatorData), this);
getServer().getPluginManager().registerEvents(
new dev.loki.listener.SetupBlockBreakListener(setupCommand), this);
getServer().getPluginManager().registerEvents(
new PlayerQuitListener(this, gameManager), this);
var interactListener = new PlayerInteractListener(this, gameManager, null);
this.interactListener = new PlayerInteractListener(this, gameManager, null);
interactListener.setSetupCommand(setupCommand);
getServer().getPluginManager().registerEvents(interactListener, this);
this.moveListener = new PlayerMoveListener(this, gameManager);
getServer().getPluginManager().registerEvents(moveListener, this);
getLogger().info("Mdbd Plugin enabled successfully!");
getLogger().info("Loaded " + templateManager.getAllTemplates().size() + " templates");
getLogger().info("Loaded " + generatorData.getCount() + " generators from data");
@ -120,6 +128,14 @@ public final class mdbd extends JavaPlugin {
return lobbyManager;
}
public PlayerInteractListener getInteractListener() {
return interactListener;
}
public PlayerMoveListener getMoveListener() {
return moveListener;
}
public void reloadConfigs() {
configManager.loadConfigs();
templateManager.loadAllTemplates();

View file

@ -2,13 +2,11 @@ package dev.loki.pallet;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.block.Block;
public class Pallet {
private final Location location;
private PalletState state;
private Block topBlock;
private Block bottomBlock;
public enum PalletState {
STANDING,
@ -16,70 +14,111 @@ public class Pallet {
BROKEN
}
public Pallet(Location location) {
this.location = location;
private final Location center; // The OAK_LOG position saved in locations.yml
private Block leftBlock;
private Block centerBlock;
private Block rightBlock;
private PalletState state;
public Pallet(Location center) {
this.center = center.clone();
this.state = PalletState.STANDING;
this.topBlock = location.clone().add(0, 1, 0).getBlock();
this.bottomBlock = location.getBlock();
detectStructure();
}
private boolean isPlanks(Block block) {
return block.getType() == Material.OAK_PLANKS;
}
/**
* Detects which axis the PLP structure lies on (X or Z) by checking
* neighboring blocks. Falls back to X axis if neither looks right.
*/
private void detectStructure() {
Block c = center.getBlock();
centerBlock = c;
Block xLeft = center.clone().add(-1, 0, 0).getBlock();
Block xRight = center.clone().add( 1, 0, 0).getBlock();
Block zLeft = center.clone().add( 0, 0, -1).getBlock();
Block zRight = center.clone().add( 0, 0, 1).getBlock();
// Prefer the axis where both neighbours are OAK_PLANKS
boolean xPlpValid = isPlanks(xLeft) && isPlanks(xRight);
boolean zPlpValid = isPlanks(zLeft) && isPlanks(zRight);
if (xPlpValid) {
leftBlock = xLeft;
rightBlock = xRight;
} else if (zPlpValid) {
leftBlock = zLeft;
rightBlock = zRight;
} else {
// Fallback: use X axis regardless (blocks may have been loaded before
// the world is fully available; state ops will still work).
leftBlock = xLeft;
rightBlock = xRight;
}
}
// -------------------------------------------------------------------------
// State transitions
// -------------------------------------------------------------------------
public void setDropped() {
if (state != PalletState.STANDING) return;
state = PalletState.DROPPED;
// Replace the OAK_LOG center with OAK_PLANKS (sides stay as-is)
centerBlock.setType(Material.OAK_PLANKS);
// Sound + particles at center
Location loc = center.clone().add(0.5, 0.5, 0.5);
if (loc.getWorld() != null) {
loc.getWorld().playSound(loc, Sound.BLOCK_WOOD_BREAK, 1.0f, 0.7f);
loc.getWorld().spawnParticle(Particle.BLOCK, loc, 30, 0.5, 0.3, 0.5, 0.1,
Material.OAK_PLANKS.createBlockData());
}
}
public void setBroken() {
state = PalletState.BROKEN;
leftBlock.setType(Material.AIR);
centerBlock.setType(Material.AIR);
rightBlock.setType(Material.AIR);
}
public void reset() {
state = PalletState.STANDING;
// Restore PLP structure
leftBlock.setType(Material.OAK_PLANKS);
centerBlock.setType(Material.OAK_LOG);
rightBlock.setType(Material.OAK_PLANKS);
}
// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------
public Location getLocation() {
return location;
return center.clone();
}
/** Centre of the pallet block for distance calculations. */
public Location getCenter() {
return center.clone().add(0.5, 0.5, 0.5);
}
public PalletState getState() {
return state;
}
public boolean isStanding() {
return state == PalletState.STANDING;
}
public boolean isDropped() {
return state == PalletState.DROPPED;
}
public boolean isBroken() {
return state == PalletState.BROKEN;
}
public void setDropped() {
this.state = PalletState.DROPPED;
updateVisuals();
}
public void setBroken() {
this.state = PalletState.BROKEN;
clearBlocks();
}
public void reset() {
this.state = PalletState.STANDING;
restoreBlocks();
}
private void updateVisuals() {
if (state == PalletState.DROPPED) {
bottomBlock.setType(Material.OAK_PLANKS);
topBlock.setType(Material.OAK_PLANKS);
}
}
private void clearBlocks() {
bottomBlock.setType(Material.AIR);
topBlock.setType(Material.AIR);
}
private void restoreBlocks() {
bottomBlock.setType(Material.OAK_FENCE);
topBlock.setType(Material.AIR);
}
public Location getCenter() {
return location.clone().add(0.5, 0.5, 0.5);
}
public boolean isStanding() { return state == PalletState.STANDING; }
public boolean isDropped() { return state == PalletState.DROPPED; }
public boolean isBroken() { return state == PalletState.BROKEN; }
public double distanceTo(Location loc) {
if (!getCenter().getWorld().equals(loc.getWorld())) return Double.MAX_VALUE;
return getCenter().distance(loc);
}
}

View file

@ -1,9 +1,29 @@
package dev.loki.phase;
import dev.loki.chest.ChestManager;
import dev.loki.chest.ChestOpening;
import dev.loki.core.GameSession;
import dev.loki.gate.ExitGate;
import dev.loki.gate.GateManager;
import dev.loki.hook.Hook;
import dev.loki.hook.HookManager;
import dev.loki.hook.HookRescue;
import dev.loki.hook.HookSacrifice;
import dev.loki.hook.HookStage;
import dev.loki.listener.PlayerInteractListener;
import dev.loki.listener.PlayerMoveListener;
import dev.loki.mdbd;
import dev.loki.objective.GeneratorManager;
import dev.loki.pallet.Pallet;
import dev.loki.pallet.PalletBreak;
import dev.loki.pallet.PalletDrop;
import dev.loki.pallet.PalletManager;
import dev.loki.pallet.PalletStun;
import dev.loki.player.BasePlayer;
import dev.loki.player.Killer;
import dev.loki.player.PlayerState;
import dev.loki.player.Survivor;
import dev.loki.ui.ActionBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
@ -11,23 +31,71 @@ import org.bukkit.Sound;
import org.bukkit.entity.Player;
public class GameplayPhase implements Phase {
private final GeneratorManager generatorManager;
private int tickCounter;
private boolean shouldEnd;
private boolean allGensDone;
// Managers initialised in enter()
private PalletManager palletManager;
private HookManager hookManager;
private GateManager gateManager;
private HookRescue hookRescue;
private HookSacrifice hookSacrifice;
private PalletDrop palletDrop;
private PalletBreak palletBreak;
private PalletStun palletStun;
private ChestManager chestManager;
private ChestOpening chestOpening;
// Listener references grabbed from plugin
private PlayerMoveListener moveListener;
private PlayerInteractListener interactListener;
public GameplayPhase(GeneratorManager generatorManager) {
this.generatorManager = generatorManager;
this.tickCounter = 0;
this.shouldEnd = false;
this.allGensDone = false;
}
// -------------------------------------------------------------------------
// Phase lifecycle
// -------------------------------------------------------------------------
@Override
public void enter(GameSession session) {
Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!", NamedTextColor.GREEN));
mdbd plugin = session.getPlugin();
tickCounter = 0;
// Teleport is handled by RoleAssigner.assignRoles — no double-teleport needed here
shouldEnd = false;
allGensDone = false;
// --- Initialise managers ---
palletManager = new PalletManager(plugin.getConfigManager().getLocations());
hookManager = new HookManager(plugin);
hookManager.loadHooks(plugin.getConfigManager().getLocations());
gateManager = new GateManager(plugin.getConfigManager().getLocations());
ActionBar ab = new ActionBar();
hookRescue = new HookRescue(plugin, ab);
hookSacrifice = new HookSacrifice(plugin, null);
palletDrop = new PalletDrop(palletManager);
palletBreak = new PalletBreak(session.getConfig());
palletStun = new PalletStun(session.getConfig());
chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations());
chestOpening = new ChestOpening(plugin);
// Store managers on session so other code can reach them
session.setPalletManager(palletManager);
session.setHookManager(hookManager);
session.setGateManager(gateManager);
// --- Wire listeners ---
moveListener = plugin.getMoveListener();
interactListener = plugin.getInteractListener();
wireMoveListener(session);
wireInteractListener(session);
Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!",
NamedTextColor.GREEN));
}
@Override
@ -35,19 +103,92 @@ public class GameplayPhase implements Phase {
tickCounter++;
generatorManager.tick();
// Tick hook timers every game tick
tickHooks(session);
if (tickCounter % 20 == 0) {
checkGeneratorProgress(session);
checkWinConditions(session);
}
}
@Override
public void exit(GameSession session) {
// Clean up listeners
if (moveListener != null) {
moveListener.setOnSurvivorMove(null);
}
if (interactListener != null) {
interactListener.setOnPalletInteract(null);
interactListener.setOnRescueAttempt(null);
interactListener.setOnChestInteract(null);
interactListener.setOnAttack(null);
}
// Stop hooks
if (hookManager != null) {
hookManager.clear();
}
if (hookRescue != null) {
hookRescue.clear();
}
if (chestOpening != null) {
chestOpening.cancelAll();
}
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
}
@Override
public boolean isComplete() {
return shouldEnd;
}
// -------------------------------------------------------------------------
// Hook ticking
// -------------------------------------------------------------------------
private void tickHooks(GameSession session) {
for (Hook hook : hookManager.getAllHooks()) {
if (!hook.isOccupied()) continue;
hook.tick();
Survivor victim = hook.getHookedSurvivor();
if (victim == null) continue;
// Show action bar progress to the hooked survivor
Player vp = victim.getPlayer();
if (vp != null && vp.isOnline()) {
// ActionBar shows stage progress
double pct = hook.getStageProgressPercent();
String label = hook.getCurrentStage() == HookStage.STAGE_2
? "§cStage 2 - struggle!"
: "§eOn hook";
vp.sendActionBar(Component.text(label + " " + (int) pct + "%"));
}
// Sacrifice when timer expires
if (hook.getCurrentStage() == HookStage.SACRIFICE) {
hookSacrifice.sacrificeIfExpired(victim, hook);
}
}
}
// -------------------------------------------------------------------------
// Generator / win-condition checks
// -------------------------------------------------------------------------
private void checkGeneratorProgress(GameSession session) {
if (allGensDone) return;
if (generatorManager.areAllRequiredCompleted()) {
allGensDone = true;
gateManager.activateEndgame();
Bukkit.broadcast(Component.text(
"[DBD] Все генераторы починены! Открывайте ворота!", NamedTextColor.GREEN));
for (BasePlayer bp : session.getPlayers().values()) {
Player p = bp.getPlayer();
if (p != null) {
@ -58,36 +199,127 @@ public class GameplayPhase implements Phase {
}
private void checkWinConditions(GameSession session) {
int aliveSurvivors = 0;
int aliveSurvivors = 0;
int escapedSurvivors = 0;
int totalSurvivors = 0;
int totalSurvivors = 0;
for (BasePlayer bp : session.getPlayers().values()) {
if (bp.isKiller()) continue;
totalSurvivors++;
PlayerState state = bp.getState();
if (state == PlayerState.ESCAPED) {
PlayerState st = bp.getState();
if (st == PlayerState.ESCAPED) {
escapedSurvivors++;
} else if (state != PlayerState.DEAD && state != PlayerState.HOOKED) {
} else if (st != PlayerState.DEAD && st != PlayerState.HOOKED) {
aliveSurvivors++;
}
}
boolean allSurvivorsDead = (aliveSurvivors == 0 && escapedSurvivors == 0 && totalSurvivors > 0);
boolean allDead = (aliveSurvivors == 0 && escapedSurvivors == 0 && totalSurvivors > 0);
boolean allEscaped = (escapedSurvivors == totalSurvivors && totalSurvivors > 0);
if (allSurvivorsDead || allEscaped) {
if (allDead || allEscaped) {
shouldEnd = true;
}
}
@Override
public void exit(GameSession session) {
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
// -------------------------------------------------------------------------
// Listener wiring
// -------------------------------------------------------------------------
private void wireMoveListener(GameSession session) {
if (moveListener == null) return;
// Gate escape: survivor walks into open gate zone after all gens done
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));
}
}
});
}
@Override
public boolean isComplete() {
return shouldEnd;
private void wireInteractListener(GameSession session) {
if (interactListener == null) return;
// Pallet interact: survivor right-click = drop, killer left-click = break
interactListener.setOnPalletInteract((player, isRightClick) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null) return;
if (isRightClick && !bp.isKiller()) {
palletDrop.tryDrop((Survivor) bp);
} else if (!isRightClick && bp.isKiller()) {
Killer killer = (Killer) bp;
Pallet nearby = palletManager.getPalletAt(player.getLocation(), 3.0);
if (nearby != null && nearby.isDropped()) {
palletBreak.startBreaking(killer, nearby, session.getPlugin());
}
}
});
// Rescue attempt: survivor right-clicks near a hook
interactListener.setOnRescueAttempt((player, ignored) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
Survivor rescuer = (Survivor) bp;
if (rescuer.getState() == PlayerState.HOOKED) return;
Hook hook = hookManager.findNearestOccupiedHook(player.getLocation(), 3.0);
if (hook != null) {
hookRescue.startRescue(rescuer, hook);
}
});
// Killer right-click: hook a carried survivor
interactListener.setOnAttack((player, isRightClick) -> {
if (!isRightClick) return; // left-click handled by damage system
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || !bp.isKiller()) return;
Killer killer = (Killer) bp;
if (killer.getState() == PlayerState.CARRIED || killer.isCarrying()) {
// Find the carried survivor
for (BasePlayer other : session.getPlayers().values()) {
if (!other.isKiller() && other.getState() == PlayerState.CARRIED) {
Survivor carried = (Survivor) other;
if (hookManager.tryHookSurvivor(carried, player.getLocation())) {
killer.setCarrying(false);
// Teleport survivor to hook
Hook nearestHook = hookManager.findNearestOccupiedHook(
player.getLocation(), 5.0);
if (nearestHook != null) {
Player vp = carried.getPlayer();
if (vp != null) {
vp.teleport(nearestHook.getLocation());
}
}
}
break;
}
}
}
});
// Chest interact
interactListener.setOnChestInteract((player, ignored) -> {
BasePlayer bp = session.getPlayer(player.getUniqueId());
if (bp == null || bp.isKiller()) return;
var chest = chestManager.getNearestChest(player.getLocation(), 3.0);
if (chest != null) {
chestOpening.startOpening(player, chest);
}
});
}
}