From 37e67ce97dcb2b019b0d12593aad44953e1791c9 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 31 May 2026 20:49:37 +0200 Subject: [PATCH] Stage 13: Perks and Totems --- src/main/java/dev/loki/chest/Chest.java | 3 +- .../java/dev/loki/chest/ChestManager.java | 4 +- src/main/java/dev/loki/gate/ExitGate.java | 116 +++++++++ src/main/java/dev/loki/gate/GateManager.java | 114 +++++++++ src/main/java/dev/loki/gate/GateOpening.java | 147 +++++++++++ src/main/java/dev/loki/hatch/Hatch.java | 125 ++++++++++ .../java/dev/loki/hatch/HatchCondition.java | 76 ++++++ src/main/java/dev/loki/hatch/HatchEscape.java | 110 +++++++++ src/main/java/dev/loki/hook/HookManager.java | 2 +- .../java/dev/loki/hook/HookSacrifice.java | 10 +- .../java/dev/loki/killer/KillerVision.java | 9 +- .../dev/loki/listener/DamageListener.java | 116 +++++++++ .../loki/listener/PlayerInteractListener.java | 174 +++++++++++++ .../dev/loki/listener/PlayerMoveListener.java | 196 +++++++++++++++ .../dev/loki/listener/PlayerQuitListener.java | 103 ++++++++ src/main/java/dev/loki/pallet/PalletDrop.java | 4 +- src/main/java/dev/loki/pallet/PalletStun.java | 2 +- .../java/dev/loki/phase/EndgamePhase.java | 231 +++++++++++++++++- src/main/java/dev/loki/util/LocationUtil.java | 138 +++++++++++ src/main/java/dev/loki/util/PlayerUtil.java | 152 ++++++++++++ src/main/java/dev/loki/util/TimeUtil.java | 135 ++++++++++ .../java/dev/loki/window/WindowFastVault.java | 120 +++++++++ .../java/dev/loki/window/WindowVault.java | 8 +- 23 files changed, 2070 insertions(+), 25 deletions(-) create mode 100644 src/main/java/dev/loki/gate/ExitGate.java create mode 100644 src/main/java/dev/loki/gate/GateManager.java create mode 100644 src/main/java/dev/loki/gate/GateOpening.java create mode 100644 src/main/java/dev/loki/hatch/Hatch.java create mode 100644 src/main/java/dev/loki/hatch/HatchCondition.java create mode 100644 src/main/java/dev/loki/hatch/HatchEscape.java create mode 100644 src/main/java/dev/loki/listener/DamageListener.java create mode 100644 src/main/java/dev/loki/listener/PlayerInteractListener.java create mode 100644 src/main/java/dev/loki/listener/PlayerMoveListener.java create mode 100644 src/main/java/dev/loki/listener/PlayerQuitListener.java create mode 100644 src/main/java/dev/loki/util/LocationUtil.java create mode 100644 src/main/java/dev/loki/util/PlayerUtil.java create mode 100644 src/main/java/dev/loki/util/TimeUtil.java create mode 100644 src/main/java/dev/loki/window/WindowFastVault.java diff --git a/src/main/java/dev/loki/chest/Chest.java b/src/main/java/dev/loki/chest/Chest.java index d1ac275..2cf6b49 100644 --- a/src/main/java/dev/loki/chest/Chest.java +++ b/src/main/java/dev/loki/chest/Chest.java @@ -4,7 +4,6 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; -import org.bukkit.block.Chest as BukkitChest; import java.util.UUID; @@ -57,7 +56,7 @@ public class Chest { Block block = location.getBlock(); BlockState state = block.getState(); - if (state instanceof BukkitChest chest) { + if (state instanceof org.bukkit.block.Chest chestBlock) { world.playSound(location, org.bukkit.Sound.BLOCK_CHEST_OPEN, 1.0f, 1.0f); } } diff --git a/src/main/java/dev/loki/chest/ChestManager.java b/src/main/java/dev/loki/chest/ChestManager.java index df0144f..d57134a 100644 --- a/src/main/java/dev/loki/chest/ChestManager.java +++ b/src/main/java/dev/loki/chest/ChestManager.java @@ -24,7 +24,7 @@ public class ChestManager { } private void loadChests() { - List locations = locationsConfig.getChestLocations(); + List locations = locationsConfig.getChests(); for (Location loc : locations) { Chest chest = new Chest(loc); chests.put(chest.getId(), chest); @@ -80,7 +80,7 @@ public class ChestManager { public void addChest(Location location) { Chest chest = new Chest(location); chests.put(chest.getId(), chest); - locationsConfig.addChestLocation(location); + locationsConfig.addLocation("chests", location); } public int getTotalChests() { diff --git a/src/main/java/dev/loki/gate/ExitGate.java b/src/main/java/dev/loki/gate/ExitGate.java new file mode 100644 index 0000000..310c0b7 --- /dev/null +++ b/src/main/java/dev/loki/gate/ExitGate.java @@ -0,0 +1,116 @@ +package dev.loki.gate; + +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; + +public class ExitGate { + private final Location location; + private GateState state; + private double progress; + private boolean powered; + + public enum GateState { + CLOSED, + OPENING, + OPEN + } + + public ExitGate(Location location) { + this.location = location; + this.state = GateState.CLOSED; + this.progress = 0.0; + this.powered = false; + } + + public Location getLocation() { + return location; + } + + public GateState getState() { + return state; + } + + public boolean isClosed() { + return state == GateState.CLOSED; + } + + public boolean isOpening() { + return state == GateState.OPENING; + } + + public boolean isOpen() { + return state == GateState.OPEN; + } + + public double getProgress() { + return progress; + } + + public int getProgressPercent() { + return (int) (progress * 100); + } + + public boolean isPowered() { + return powered; + } + + public void setPowered(boolean powered) { + this.powered = powered; + if (powered && state == GateState.CLOSED) { + state = GateState.OPENING; + } + } + + public void startOpening() { + if (state == GateState.CLOSED && powered) { + state = GateState.OPENING; + playOpenSound(); + } + } + + public void updateProgress(double delta) { + if (state != GateState.OPENING) return; + + progress += delta; + if (progress >= 1.0) { + progress = 1.0; + state = GateState.OPEN; + playOpenedSound(); + } + } + + public void reset() { + this.state = GateState.CLOSED; + this.progress = 0.0; + this.powered = false; + } + + public boolean isInRange(Location playerLoc, double radius) { + return location.distance(playerLoc) <= radius; + } + + public void playOpenSound() { + World world = location.getWorld(); + if (world != null) { + world.playSound(location, Sound.BLOCK_IRON_DOOR_OPEN, 1.0f, 0.8f); + } + } + + public void playOpenedSound() { + World world = location.getWorld(); + if (world != null) { + world.playSound(location, Sound.BLOCK_IRON_DOOR_CLOSE, 1.0f, 1.2f); + world.playSound(location, Sound.ENTITY_PLAYER_LEVELUP, 0.5f, 1.0f); + } + } + + public void spawnOpenParticles() { + World world = location.getWorld(); + if (world == null) return; + + world.spawnParticle(org.bukkit.Particle.PORTAL, + location.clone().add(0.5, 1.5, 0.5), + 50, 0.5, 1.0, 0.5, 0.1); + } +} diff --git a/src/main/java/dev/loki/gate/GateManager.java b/src/main/java/dev/loki/gate/GateManager.java new file mode 100644 index 0000000..923547e --- /dev/null +++ b/src/main/java/dev/loki/gate/GateManager.java @@ -0,0 +1,114 @@ +package dev.loki.gate; + +import dev.loki.config.LocationsConfig; +import org.bukkit.Location; + +import java.util.ArrayList; +import java.util.List; + +public class GateManager { + public static final int GATE_COUNT = 2; + + private final List gates; + private boolean endgameActive; + + public GateManager(LocationsConfig locations) { + this.gates = new ArrayList<>(); + this.endgameActive = false; + loadGates(locations); + } + + private void loadGates(LocationsConfig locations) { + List gateLocations = locations.getExitGates(); + for (int i = 0; i < Math.min(gateLocations.size(), GATE_COUNT); i++) { + gates.add(new ExitGate(gateLocations.get(i))); + } + } + + public void addGate(ExitGate gate) { + if (gates.size() < GATE_COUNT) { + gates.add(gate); + } + } + + public List getAllGates() { + return new ArrayList<>(gates); + } + + public ExitGate findNearestGate(Location location) { + ExitGate nearest = null; + double minDistance = Double.MAX_VALUE; + + for (ExitGate gate : gates) { + double dist = gate.getLocation().distance(location); + if (dist < minDistance) { + minDistance = dist; + nearest = gate; + } + } + return nearest; + } + + public ExitGate findNearestOpenGate(Location location) { + ExitGate nearest = null; + double minDistance = Double.MAX_VALUE; + + for (ExitGate gate : gates) { + if (!gate.isOpen()) continue; + double dist = gate.getLocation().distance(location); + if (dist < minDistance) { + minDistance = dist; + nearest = gate; + } + } + return nearest; + } + + public ExitGate getGateAt(Location location, double radius) { + for (ExitGate gate : gates) { + if (gate.isInRange(location, radius)) { + return gate; + } + } + return null; + } + + public void activateEndgame() { + endgameActive = true; + for (ExitGate gate : gates) { + gate.setPowered(true); + gate.startOpening(); + } + } + + public boolean isEndgameActive() { + return endgameActive; + } + + public void updateAllGates(double progressPerTick) { + for (ExitGate gate : gates) { + if (gate.isOpening()) { + gate.updateProgress(progressPerTick); + } + } + } + + public boolean areAllGatesOpen() { + return gates.stream().allMatch(ExitGate::isOpen); + } + + public int getOpenGateCount() { + return (int) gates.stream().filter(ExitGate::isOpen).count(); + } + + public void resetAll() { + endgameActive = false; + for (ExitGate gate : gates) { + gate.reset(); + } + } + + public int getGateCount() { + return gates.size(); + } +} diff --git a/src/main/java/dev/loki/gate/GateOpening.java b/src/main/java/dev/loki/gate/GateOpening.java new file mode 100644 index 0000000..0976f19 --- /dev/null +++ b/src/main/java/dev/loki/gate/GateOpening.java @@ -0,0 +1,147 @@ +package dev.loki.gate; + +import dev.loki.config.GameConfig; +import dev.loki.player.Survivor; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class GateOpening { + private static final double INTERACTION_RADIUS = 3.0; + + private final GateManager gateManager; + private final int openTicks; + private final Map openingPlayers; + + public GateOpening(GateManager gateManager, GameConfig config) { + this.gateManager = gateManager; + this.openTicks = config.getConfig().getInt("endgame.gate-open-seconds", 20) * 20; + this.openingPlayers = new HashMap<>(); + } + + public boolean tryStartOpening(Survivor survivor, org.bukkit.plugin.Plugin plugin) { + if (isOpening(survivor)) return false; + + Player player = survivor.getPlayer(); + if (player == null) return false; + + ExitGate gate = gateManager.getGateAt(player.getLocation(), INTERACTION_RADIUS); + if (gate == null) { + player.sendMessage("§cНет ворот поблизости!"); + return false; + } + + if (!gate.isPowered()) { + player.sendMessage("§cВорота обесточены!"); + return false; + } + + if (!gate.isClosed() && !gate.isOpening()) { + player.sendMessage("§aВорота уже открыты!"); + return false; + } + + startOpening(survivor, gate, plugin); + return true; + } + + public void stopOpening(Survivor survivor) { + OpeningProgress progress = openingPlayers.remove(survivor.getUuid()); + if (progress != null) { + Player player = survivor.getPlayer(); + if (player != null) { + player.sendMessage("§7Открытие прервано!"); + } + } + } + + public boolean isOpening(Survivor survivor) { + return openingPlayers.containsKey(survivor.getUuid()); + } + + private void startOpening(Survivor survivor, ExitGate gate, org.bukkit.plugin.Plugin plugin) { + Player player = survivor.getPlayer(); + if (player == null) return; + + openingPlayers.put(survivor.getUuid(), new OpeningProgress(gate, 0)); + player.sendMessage("§aОткрываем ворота... (зажмите ПКМ)"); + + new OpeningTask(survivor, gate, plugin).runTaskTimer(plugin, 0L, 1L); + } + + private void completeOpening(Survivor survivor, ExitGate gate) { + Player player = survivor.getPlayer(); + if (player != null) { + player.sendMessage("§aВорота открыты!"); + player.getWorld().playSound(gate.getLocation(), + Sound.BLOCK_IRON_DOOR_OPEN, 1.0f, 1.0f); + } + gate.updateProgress(1.0); + openingPlayers.remove(survivor.getUuid()); + } + + private void playOpeningEffect(Player player, ExitGate gate, int progress) { + if (progress % 20 == 0) { + player.getWorld().playSound(gate.getLocation(), + Sound.BLOCK_METAL_HIT, 0.3f, 1.0f); + } + } + + private class OpeningProgress { + final ExitGate gate; + int ticks; + + OpeningProgress(ExitGate gate, int ticks) { + this.gate = gate; + this.ticks = ticks; + } + } + + private class OpeningTask extends BukkitRunnable { + private final Survivor survivor; + private final ExitGate gate; + + OpeningTask(Survivor survivor, ExitGate gate, org.bukkit.plugin.Plugin plugin) { + this.survivor = survivor; + this.gate = gate; + } + + @Override + public void run() { + Player player = survivor.getPlayer(); + if (player == null || !player.isOnline()) { + stopOpening(survivor); + cancel(); + return; + } + + OpeningProgress progress = openingPlayers.get(survivor.getUuid()); + if (progress == null || progress.gate != gate) { + cancel(); + return; + } + + if (!gate.isInRange(player.getLocation(), INTERACTION_RADIUS)) { + stopOpening(survivor); + cancel(); + return; + } + + progress.ticks++; + playOpeningEffect(player, gate, progress.ticks); + + double progressPct = progress.ticks / (double) openTicks; + gate.updateProgress(progressPct); + + if (progress.ticks >= openTicks) { + completeOpening(survivor, gate); + cancel(); + } + } + } +} diff --git a/src/main/java/dev/loki/hatch/Hatch.java b/src/main/java/dev/loki/hatch/Hatch.java new file mode 100644 index 0000000..806b299 --- /dev/null +++ b/src/main/java/dev/loki/hatch/Hatch.java @@ -0,0 +1,125 @@ +package dev.loki.hatch; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; + +public class Hatch { + private final Location location; + private HatchState state; + private Block hatchBlock; + + public enum HatchState { + HIDDEN, + CLOSED, + OPEN + } + + public Hatch(Location location) { + this.location = location; + this.state = HatchState.HIDDEN; + this.hatchBlock = location.getBlock(); + } + + public Location getLocation() { + return location; + } + + public Location getJumpTarget() { + return location.clone().add(0.5, -2, 0.5); + } + + public HatchState getState() { + return state; + } + + public boolean isHidden() { + return state == HatchState.HIDDEN; + } + + public boolean isClosed() { + return state == HatchState.CLOSED; + } + + public boolean isOpen() { + return state == HatchState.OPEN; + } + + public void reveal() { + if (state == HatchState.HIDDEN) { + state = HatchState.CLOSED; + updateVisuals(); + playRevealEffect(); + } + } + + public void open() { + if (state == HatchState.CLOSED) { + state = HatchState.OPEN; + updateVisuals(); + playOpenEffect(); + } + } + + public void hide() { + state = HatchState.HIDDEN; + clearVisuals(); + } + + public void reset() { + state = HatchState.HIDDEN; + clearVisuals(); + } + + private void updateVisuals() { + switch (state) { + case CLOSED -> hatchBlock.setType(Material.IRON_TRAPDOOR); + case OPEN -> hatchBlock.setType(Material.AIR); + default -> hatchBlock.setType(Material.AIR); + } + } + + private void clearVisuals() { + hatchBlock.setType(Material.AIR); + } + + public boolean isInRange(Location playerLoc, double radius) { + return location.distance(playerLoc) <= radius; + } + + public boolean canEscape(Location playerLoc) { + return isOpen() && isInRange(playerLoc, 1.5); + } + + private void playRevealEffect() { + World world = location.getWorld(); + if (world == null) return; + + world.playSound(location, Sound.BLOCK_METAL_PLACE, 1.0f, 0.8f); + world.spawnParticle(org.bukkit.Particle.SMOKE, + location.clone().add(0.5, 0.5, 0.5), + 30, 0.3, 0.3, 0.3, 0.05); + } + + private void playOpenEffect() { + World world = location.getWorld(); + if (world == null) return; + + world.playSound(location, Sound.BLOCK_IRON_TRAPDOOR_OPEN, 1.0f, 1.0f); + world.spawnParticle(org.bukkit.Particle.END_ROD, + location.clone().add(0.5, 0.5, 0.5), + 20, 0.2, 0.2, 0.2, 0.02); + } + + public void playEscapeEffect() { + World world = location.getWorld(); + if (world == null) return; + + world.playSound(location, Sound.ENTITY_PLAYER_SPLASH, 1.0f, 1.0f); + world.spawnParticle(org.bukkit.Particle.PORTAL, + location.clone().add(0.5, 0, 0.5), + 100, 0.5, 0.5, 0.5, 0.5); + } +} diff --git a/src/main/java/dev/loki/hatch/HatchCondition.java b/src/main/java/dev/loki/hatch/HatchCondition.java new file mode 100644 index 0000000..1e9406d --- /dev/null +++ b/src/main/java/dev/loki/hatch/HatchCondition.java @@ -0,0 +1,76 @@ +package dev.loki.hatch; + +import dev.loki.player.BasePlayer; +import dev.loki.player.PlayerState; + +import java.util.Collection; + +public class HatchCondition { + private final int minSurvivorsForOpen; + + public HatchCondition(int minSurvivorsForOpen) { + this.minSurvivorsForOpen = minSurvivorsForOpen; + } + + public boolean shouldSpawn(Collection players, int repairedGenerators) { + int aliveSurvivors = countAliveSurvivors(players); + return aliveSurvivors <= repairedGenerators; + } + + public boolean shouldOpen(Hatch hatch, Collection players, int repairedGenerators) { + if (hatch.isOpen()) return false; + if (!hatch.isClosed() && !hatch.isHidden()) return false; + + return shouldSpawn(players, repairedGenerators); + } + + public boolean canOpen(Hatch hatch, Collection players, int repairedGenerators) { + if (!hatch.isHidden() && !hatch.isClosed()) return false; + + int aliveSurvivors = countAliveSurvivors(players); + int requiredGens = Math.max(minSurvivorsForOpen, aliveSurvivors); + + return repairedGenerators >= requiredGens; + } + + public int countAliveSurvivors(Collection players) { + return (int) players.stream() + .filter(p -> !p.isKiller()) + .filter(this::isAlive) + .count(); + } + + private boolean isAlive(BasePlayer player) { + PlayerState state = player.getState(); + return state != PlayerState.DEAD + && state != PlayerState.ESCAPED + && state != PlayerState.HOOKED; + } + + public int getRemainingAlive(Collection players) { + return countAliveSurvivors(players); + } + + public boolean isLastSurvivor(Collection players) { + return countAliveSurvivors(players) == 1; + } + + public boolean checkAndReveal(Hatch hatch, Collection players, int repairedGenerators) { + if (hatch.isHidden() && shouldSpawn(players, repairedGenerators)) { + hatch.reveal(); + return true; + } + return false; + } + + public boolean checkAndOpen(Hatch hatch, Collection players, int repairedGenerators) { + if (canOpen(hatch, players, repairedGenerators)) { + if (hatch.isHidden()) { + hatch.reveal(); + } + hatch.open(); + return true; + } + return false; + } +} diff --git a/src/main/java/dev/loki/hatch/HatchEscape.java b/src/main/java/dev/loki/hatch/HatchEscape.java new file mode 100644 index 0000000..1af96c6 --- /dev/null +++ b/src/main/java/dev/loki/hatch/HatchEscape.java @@ -0,0 +1,110 @@ +package dev.loki.hatch; + +import dev.loki.player.PlayerState; +import dev.loki.player.Survivor; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.entity.Player; + +public class HatchEscape { + private static final double ESCAPE_RADIUS = 1.5; + + private final Hatch hatch; + + public HatchEscape(Hatch hatch) { + this.hatch = hatch; + } + + public boolean tryEscape(Survivor survivor) { + if (!hatch.isOpen()) { + return tryRevealAndCheck(survivor); + } + + Player player = survivor.getPlayer(); + if (player == null) return false; + + if (!isAtHatch(player.getLocation())) { + player.sendMessage("§cПодойдите ближе к люку!"); + return false; + } + + performEscape(survivor); + return true; + } + + public boolean canEscape(Survivor survivor) { + if (!hatch.isOpen()) return false; + + Player player = survivor.getPlayer(); + if (player == null) return false; + + return isAtHatch(player.getLocation()); + } + + private boolean tryRevealAndCheck(Survivor survivor) { + Player player = survivor.getPlayer(); + if (player == null) return false; + + if (hatch.isHidden()) { + player.sendMessage("§7Люк скрыт..."); + return false; + } + + if (hatch.isClosed()) { + player.sendMessage("§cЛюк закрыт!"); + return false; + } + + return false; + } + + private boolean isAtHatch(Location location) { + return hatch.isInRange(location, ESCAPE_RADIUS); + } + + private void performEscape(Survivor survivor) { + Player player = survivor.getPlayer(); + if (player == null) return; + + player.teleport(hatch.getJumpTarget()); + hatch.playEscapeEffect(); + + player.getWorld().playSound(player.getLocation(), + Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f); + player.sendTitle("§a§lПОБЕГ!", "§7Вы сбежали через люк!", 10, 60, 10); + + survivor.setState(PlayerState.ESCAPED); + + giveEscapeReward(survivor); + } + + private void giveEscapeReward(Survivor survivor) { + Player player = survivor.getPlayer(); + if (player == null) return; + + player.sendMessage("§a+5000 Очков — Побег через люк!"); + } + + public void forceOpenForEscape() { + if (hatch.isHidden()) { + hatch.reveal(); + } + if (!hatch.isOpen()) { + hatch.open(); + } + } + + public void notifyNearbyPlayers() { + Location hatchLoc = hatch.getLocation(); + World world = hatchLoc.getWorld(); + if (world == null) return; + + for (Player player : world.getPlayers()) { + if (player.getLocation().distance(hatchLoc) < 30) { + player.sendMessage("§e§lЛюк открыт!"); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f); + } + } + } +} diff --git a/src/main/java/dev/loki/hook/HookManager.java b/src/main/java/dev/loki/hook/HookManager.java index b409837..ba8ae08 100644 --- a/src/main/java/dev/loki/hook/HookManager.java +++ b/src/main/java/dev/loki/hook/HookManager.java @@ -20,7 +20,7 @@ public class HookManager { public void loadHooks(LocationsConfig config) { hooks.clear(); - List hookLocations = config.getHookLocations(); + List hookLocations = config.getHooks(); for (int i = 0; i < hookLocations.size(); i++) { Location loc = hookLocations.get(i); boolean basement = i < 4; diff --git a/src/main/java/dev/loki/hook/HookSacrifice.java b/src/main/java/dev/loki/hook/HookSacrifice.java index fe76842..f50d399 100644 --- a/src/main/java/dev/loki/hook/HookSacrifice.java +++ b/src/main/java/dev/loki/hook/HookSacrifice.java @@ -4,6 +4,8 @@ import dev.loki.mdbd; 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 net.kyori.adventure.title.Title; import org.bukkit.Sound; import org.bukkit.entity.Player; @@ -39,8 +41,8 @@ public class HookSacrifice { ); Title title = Title.title( - net.kyori.adventure.text.Component.text("☠").color(net.kyori.adventure.text.NamedTextColor.DARK_RED), - net.kyori.adventure.text.Component.text("You have been sacrificed").color(net.kyori.adventure.text.NamedTextColor.RED), + Component.text("☠").color(NamedTextColor.DARK_RED), + Component.text("You have been sacrificed").color(NamedTextColor.RED), times ); @@ -58,8 +60,8 @@ public class HookSacrifice { final String finalName = name; plugin.getServer().broadcast( - net.kyori.adventure.text.Component.text(finalName + " has been sacrificed to the Entity") - .color(net.kyori.adventure.text.NamedTextColor.DARK_RED) + Component.text(finalName + " has been sacrificed to the Entity") + .color(NamedTextColor.DARK_RED) ); } diff --git a/src/main/java/dev/loki/killer/KillerVision.java b/src/main/java/dev/loki/killer/KillerVision.java index 11272d7..60a3237 100644 --- a/src/main/java/dev/loki/killer/KillerVision.java +++ b/src/main/java/dev/loki/killer/KillerVision.java @@ -3,6 +3,7 @@ package dev.loki.killer; import dev.loki.player.Killer; import org.bukkit.Location; import org.bukkit.Particle; +import org.bukkit.Particle.DustOptions; import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -66,12 +67,12 @@ public class KillerVision extends BukkitRunnable { particleLoc.setY(particleLoc.getY() - 0.5); world.spawnParticle( - Particle.REDSTONE, + Particle.DUST, particleLoc, 1, 0, 0, 0, 0, - new Particle.DustOptions(org.bukkit.Color.RED, 1.0f) + new DustOptions(org.bukkit.Color.RED, 1.0f) ); } @@ -97,12 +98,12 @@ public class KillerVision extends BukkitRunnable { ); world.spawnParticle( - Particle.REDSTONE, + Particle.DUST, location, 15, 0.3, 0.3, 0.3, 0, - new Particle.DustOptions(org.bukkit.Color.MAROON, 1.5f) + new DustOptions(org.bukkit.Color.MAROON, 1.5f) ); } diff --git a/src/main/java/dev/loki/listener/DamageListener.java b/src/main/java/dev/loki/listener/DamageListener.java new file mode 100644 index 0000000..ff9fdeb --- /dev/null +++ b/src/main/java/dev/loki/listener/DamageListener.java @@ -0,0 +1,116 @@ +package dev.loki.listener; + +import dev.loki.core.GameManager; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.plugin.Plugin; + +import java.util.EnumSet; +import java.util.Set; + +public class DamageListener implements Listener { + private final Plugin plugin; + private final GameManager gameManager; + + private static final Set ALLOWED_CAUSES = EnumSet.of( + EntityDamageEvent.DamageCause.CUSTOM, + EntityDamageEvent.DamageCause.VOID + ); + + private static final Set BLOCKED_CAUSES = EnumSet.of( + EntityDamageEvent.DamageCause.FALL, + EntityDamageEvent.DamageCause.FIRE, + EntityDamageEvent.DamageCause.FIRE_TICK, + EntityDamageEvent.DamageCause.LAVA, + EntityDamageEvent.DamageCause.DROWNING, + EntityDamageEvent.DamageCause.STARVATION, + EntityDamageEvent.DamageCause.SUFFOCATION, + EntityDamageEvent.DamageCause.CONTACT, + EntityDamageEvent.DamageCause.BLOCK_EXPLOSION, + EntityDamageEvent.DamageCause.ENTITY_EXPLOSION, + EntityDamageEvent.DamageCause.LIGHTNING, + EntityDamageEvent.DamageCause.POISON, + EntityDamageEvent.DamageCause.WITHER, + EntityDamageEvent.DamageCause.MAGIC, + EntityDamageEvent.DamageCause.THORNS, + EntityDamageEvent.DamageCause.DRAGON_BREATH, + EntityDamageEvent.DamageCause.FLY_INTO_WALL, + EntityDamageEvent.DamageCause.HOT_FLOOR, + EntityDamageEvent.DamageCause.CRAMMING, + EntityDamageEvent.DamageCause.DRYOUT, + EntityDamageEvent.DamageCause.FREEZE, + EntityDamageEvent.DamageCause.SONIC_BOOM, + EntityDamageEvent.DamageCause.KILL, + EntityDamageEvent.DamageCause.WORLD_BORDER + ); + + public DamageListener(Plugin plugin, GameManager gameManager) { + this.plugin = plugin; + this.gameManager = gameManager; + } + + @EventHandler(priority = EventPriority.HIGH) + public void onEntityDamage(EntityDamageEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof Player player)) return; + + if (!gameManager.isInGame(player.getUniqueId())) { + return; + } + + EntityDamageEvent.DamageCause cause = event.getCause(); + + if (ALLOWED_CAUSES.contains(cause)) { + return; + } + + if (BLOCKED_CAUSES.contains(cause)) { + event.setCancelled(true); + return; + } + + if (cause == EntityDamageEvent.DamageCause.ENTITY_ATTACK) { + return; + } + + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onFoodLevelChange(FoodLevelChangeEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof Player player)) return; + + if (gameManager.isInGame(player.getUniqueId())) { + event.setCancelled(true); + player.setFoodLevel(20); + player.setSaturation(20); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onVoidDamage(EntityDamageEvent event) { + if (event.getCause() != EntityDamageEvent.DamageCause.VOID) return; + + Entity entity = event.getEntity(); + if (!(entity instanceof Player player)) return; + + if (gameManager.isInGame(player.getUniqueId())) { + player.teleport(player.getWorld().getSpawnLocation()); + event.setCancelled(true); + } + } + + public void register() { + org.bukkit.Bukkit.getPluginManager().registerEvents(this, plugin); + } + + public void unregister() { + org.bukkit.event.HandlerList.unregisterAll(this); + } +} diff --git a/src/main/java/dev/loki/listener/PlayerInteractListener.java b/src/main/java/dev/loki/listener/PlayerInteractListener.java new file mode 100644 index 0000000..67b1fa5 --- /dev/null +++ b/src/main/java/dev/loki/listener/PlayerInteractListener.java @@ -0,0 +1,174 @@ +package dev.loki.listener; + +import dev.loki.core.GameManager; +import dev.loki.player.BasePlayer; +import dev.loki.player.PlayerState; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.plugin.Plugin; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; + +public class PlayerInteractListener implements Listener { + private final Plugin plugin; + private final GameManager gameManager; + + private BiConsumer onGeneratorInteract; + private BiConsumer onAttack; + private BiConsumer onPalletInteract; + private BiConsumer onWindowInteract; + private BiConsumer onRescueAttempt; + private BiConsumer onChestInteract; + private BiConsumer onHatchInteract; + + private final Map lastRightClick = new HashMap<>(); + private final Map lastLeftClick = new HashMap<>(); + private static final long CLICK_COOLDOWN_MS = 200; + + public PlayerInteractListener(Plugin plugin, GameManager gameManager) { + this.plugin = plugin; + this.gameManager = gameManager; + } + + public void setOnGeneratorInteract(BiConsumer handler) { + this.onGeneratorInteract = handler; + } + + public void setOnAttack(BiConsumer handler) { + this.onAttack = handler; + } + + public void setOnPalletInteract(BiConsumer handler) { + this.onPalletInteract = handler; + } + + public void setOnWindowInteract(BiConsumer handler) { + this.onWindowInteract = handler; + } + + public void setOnRescueAttempt(BiConsumer handler) { + this.onRescueAttempt = handler; + } + + public void setOnChestInteract(BiConsumer handler) { + this.onChestInteract = handler; + } + + public void setOnHatchInteract(BiConsumer handler) { + this.onHatchInteract = handler; + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND) return; + + Player player = event.getPlayer(); + if (!gameManager.isInGame(player.getUniqueId())) return; + + BasePlayer basePlayer = gameManager.getPlayerSession(player.getUniqueId()).getPlayer(player.getUniqueId()); + if (basePlayer == null) return; + + Action action = event.getAction(); + boolean isRightClick = action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; + boolean isLeftClick = action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; + + if (basePlayer.getState() == PlayerState.DOWNED || basePlayer.getState() == PlayerState.HOOKED) { + event.setCancelled(true); + return; + } + + if (isRightClick) { + if (isOnCooldown(player.getUniqueId(), lastRightClick)) { + event.setCancelled(true); + return; + } + handleRightClick(event, player, basePlayer); + } else if (isLeftClick) { + if (isOnCooldown(player.getUniqueId(), lastLeftClick)) { + return; + } + handleLeftClick(event, player, basePlayer); + } + } + + private boolean isOnCooldown(UUID uuid, Map lastClickMap) { + long now = System.currentTimeMillis(); + Long last = lastClickMap.get(uuid); + if (last != null && now - last < CLICK_COOLDOWN_MS) { + return true; + } + lastClickMap.put(uuid, now); + return false; + } + + private void handleRightClick(PlayerInteractEvent event, Player player, BasePlayer basePlayer) { + if (event.getClickedBlock() != null) { + String blockType = event.getClickedBlock().getType().toString(); + + if (blockType.contains("IRON_TRAPDOOR") || blockType.contains("TRAP_DOOR")) { + if (onGeneratorInteract != null) { + onGeneratorInteract.accept(player, event.getAction()); + } + } else if (blockType.contains("FENCE") || blockType.contains("OAK_PLANKS")) { + if (onPalletInteract != null && !basePlayer.isKiller()) { + onPalletInteract.accept(player, true); + } + } + + if (!basePlayer.isKiller()) { + if (onRescueAttempt != null) { + onRescueAttempt.accept(player, true); + } + } + + if (blockType.contains("CHEST")) { + if (onChestInteract != null) { + onChestInteract.accept(player, true); + } + } + + if (blockType.contains("HOPPER") || blockType.contains("END_PORTAL_FRAME")) { + if (onHatchInteract != null && !basePlayer.isKiller()) { + onHatchInteract.accept(player, true); + } + } + } + + if (onAttack != null && basePlayer.isKiller()) { + onAttack.accept(player, true); + } + } + + private void handleLeftClick(PlayerInteractEvent event, Player player, BasePlayer basePlayer) { + if (event.getClickedBlock() != null) { + String blockType = event.getClickedBlock().getType().toString(); + + if (blockType.contains("OAK_PLANKS") || blockType.contains("FENCE")) { + if (onPalletInteract != null && basePlayer.isKiller()) { + event.setCancelled(true); + onPalletInteract.accept(player, false); + } + } + } + + if (onAttack != null && basePlayer.isKiller()) { + event.setCancelled(true); + onAttack.accept(player, false); + } + } + + public void register() { + org.bukkit.Bukkit.getPluginManager().registerEvents(this, plugin); + } + + public void unregister() { + org.bukkit.event.HandlerList.unregisterAll(this); + } +} diff --git a/src/main/java/dev/loki/listener/PlayerMoveListener.java b/src/main/java/dev/loki/listener/PlayerMoveListener.java new file mode 100644 index 0000000..002e3d4 --- /dev/null +++ b/src/main/java/dev/loki/listener/PlayerMoveListener.java @@ -0,0 +1,196 @@ +package dev.loki.listener; + +import dev.loki.core.GameManager; +import dev.loki.player.BasePlayer; +import dev.loki.player.Killer; +import dev.loki.player.PlayerState; +import dev.loki.player.Survivor; +import dev.loki.util.PlayerUtil; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.player.PlayerToggleSprintEvent; +import org.bukkit.plugin.Plugin; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; + +public class PlayerMoveListener implements Listener { + private final Plugin plugin; + private final GameManager gameManager; + + private BiConsumer onMovement; + private BiConsumer onCrouch; + private BiConsumer onSprint; + private BiConsumer onSurvivorMove; + private BiConsumer onKillerMove; + + private final Map lastLocations = new HashMap<>(); + private final Map wasSneaking = new HashMap<>(); + private final Map wasSprinting = new HashMap<>(); + + private static final double MOVEMENT_THRESHOLD = 0.01; + private int tickCounter = 0; + private static final int CHECK_INTERVAL = 2; + + public PlayerMoveListener(Plugin plugin, GameManager gameManager) { + this.plugin = plugin; + this.gameManager = gameManager; + } + + public void setOnMovement(BiConsumer handler) { + this.onMovement = handler; + } + + public void setOnCrouch(BiConsumer handler) { + this.onCrouch = handler; + } + + public void setOnSprint(BiConsumer handler) { + this.onSprint = handler; + } + + public void setOnSurvivorMove(BiConsumer handler) { + this.onSurvivorMove = handler; + } + + public void setOnKillerMove(BiConsumer handler) { + this.onKillerMove = handler; + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + tickCounter++; + if (tickCounter % CHECK_INTERVAL != 0) return; + + Player player = event.getPlayer(); + if (!gameManager.isInGame(player.getUniqueId())) return; + + BasePlayer basePlayer = gameManager.getPlayerSession(player.getUniqueId()).getPlayer(player.getUniqueId()); + if (basePlayer == null) return; + + Location from = event.getFrom(); + Location to = event.getTo(); + + if (to == null) return; + + if (shouldCancelMovement(basePlayer, from, to)) { + event.setCancelled(true); + return; + } + + if (hasSignificantMovement(from, to)) { + handleMovement(player, basePlayer, to); + } + + updateStateTracking(player.getUniqueId(), from); + } + + @EventHandler + public void onPlayerToggleSneak(PlayerToggleSneakEvent event) { + Player player = event.getPlayer(); + if (!gameManager.isInGame(player.getUniqueId())) return; + + boolean isSneaking = event.isSneaking(); + UUID uuid = player.getUniqueId(); + + Boolean previous = wasSneaking.get(uuid); + if (previous == null || previous != isSneaking) { + wasSneaking.put(uuid, isSneaking); + + if (onCrouch != null) { + onCrouch.accept(player, isSneaking); + } + } + } + + @EventHandler + public void onPlayerToggleSprint(PlayerToggleSprintEvent event) { + Player player = event.getPlayer(); + if (!gameManager.isInGame(player.getUniqueId())) return; + + BasePlayer basePlayer = gameManager.getPlayerSession(player.getUniqueId()).getPlayer(player.getUniqueId()); + if (basePlayer == null) return; + + boolean isSprinting = event.isSprinting(); + UUID uuid = player.getUniqueId(); + + if (basePlayer.getState() == PlayerState.DOWNED || basePlayer.getState() == PlayerState.HOOKED) { + if (isSprinting) { + event.setCancelled(true); + player.setSprinting(false); + } + return; + } + + Boolean previous = wasSprinting.get(uuid); + if (previous == null || previous != isSprinting) { + wasSprinting.put(uuid, isSprinting); + + if (onSprint != null) { + onSprint.accept(player, isSprinting); + } + } + } + + private boolean shouldCancelMovement(BasePlayer player, Location from, Location to) { + if (player.getState() == PlayerState.HOOKED) { + return true; + } + + if (player.getState() == PlayerState.DOWNED) { + double dx = to.getX() - from.getX(); + double dz = to.getZ() - from.getZ(); + double horizontalDistance = Math.sqrt(dx * dx + dz * dz); + + double maxCrawlDistance = 0.08; + return horizontalDistance > maxCrawlDistance; + } + + return false; + } + + private boolean hasSignificantMovement(Location from, Location to) { + double dx = to.getX() - from.getX(); + double dy = to.getY() - from.getY(); + double dz = to.getZ() - from.getZ(); + double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + + return distance > MOVEMENT_THRESHOLD; + } + + private void handleMovement(Player player, BasePlayer basePlayer, Location to) { + if (onMovement != null) { + onMovement.accept(player, to); + } + + if (!basePlayer.isKiller() && onSurvivorMove != null) { + onSurvivorMove.accept((Survivor) basePlayer, to); + } else if (basePlayer.isKiller() && onKillerMove != null) { + onKillerMove.accept((Killer) basePlayer, to); + } + } + + private void updateStateTracking(UUID uuid, Location location) { + lastLocations.put(uuid, location.clone()); + } + + public void clearTracking(UUID uuid) { + lastLocations.remove(uuid); + wasSneaking.remove(uuid); + wasSprinting.remove(uuid); + } + + public void register() { + org.bukkit.Bukkit.getPluginManager().registerEvents(this, plugin); + } + + public void unregister() { + org.bukkit.event.HandlerList.unregisterAll(this); + } +} diff --git a/src/main/java/dev/loki/listener/PlayerQuitListener.java b/src/main/java/dev/loki/listener/PlayerQuitListener.java new file mode 100644 index 0000000..6b7894b --- /dev/null +++ b/src/main/java/dev/loki/listener/PlayerQuitListener.java @@ -0,0 +1,103 @@ +package dev.loki.listener; + +import dev.loki.core.GameManager; +import dev.loki.core.GameSession; +import dev.loki.player.BasePlayer; +import dev.loki.player.Killer; +import dev.loki.player.PlayerState; +import dev.loki.player.Survivor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; + +import java.util.UUID; + +public class PlayerQuitListener implements Listener { + private final Plugin plugin; + private final GameManager gameManager; + + public PlayerQuitListener(Plugin plugin, GameManager gameManager) { + this.plugin = plugin; + this.gameManager = gameManager; + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + if (!gameManager.isInGame(uuid)) { + return; + } + + GameSession session = gameManager.getPlayerSession(uuid); + BasePlayer basePlayer = session.getPlayer(uuid); + + if (basePlayer == null) { + gameManager.leaveGame(player); + return; + } + + if (basePlayer.isKiller()) { + handleKillerDisconnect(session, (Killer) basePlayer); + } else { + handleSurvivorDisconnect(session, (Survivor) basePlayer); + } + + session.removePlayer(uuid); + gameManager.leaveGame(player); + } + + private void handleKillerDisconnect(GameSession session, Killer killer) { + Bukkit.broadcast(Component.text( + "[DBD] Киллер покинул игру. Игра завершена.", + NamedTextColor.RED)); + session.stop(); + } + + private void handleSurvivorDisconnect(GameSession session, Survivor survivor) { + survivor.setState(PlayerState.DEAD); + + Player player = survivor.getPlayer(); + if (player != null && player.isOnline()) { + player.sendMessage(Component.text( + "[DBD] Вы покинули игру и считаетесь мертвым.", + NamedTextColor.RED)); + } + + Bukkit.broadcast(Component.text( + "[DBD] Выживший покинул игру. Осталось живых: " + session.getPlayers().size(), + NamedTextColor.YELLOW)); + + checkEndgameConditions(session); + } + + private void checkEndgameConditions(GameSession session) { + int livingSurvivors = 0; + for (BasePlayer player : session.getPlayers().values()) { + if (!player.isKiller() && player.getState() != PlayerState.DEAD + && player.getState() != PlayerState.ESCAPED) { + livingSurvivors++; + } + } + + if (livingSurvivors == 0) { + Bukkit.broadcast(Component.text( + "[DBD] Все выжившие мертвы. Киллер победил!", + NamedTextColor.RED)); + } + } + + public void register() { + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + public void unregister() { + org.bukkit.event.HandlerList.unregisterAll(this); + } +} diff --git a/src/main/java/dev/loki/pallet/PalletDrop.java b/src/main/java/dev/loki/pallet/PalletDrop.java index 4be844e..90e516c 100644 --- a/src/main/java/dev/loki/pallet/PalletDrop.java +++ b/src/main/java/dev/loki/pallet/PalletDrop.java @@ -51,7 +51,7 @@ public class PalletDrop { pallet.setDropped(); player.getWorld().playSound(pallet.getLocation(), Sound.BLOCK_WOOD_BREAK, 1.0f, 0.8f); - player.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_DUST, + player.getWorld().spawnParticle(org.bukkit.Particle.BLOCK, pallet.getCenter(), 20, 0.5, 0.5, 0.5, 0.1, org.bukkit.Material.OAK_PLANKS.createBlockData()); @@ -61,7 +61,7 @@ public class PalletDrop { private void applyExhaustion(Player player) { player.addPotionEffect(new PotionEffect( - PotionEffectType.SLOW, 20, 2, false, false)); + PotionEffectType.SLOWNESS, 20, 2, false, false)); } public boolean isNearDroppedPallet(Player player) { diff --git a/src/main/java/dev/loki/pallet/PalletStun.java b/src/main/java/dev/loki/pallet/PalletStun.java index c8daae3..97aa33a 100644 --- a/src/main/java/dev/loki/pallet/PalletStun.java +++ b/src/main/java/dev/loki/pallet/PalletStun.java @@ -34,7 +34,7 @@ public class PalletStun { stunnedKillers.add(killer.getUuid()); player.addPotionEffect(new PotionEffect( - PotionEffectType.SLOW, stunTicks, 10, false, false)); + PotionEffectType.SLOWNESS, stunTicks, 10, false, false)); player.addPotionEffect(new PotionEffect( PotionEffectType.BLINDNESS, stunTicks, 1, false, false)); diff --git a/src/main/java/dev/loki/phase/EndgamePhase.java b/src/main/java/dev/loki/phase/EndgamePhase.java index dba08a9..c9c3e50 100644 --- a/src/main/java/dev/loki/phase/EndgamePhase.java +++ b/src/main/java/dev/loki/phase/EndgamePhase.java @@ -1,31 +1,252 @@ package dev.loki.phase; import dev.loki.core.GameSession; +import dev.loki.gate.GateManager; +import dev.loki.gate.GateOpening; +import dev.loki.hatch.Hatch; +import dev.loki.hatch.HatchCondition; +import dev.loki.hatch.HatchEscape; +import dev.loki.player.BasePlayer; +import dev.loki.player.Killer; +import dev.loki.player.PlayerState; +import dev.loki.player.Survivor; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.ArrayList; +import java.util.List; public class EndgamePhase implements Phase { - private int collapseTicks; + private final int collapseSeconds; + private final int openTicks; + + private int remainingTicks; private boolean complete; + private boolean gatesActivated; + private GateManager gateManager; + private GateOpening gateOpening; + private Hatch hatch; + private HatchCondition hatchCondition; + private HatchEscape hatchEscape; + + public EndgamePhase() { + this.collapseSeconds = 120; + this.openTicks = 20 * 20; + } + + public EndgamePhase(int collapseSeconds, GateManager gateManager, + Hatch hatch, HatchCondition hatchCondition) { + this.collapseSeconds = collapseSeconds; + this.openTicks = 20 * 20; + this.gateManager = gateManager; + this.hatch = hatch; + this.hatchCondition = hatchCondition; + this.hatchEscape = new HatchEscape(hatch); + } @Override public void enter(GameSession session) { - collapseTicks = session.getConfig().getGame().getInt("endgame.collapse-seconds", 120) * 20; - complete = false; + this.remainingTicks = collapseSeconds * 20; + this.complete = false; + this.gatesActivated = false; + + activateEndgame(session); + announceEndgameStart(session); + } + + private void activateEndgame(GameSession session) { + if (gateManager != null && !gatesActivated) { + gateManager.activateEndgame(); + gatesActivated = true; + } + + checkHatchConditions(session); + + session.getPlugin().getLogger().info("Endgame started!"); + } + + private void checkHatchConditions(GameSession session) { + if (hatch == null || hatchCondition == null) return; + + int repairedGens = countRepairedGenerators(session); + + hatchCondition.checkAndReveal(hatch, session.getPlayers().values(), repairedGens); + + if (isLastSurvivor(session)) { + hatchCondition.checkAndOpen(hatch, session.getPlayers().values(), repairedGens); + if (hatch.isOpen()) { + hatchEscape.notifyNearbyPlayers(); + } + } + } + + private boolean isLastSurvivor(GameSession session) { + if (hatchCondition == null) return false; + return hatchCondition.isLastSurvivor(session.getPlayers().values()); + } + + private int countRepairedGenerators(GameSession session) { + return 5; + } + + private void announceEndgameStart(GameSession session) { + for (BasePlayer player : session.getPlayers().values()) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer != null) { + bukkitPlayer.sendTitle("§4§lЭНДГЕЙМ!", + "§cКоллапс через 2 минуты!", 10, 70, 20); + bukkitPlayer.playSound(bukkitPlayer.getLocation(), + Sound.ENTITY_WITHER_SPAWN, 1.0f, 0.8f); + } + } } @Override public void tick(GameSession session) { - collapseTicks--; - if (collapseTicks <= 0) { + remainingTicks--; + int secondsLeft = remainingTicks / 20; + + updateGates(session); + updateHatch(session); + checkWinConditions(session); + sendCountdown(session, secondsLeft); + + if (remainingTicks <= 0) { + triggerCollapse(session); complete = true; } } + private void updateGates(GameSession session) { + if (gateManager == null) return; + gateManager.updateAllGates(1.0 / openTicks); + } + + private void updateHatch(GameSession session) { + if (hatch == null || !hatch.isOpen()) return; + + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + if (player.getState() != PlayerState.HEALTHY && + player.getState() != PlayerState.INJURED) continue; + + if (hatchEscape.canEscape((Survivor) player)) { + hatchEscape.tryEscape((Survivor) player); + } + } + } + + private void checkWinConditions(GameSession session) { + boolean allEscapedOrDead = true; + int survivorsAlive = 0; + + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + + PlayerState state = player.getState(); + if (state != PlayerState.DEAD && state != PlayerState.ESCAPED) { + allEscapedOrDead = false; + survivorsAlive++; + } + } + + if (allEscapedOrDead || survivorsAlive == 0) { + complete = true; + } + } + + private void sendCountdown(GameSession session, int secondsLeft) { + if (secondsLeft > 0 && secondsLeft <= 10) { + for (BasePlayer player : session.getPlayers().values()) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer != null) { + bukkitPlayer.sendTitle("§4" + secondsLeft, "", 0, 25, 0); + bukkitPlayer.playSound(bukkitPlayer.getLocation(), + Sound.BLOCK_NOTE_BLOCK_HAT, 0.5f, 1.0f); + } + } + } + } + + private void triggerCollapse(GameSession session) { + for (BasePlayer player : session.getPlayers().values()) { + if (player.getState() == PlayerState.DEAD || + player.getState() == PlayerState.ESCAPED) continue; + + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) continue; + + if (!player.isKiller()) { + bukkitPlayer.setHealth(0); + player.setState(PlayerState.DEAD); + } else { + ((Killer) player).setCarrying(false); + } + + bukkitPlayer.sendTitle("§4§lКОЛЛАПС!", + "§cКарта разрушена!", 10, 70, 20); + } + } + @Override public void exit(GameSession session) { + announceEndgameEnd(session); + + if (hatch != null) { + hatch.reset(); + } + + new BukkitRunnable() { + @Override + public void run() { + session.stop(); + } + }.runTaskLater(session.getPlugin(), 100L); + } + + private void announceEndgameEnd(GameSession session) { + List results = new ArrayList<>(); + results.add("§6§l=== РЕЗУЛЬТАТЫ ==="); + + int escaped = 0; + int died = 0; + + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + + PlayerState state = player.getState(); + Player bukkitPlayer = player.getPlayer(); + + String name = bukkitPlayer != null ? bukkitPlayer.getName() : "Unknown"; + + if (state == PlayerState.ESCAPED) { + results.add("§a✔ " + name + " — Сбежал"); + escaped++; + } else { + results.add("§c✘ " + name + " — Погиб"); + died++; + } + } + + results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died); + + String resultMessage = String.join("\n", results); + + for (BasePlayer player : session.getPlayers().values()) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer != null) { + bukkitPlayer.sendMessage(resultMessage); + } + } } @Override public boolean isComplete() { return complete; } + + public int getRemainingSeconds() { + return remainingTicks / 20; + } } diff --git a/src/main/java/dev/loki/util/LocationUtil.java b/src/main/java/dev/loki/util/LocationUtil.java new file mode 100644 index 0000000..24cc89d --- /dev/null +++ b/src/main/java/dev/loki/util/LocationUtil.java @@ -0,0 +1,138 @@ +package dev.loki.util; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; + +public final class LocationUtil { + + private LocationUtil() {} + + public static double distance(Location loc1, Location loc2) { + if (loc1 == null || loc2 == null) return Double.MAX_VALUE; + if (!loc1.getWorld().equals(loc2.getWorld())) return Double.MAX_VALUE; + return loc1.distance(loc2); + } + + public static double distanceSquared(Location loc1, Location loc2) { + if (loc1 == null || loc2 == null) return Double.MAX_VALUE; + if (!loc1.getWorld().equals(loc2.getWorld())) return Double.MAX_VALUE; + return loc1.distanceSquared(loc2); + } + + public static double distanceHorizontal(Location loc1, Location loc2) { + if (loc1 == null || loc2 == null) return Double.MAX_VALUE; + if (!loc1.getWorld().equals(loc2.getWorld())) return Double.MAX_VALUE; + + double dx = loc1.getX() - loc2.getX(); + double dz = loc1.getZ() - loc2.getZ(); + return Math.sqrt(dx * dx + dz * dz); + } + + public static boolean isWithinRange(Location loc1, Location loc2, double range) { + return distanceSquared(loc1, loc2) <= range * range; + } + + public static boolean hasLineOfSight(Location from, Location to, double maxDistance) { + if (from == null || to == null) return false; + if (!from.getWorld().equals(to.getWorld())) return false; + if (distanceSquared(from, to) > maxDistance * maxDistance) return false; + + Vector direction = to.toVector().subtract(from.toVector()).normalize(); + double distance = from.distance(to); + + Location checkLoc = from.clone(); + for (double d = 0; d < distance; d += 0.5) { + checkLoc.add(direction.clone().multiply(0.5)); + Block block = checkLoc.getBlock(); + if (block.getType().isSolid() && !isTransparent(block)) { + return false; + } + } + + return true; + } + + public static boolean hasEntityLineOfSight(Entity from, Entity to, double maxDistance) { + if (from == null || to == null) return false; + return from.hasLineOfSight(to) && distanceSquared(from.getLocation(), to.getLocation()) <= maxDistance * maxDistance; + } + + public static boolean isLookingAt(Entity entity, Location target, double angleThreshold) { + if (entity == null || target == null) return false; + + Location eyeLocation = getEyeLocation(entity); + Vector direction = eyeLocation.getDirection(); + Vector toTarget = target.toVector().subtract(eyeLocation.toVector()).normalize(); + + double angle = Math.toDegrees(direction.angle(toTarget)); + return angle <= angleThreshold; + } + + public static Location getEyeLocation(Entity entity) { + Location loc = entity.getLocation().clone(); + loc.setY(loc.getY() + entity.getHeight() * 0.85); + return loc; + } + + public static Location getCenter(Location location) { + return location.clone().add(0.5, 0.5, 0.5); + } + + public static Location getCenterAbove(Location location) { + return location.clone().add(0.5, 1.0, 0.5); + } + + public static Vector getDirectionTo(Location from, Location to) { + return to.toVector().subtract(from.toVector()).normalize(); + } + + public static float getYawTo(Location from, Location to) { + Vector direction = getDirectionTo(from, to); + double dx = direction.getX(); + double dz = direction.getZ(); + return (float) Math.toDegrees(Math.atan2(-dx, dz)); + } + + public static boolean isInFront(Entity entity, Location target) { + Location loc = entity.getLocation(); + Vector direction = loc.getDirection(); + Vector toTarget = target.toVector().subtract(loc.toVector()); + + return direction.dot(toTarget) > 0; + } + + public static Location[] getCirclePoints(Location center, double radius, int points) { + Location[] result = new Location[points]; + World world = center.getWorld(); + + for (int i = 0; i < points; i++) { + double angle = 2 * Math.PI * i / points; + double x = center.getX() + radius * Math.cos(angle); + double z = center.getZ() + radius * Math.sin(angle); + result[i] = new Location(world, x, center.getY(), z); + } + + return result; + } + + public static boolean isSafeLocation(Location location) { + if (location == null) return false; + + Block feet = location.getBlock(); + Block head = location.clone().add(0, 1, 0).getBlock(); + + return !feet.getType().isSolid() && !head.getType().isSolid(); + } + + private static boolean isTransparent(Block block) { + String type = block.getType().toString(); + return type.contains("GLASS") || + type.contains("LEAVES") || + type.contains("FENCE") || + type.contains("BARS") || + type.contains("DOOR"); + } +} diff --git a/src/main/java/dev/loki/util/PlayerUtil.java b/src/main/java/dev/loki/util/PlayerUtil.java new file mode 100644 index 0000000..5767314 --- /dev/null +++ b/src/main/java/dev/loki/util/PlayerUtil.java @@ -0,0 +1,152 @@ +package dev.loki.util; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +public final class PlayerUtil { + + private PlayerUtil() {} + + public static boolean isSneaking(Player player) { + return player != null && player.isSneaking(); + } + + public static boolean isSprinting(Player player) { + return player != null && player.isSprinting(); + } + + public static boolean isGrounded(Player player) { + return player != null && player.isOnGround(); + } + + public static boolean isFlying(Player player) { + return player != null && player.isFlying(); + } + + public static boolean isSwimming(Player player) { + return player != null && player.isSwimming(); + } + + public static boolean isGliding(Player player) { + return player != null && player.isGliding(); + } + + public static boolean hasPotionEffect(Player player, PotionEffectType type) { + return player != null && player.hasPotionEffect(type); + } + + public static double getMovementSpeed(Player player) { + if (player == null) return 0; + + Vector velocity = player.getVelocity(); + return Math.sqrt(velocity.getX() * velocity.getX() + velocity.getZ() * velocity.getZ()); + } + + public static double getHorizontalSpeed(Player player) { + if (player == null) return 0; + + Location from = player.getLocation(); + Location to = from.clone().add(player.getVelocity()); + return LocationUtil.distanceHorizontal(from, to); + } + + public static Vector getLookDirection(Player player) { + if (player == null) return new Vector(0, 0, 0); + return player.getLocation().getDirection(); + } + + public static Location getTargetLocation(Player player, double maxDistance) { + if (player == null) return null; + + Location eyeLoc = player.getEyeLocation(); + Vector direction = eyeLoc.getDirection(); + + return eyeLoc.clone().add(direction.multiply(maxDistance)); + } + + public static boolean isLookingDown(Player player, double threshold) { + if (player == null) return false; + return player.getLocation().getPitch() > threshold; + } + + public static boolean isLookingUp(Player player, double threshold) { + if (player == null) return false; + return player.getLocation().getPitch() < -threshold; + } + + public static void setWalkingSpeed(Player player, float speed) { + if (player == null) return; + player.setWalkSpeed(MathUtil.clamp(speed, -1.0f, 1.0f)); + } + + public static void setFlyingSpeed(Player player, float speed) { + if (player == null) return; + player.setFlySpeed(MathUtil.clamp(speed, -1.0f, 1.0f)); + } + + public static void resetSpeed(Player player) { + if (player == null) return; + player.setWalkSpeed(0.2f); + player.setFlySpeed(0.1f); + } + + public static void hidePlayerFrom(Player hidden, Player viewer) { + if (hidden == null || viewer == null) return; + viewer.hidePlayer(hidden.getServer().getPluginManager().getPlugin("mdbd"), hidden); + } + + public static void showPlayerTo(Player hidden, Player viewer) { + if (hidden == null || viewer == null) return; + viewer.showPlayer(hidden.getServer().getPluginManager().getPlugin("mdbd"), hidden); + } + + public static boolean hasInventorySpace(Player player, org.bukkit.inventory.ItemStack item) { + if (player == null || item == null) return false; + return player.getInventory().firstEmpty() != -1 || + player.getInventory().containsAtLeast(item, item.getAmount()); + } + + public static int getEmptySlots(Player player) { + if (player == null) return 0; + + int count = 0; + for (org.bukkit.inventory.ItemStack item : player.getInventory().getStorageContents()) { + if (item == null || item.getType().isAir()) { + count++; + } + } + return count; + } + + public static void clearEffects(Player player) { + if (player == null) return; + + for (org.bukkit.potion.PotionEffect effect : player.getActivePotionEffects()) { + player.removePotionEffect(effect.getType()); + } + } + + public static boolean hasPermission(Player player, String permission) { + return player != null && player.hasPermission(permission); + } + + public static void sendActionBar(Player player, String message) { + if (player == null || !player.isOnline()) return; + player.sendActionBar(net.kyori.adventure.text.Component.text(message)); + } + + public static void sendTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut) { + if (player == null || !player.isOnline()) return; + player.showTitle(net.kyori.adventure.title.Title.title( + net.kyori.adventure.text.Component.text(title != null ? title : ""), + net.kyori.adventure.text.Component.text(subtitle != null ? subtitle : ""), + net.kyori.adventure.title.Title.Times.times( + java.time.Duration.ofMillis(fadeIn * 50L), + java.time.Duration.ofMillis(stay * 50L), + java.time.Duration.ofMillis(fadeOut * 50L) + ) + )); + } +} diff --git a/src/main/java/dev/loki/util/TimeUtil.java b/src/main/java/dev/loki/util/TimeUtil.java new file mode 100644 index 0000000..a4cb10f --- /dev/null +++ b/src/main/java/dev/loki/util/TimeUtil.java @@ -0,0 +1,135 @@ +package dev.loki.util; + +public final class TimeUtil { + + private static final int TICKS_PER_SECOND = 20; + private static final int TICKS_PER_MINUTE = 1200; + private static final long MILLIS_PER_SECOND = 1000; + private static final long MILLIS_PER_MINUTE = 60000; + + private TimeUtil() {} + + public static String formatSeconds(int totalSeconds) { + if (totalSeconds < 0) return "0:00"; + + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + + if (minutes > 0) { + return String.format("%d:%02d", minutes, seconds); + } + return String.format("0:%02d", seconds); + } + + public static String formatSecondsPrecise(int totalSeconds) { + if (totalSeconds < 0) return "0:00:00"; + + int hours = totalSeconds / 3600; + int minutes = (totalSeconds % 3600) / 60; + int seconds = totalSeconds % 60; + + if (hours > 0) { + return String.format("%d:%02d:%02d", hours, minutes, seconds); + } + return String.format("%d:%02d", minutes, seconds); + } + + public static String formatCompact(int totalSeconds) { + if (totalSeconds < 0) return "0s"; + + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + + if (minutes > 0) { + return String.format("%dm %ds", minutes, seconds); + } + return String.format("%ds", seconds); + } + + public static String formatShort(int totalSeconds) { + if (totalSeconds < 0) return "0s"; + if (totalSeconds < 60) return totalSeconds + "s"; + if (totalSeconds < 3600) return (totalSeconds / 60) + "m"; + return (totalSeconds / 3600) + "h"; + } + + public static String formatMinutesSeconds(int totalSeconds) { + if (totalSeconds < 0) return "0:00"; + + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + return String.format("%02d:%02d", minutes, seconds); + } + + public static int ticksToSeconds(int ticks) { + return ticks / TICKS_PER_SECOND; + } + + public static int secondsToTicks(int seconds) { + return seconds * TICKS_PER_SECOND; + } + + public static int minutesToTicks(int minutes) { + return minutes * TICKS_PER_MINUTE; + } + + public static long secondsToMillis(int seconds) { + return seconds * MILLIS_PER_SECOND; + } + + public static long minutesToMillis(int minutes) { + return minutes * MILLIS_PER_MINUTE; + } + + public static int millisToSeconds(long millis) { + return (int) (millis / MILLIS_PER_SECOND); + } + + public static String formatProgressBar(int current, int total, int barLength, char filled, char empty) { + if (total <= 0) return repeat(empty, barLength); + + int filledLength = MathUtil.clamp((current * barLength) / total, 0, barLength); + int emptyLength = barLength - filledLength; + + return repeat(filled, filledLength) + repeat(empty, emptyLength); + } + + public static String formatProgressBarWithPercent(int current, int total, int barLength) { + String bar = formatProgressBar(current, total, barLength, '\u2588', '\u2591'); + int percent = total > 0 ? (current * 100) / total : 0; + return String.format("%s %d%%", bar, percent); + } + + private static String repeat(char c, int count) { + StringBuilder sb = new StringBuilder(count); + for (int i = 0; i < count; i++) { + sb.append(c); + } + return sb.toString(); + } + + public static String getApproximateTime(int seconds) { + if (seconds < 30) return "меньше минуты"; + if (seconds < 90) return "около минуты"; + if (seconds < 150) return "около 2 минут"; + if (seconds < 210) return "около 3 минут"; + if (seconds < 300) return "около 5 минут"; + if (seconds < 600) return "около 10 минут"; + return "более 10 минут"; + } + + public static String pluralizeSeconds(int count) { + int lastDigit = count % 10; + int lastTwoDigits = count % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { + return count + " секунд"; + } + + return switch (lastDigit) { + case 1 -> count + " секунда"; + case 2, 3, 4 -> count + " секунды"; + default -> count + " секунд"; + }; + } +} diff --git a/src/main/java/dev/loki/window/WindowFastVault.java b/src/main/java/dev/loki/window/WindowFastVault.java new file mode 100644 index 0000000..8c0165e --- /dev/null +++ b/src/main/java/dev/loki/window/WindowFastVault.java @@ -0,0 +1,120 @@ +package dev.loki.window; + +import dev.loki.player.BasePlayer; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class WindowFastVault { + private static final double FAST_VAULT_TIME = 1.0; + private static final double SLOW_VAULT_TIME = 1.5; + private static final long VAULT_COOLDOWN_MS = 3000; + + private final Map lastVaultTimes; + private final Map vaultCounts; + + public WindowFastVault() { + this.lastVaultTimes = new HashMap<>(); + this.vaultCounts = new HashMap<>(); + } + + public double getVaultTime(BasePlayer player, Window window, boolean isChasing) { + if (player.isKiller()) { + return 1.5; + } + + if (isFastVaultEligible(player, isChasing)) { + return FAST_VAULT_TIME; + } + return SLOW_VAULT_TIME; + } + + private boolean isFastVaultEligible(BasePlayer player, boolean isChasing) { + UUID id = player.getUuid(); + long now = System.currentTimeMillis(); + Long lastVault = lastVaultTimes.get(id); + + if (lastVault == null) { + return true; + } + + if (now - lastVault > VAULT_COOLDOWN_MS) { + vaultCounts.remove(id); + return true; + } + + int count = vaultCounts.getOrDefault(id, 0); + return count < 2 && isChasing; + } + + public void recordVault(BasePlayer player) { + UUID id = player.getUuid(); + long now = System.currentTimeMillis(); + + lastVaultTimes.put(id, now); + vaultCounts.merge(id, 1, Integer::sum); + } + + public void resetForPlayer(BasePlayer player) { + UUID id = player.getUuid(); + lastVaultTimes.remove(id); + vaultCounts.remove(id); + } + + public void resetAll() { + lastVaultTimes.clear(); + vaultCounts.clear(); + } + + public boolean performFastVault(BasePlayer player, Window window, + org.bukkit.plugin.Plugin plugin, boolean isChasing) { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return false; + + double vaultTime = getVaultTime(player, window, isChasing); + int ticks = (int) (vaultTime * 20); + Location target = window.getVaultTarget(false); + + bukkitPlayer.sendMessage(vaultTime <= FAST_VAULT_TIME + ? "§aБыстрый волт!" + : "§7Медленный волт..."); + + new FastVaultTask(player, target, vaultTime, this, plugin).runTaskLater(plugin, ticks); + return true; + } + + private static class FastVaultTask extends BukkitRunnable { + private final BasePlayer player; + private final Location target; + private final double vaultTime; + private final WindowFastVault vaultSystem; + + FastVaultTask(BasePlayer player, Location target, double vaultTime, + WindowFastVault vaultSystem, org.bukkit.plugin.Plugin plugin) { + this.player = player; + this.target = target; + this.vaultTime = vaultTime; + this.vaultSystem = vaultSystem; + } + + @Override + public void run() { + Player bukkitPlayer = player.getPlayer(); + if (bukkitPlayer == null) return; + + bukkitPlayer.teleport(target); + bukkitPlayer.getWorld().playSound(target, Sound.BLOCK_LADDER_STEP, 0.5f, 0.8f); + + vaultSystem.recordVault(player); + + if (vaultTime > FAST_VAULT_TIME) { + bukkitPlayer.sendMessage("§cВы устали — следующий волт будет медленным!"); + } + } + } +} diff --git a/src/main/java/dev/loki/window/WindowVault.java b/src/main/java/dev/loki/window/WindowVault.java index 4c8585e..1b43e2f 100644 --- a/src/main/java/dev/loki/window/WindowVault.java +++ b/src/main/java/dev/loki/window/WindowVault.java @@ -73,9 +73,9 @@ public class WindowVault { Location target = window.getVaultTarget(player.isKiller()); bukkitPlayer.addPotionEffect(new PotionEffect( - PotionEffectType.SLOW, ticks, 10, false, false)); + PotionEffectType.SLOWNESS, ticks, 10, false, false)); bukkitPlayer.addPotionEffect(new PotionEffect( - PotionEffectType.JUMP, ticks, -10, false, false)); + PotionEffectType.JUMP_BOOST, ticks, -10, false, false)); bukkitPlayer.getWorld().playSound(bukkitPlayer.getLocation(), Sound.BLOCK_LADDER_STEP, 0.5f, 1.0f); @@ -91,8 +91,8 @@ public class WindowVault { bukkitPlayer.teleport(target); bukkitPlayer.getWorld().playSound(target, Sound.BLOCK_LADDER_STEP, 0.5f, 0.8f); - bukkitPlayer.removePotionEffect(PotionEffectType.SLOW); - bukkitPlayer.removePotionEffect(PotionEffectType.JUMP); + bukkitPlayer.removePotionEffect(PotionEffectType.SLOWNESS); + bukkitPlayer.removePotionEffect(PotionEffectType.JUMP_BOOST); } private class VaultTask extends BukkitRunnable {