Stage 13: Perks and Totems
This commit is contained in:
parent
d1f854fc79
commit
37e67ce97d
23 changed files with 2070 additions and 25 deletions
|
|
@ -4,7 +4,6 @@ import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockState;
|
import org.bukkit.block.BlockState;
|
||||||
import org.bukkit.block.Chest as BukkitChest;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
@ -57,7 +56,7 @@ public class Chest {
|
||||||
Block block = location.getBlock();
|
Block block = location.getBlock();
|
||||||
BlockState state = block.getState();
|
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);
|
world.playSound(location, org.bukkit.Sound.BLOCK_CHEST_OPEN, 1.0f, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public class ChestManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadChests() {
|
private void loadChests() {
|
||||||
List<Location> locations = locationsConfig.getChestLocations();
|
List<Location> locations = locationsConfig.getChests();
|
||||||
for (Location loc : locations) {
|
for (Location loc : locations) {
|
||||||
Chest chest = new Chest(loc);
|
Chest chest = new Chest(loc);
|
||||||
chests.put(chest.getId(), chest);
|
chests.put(chest.getId(), chest);
|
||||||
|
|
@ -80,7 +80,7 @@ public class ChestManager {
|
||||||
public void addChest(Location location) {
|
public void addChest(Location location) {
|
||||||
Chest chest = new Chest(location);
|
Chest chest = new Chest(location);
|
||||||
chests.put(chest.getId(), chest);
|
chests.put(chest.getId(), chest);
|
||||||
locationsConfig.addChestLocation(location);
|
locationsConfig.addLocation("chests", location);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalChests() {
|
public int getTotalChests() {
|
||||||
|
|
|
||||||
116
src/main/java/dev/loki/gate/ExitGate.java
Normal file
116
src/main/java/dev/loki/gate/ExitGate.java
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/main/java/dev/loki/gate/GateManager.java
Normal file
114
src/main/java/dev/loki/gate/GateManager.java
Normal file
|
|
@ -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<ExitGate> gates;
|
||||||
|
private boolean endgameActive;
|
||||||
|
|
||||||
|
public GateManager(LocationsConfig locations) {
|
||||||
|
this.gates = new ArrayList<>();
|
||||||
|
this.endgameActive = false;
|
||||||
|
loadGates(locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadGates(LocationsConfig locations) {
|
||||||
|
List<Location> 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<ExitGate> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
147
src/main/java/dev/loki/gate/GateOpening.java
Normal file
147
src/main/java/dev/loki/gate/GateOpening.java
Normal file
|
|
@ -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<UUID, OpeningProgress> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/main/java/dev/loki/hatch/Hatch.java
Normal file
125
src/main/java/dev/loki/hatch/Hatch.java
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/main/java/dev/loki/hatch/HatchCondition.java
Normal file
76
src/main/java/dev/loki/hatch/HatchCondition.java
Normal file
|
|
@ -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<BasePlayer> players, int repairedGenerators) {
|
||||||
|
int aliveSurvivors = countAliveSurvivors(players);
|
||||||
|
return aliveSurvivors <= repairedGenerators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldOpen(Hatch hatch, Collection<BasePlayer> 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<BasePlayer> 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<BasePlayer> 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<BasePlayer> players) {
|
||||||
|
return countAliveSurvivors(players);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLastSurvivor(Collection<BasePlayer> players) {
|
||||||
|
return countAliveSurvivors(players) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkAndReveal(Hatch hatch, Collection<BasePlayer> players, int repairedGenerators) {
|
||||||
|
if (hatch.isHidden() && shouldSpawn(players, repairedGenerators)) {
|
||||||
|
hatch.reveal();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkAndOpen(Hatch hatch, Collection<BasePlayer> players, int repairedGenerators) {
|
||||||
|
if (canOpen(hatch, players, repairedGenerators)) {
|
||||||
|
if (hatch.isHidden()) {
|
||||||
|
hatch.reveal();
|
||||||
|
}
|
||||||
|
hatch.open();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/main/java/dev/loki/hatch/HatchEscape.java
Normal file
110
src/main/java/dev/loki/hatch/HatchEscape.java
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ public class HookManager {
|
||||||
|
|
||||||
public void loadHooks(LocationsConfig config) {
|
public void loadHooks(LocationsConfig config) {
|
||||||
hooks.clear();
|
hooks.clear();
|
||||||
List<Location> hookLocations = config.getHookLocations();
|
List<Location> hookLocations = config.getHooks();
|
||||||
for (int i = 0; i < hookLocations.size(); i++) {
|
for (int i = 0; i < hookLocations.size(); i++) {
|
||||||
Location loc = hookLocations.get(i);
|
Location loc = hookLocations.get(i);
|
||||||
boolean basement = i < 4;
|
boolean basement = i < 4;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import dev.loki.mdbd;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.ui.TitleUI;
|
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 net.kyori.adventure.title.Title;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -39,8 +41,8 @@ public class HookSacrifice {
|
||||||
);
|
);
|
||||||
|
|
||||||
Title title = Title.title(
|
Title title = Title.title(
|
||||||
net.kyori.adventure.text.Component.text("☠").color(net.kyori.adventure.text.NamedTextColor.DARK_RED),
|
Component.text("☠").color(NamedTextColor.DARK_RED),
|
||||||
net.kyori.adventure.text.Component.text("You have been sacrificed").color(net.kyori.adventure.text.NamedTextColor.RED),
|
Component.text("You have been sacrificed").color(NamedTextColor.RED),
|
||||||
times
|
times
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -58,8 +60,8 @@ public class HookSacrifice {
|
||||||
|
|
||||||
final String finalName = name;
|
final String finalName = name;
|
||||||
plugin.getServer().broadcast(
|
plugin.getServer().broadcast(
|
||||||
net.kyori.adventure.text.Component.text(finalName + " has been sacrificed to the Entity")
|
Component.text(finalName + " has been sacrificed to the Entity")
|
||||||
.color(net.kyori.adventure.text.NamedTextColor.DARK_RED)
|
.color(NamedTextColor.DARK_RED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package dev.loki.killer;
|
||||||
import dev.loki.player.Killer;
|
import dev.loki.player.Killer;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
|
import org.bukkit.Particle.DustOptions;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
@ -66,12 +67,12 @@ public class KillerVision extends BukkitRunnable {
|
||||||
particleLoc.setY(particleLoc.getY() - 0.5);
|
particleLoc.setY(particleLoc.getY() - 0.5);
|
||||||
|
|
||||||
world.spawnParticle(
|
world.spawnParticle(
|
||||||
Particle.REDSTONE,
|
Particle.DUST,
|
||||||
particleLoc,
|
particleLoc,
|
||||||
1,
|
1,
|
||||||
0, 0, 0,
|
0, 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(
|
world.spawnParticle(
|
||||||
Particle.REDSTONE,
|
Particle.DUST,
|
||||||
location,
|
location,
|
||||||
15,
|
15,
|
||||||
0.3, 0.3, 0.3,
|
0.3, 0.3, 0.3,
|
||||||
0,
|
0,
|
||||||
new Particle.DustOptions(org.bukkit.Color.MAROON, 1.5f)
|
new DustOptions(org.bukkit.Color.MAROON, 1.5f)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
116
src/main/java/dev/loki/listener/DamageListener.java
Normal file
116
src/main/java/dev/loki/listener/DamageListener.java
Normal file
|
|
@ -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<EntityDamageEvent.DamageCause> ALLOWED_CAUSES = EnumSet.of(
|
||||||
|
EntityDamageEvent.DamageCause.CUSTOM,
|
||||||
|
EntityDamageEvent.DamageCause.VOID
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Set<EntityDamageEvent.DamageCause> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
174
src/main/java/dev/loki/listener/PlayerInteractListener.java
Normal file
174
src/main/java/dev/loki/listener/PlayerInteractListener.java
Normal file
|
|
@ -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<Player, Action> onGeneratorInteract;
|
||||||
|
private BiConsumer<Player, Boolean> onAttack;
|
||||||
|
private BiConsumer<Player, Boolean> onPalletInteract;
|
||||||
|
private BiConsumer<Player, Boolean> onWindowInteract;
|
||||||
|
private BiConsumer<Player, Boolean> onRescueAttempt;
|
||||||
|
private BiConsumer<Player, Boolean> onChestInteract;
|
||||||
|
private BiConsumer<Player, Boolean> onHatchInteract;
|
||||||
|
|
||||||
|
private final Map<UUID, Long> lastRightClick = new HashMap<>();
|
||||||
|
private final Map<UUID, Long> 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<Player, Action> handler) {
|
||||||
|
this.onGeneratorInteract = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnAttack(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onAttack = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPalletInteract(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onPalletInteract = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnWindowInteract(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onWindowInteract = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRescueAttempt(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onRescueAttempt = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnChestInteract(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onChestInteract = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnHatchInteract(BiConsumer<Player, Boolean> 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<UUID, Long> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
src/main/java/dev/loki/listener/PlayerMoveListener.java
Normal file
196
src/main/java/dev/loki/listener/PlayerMoveListener.java
Normal file
|
|
@ -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<Player, Location> onMovement;
|
||||||
|
private BiConsumer<Player, Boolean> onCrouch;
|
||||||
|
private BiConsumer<Player, Boolean> onSprint;
|
||||||
|
private BiConsumer<Survivor, Location> onSurvivorMove;
|
||||||
|
private BiConsumer<Killer, Location> onKillerMove;
|
||||||
|
|
||||||
|
private final Map<UUID, Location> lastLocations = new HashMap<>();
|
||||||
|
private final Map<UUID, Boolean> wasSneaking = new HashMap<>();
|
||||||
|
private final Map<UUID, Boolean> 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<Player, Location> handler) {
|
||||||
|
this.onMovement = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCrouch(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onCrouch = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSprint(BiConsumer<Player, Boolean> handler) {
|
||||||
|
this.onSprint = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSurvivorMove(BiConsumer<Survivor, Location> handler) {
|
||||||
|
this.onSurvivorMove = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnKillerMove(BiConsumer<Killer, Location> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/main/java/dev/loki/listener/PlayerQuitListener.java
Normal file
103
src/main/java/dev/loki/listener/PlayerQuitListener.java
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,7 +51,7 @@ public class PalletDrop {
|
||||||
pallet.setDropped();
|
pallet.setDropped();
|
||||||
|
|
||||||
player.getWorld().playSound(pallet.getLocation(), Sound.BLOCK_WOOD_BREAK, 1.0f, 0.8f);
|
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,
|
pallet.getCenter(), 20, 0.5, 0.5, 0.5, 0.1,
|
||||||
org.bukkit.Material.OAK_PLANKS.createBlockData());
|
org.bukkit.Material.OAK_PLANKS.createBlockData());
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ public class PalletDrop {
|
||||||
|
|
||||||
private void applyExhaustion(Player player) {
|
private void applyExhaustion(Player player) {
|
||||||
player.addPotionEffect(new PotionEffect(
|
player.addPotionEffect(new PotionEffect(
|
||||||
PotionEffectType.SLOW, 20, 2, false, false));
|
PotionEffectType.SLOWNESS, 20, 2, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNearDroppedPallet(Player player) {
|
public boolean isNearDroppedPallet(Player player) {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ public class PalletStun {
|
||||||
stunnedKillers.add(killer.getUuid());
|
stunnedKillers.add(killer.getUuid());
|
||||||
|
|
||||||
player.addPotionEffect(new PotionEffect(
|
player.addPotionEffect(new PotionEffect(
|
||||||
PotionEffectType.SLOW, stunTicks, 10, false, false));
|
PotionEffectType.SLOWNESS, stunTicks, 10, false, false));
|
||||||
player.addPotionEffect(new PotionEffect(
|
player.addPotionEffect(new PotionEffect(
|
||||||
PotionEffectType.BLINDNESS, stunTicks, 1, false, false));
|
PotionEffectType.BLINDNESS, stunTicks, 1, false, false));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,252 @@
|
||||||
package dev.loki.phase;
|
package dev.loki.phase;
|
||||||
|
|
||||||
import dev.loki.core.GameSession;
|
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 {
|
public class EndgamePhase implements Phase {
|
||||||
private int collapseTicks;
|
private final int collapseSeconds;
|
||||||
|
private final int openTicks;
|
||||||
|
|
||||||
|
private int remainingTicks;
|
||||||
private boolean complete;
|
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
|
@Override
|
||||||
public void enter(GameSession session) {
|
public void enter(GameSession session) {
|
||||||
collapseTicks = session.getConfig().getGame().getInt("endgame.collapse-seconds", 120) * 20;
|
this.remainingTicks = collapseSeconds * 20;
|
||||||
complete = false;
|
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
|
@Override
|
||||||
public void tick(GameSession session) {
|
public void tick(GameSession session) {
|
||||||
collapseTicks--;
|
remainingTicks--;
|
||||||
if (collapseTicks <= 0) {
|
int secondsLeft = remainingTicks / 20;
|
||||||
|
|
||||||
|
updateGates(session);
|
||||||
|
updateHatch(session);
|
||||||
|
checkWinConditions(session);
|
||||||
|
sendCountdown(session, secondsLeft);
|
||||||
|
|
||||||
|
if (remainingTicks <= 0) {
|
||||||
|
triggerCollapse(session);
|
||||||
complete = true;
|
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
|
@Override
|
||||||
public void exit(GameSession session) {
|
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<String> 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
|
@Override
|
||||||
public boolean isComplete() {
|
public boolean isComplete() {
|
||||||
return complete;
|
return complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getRemainingSeconds() {
|
||||||
|
return remainingTicks / 20;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
138
src/main/java/dev/loki/util/LocationUtil.java
Normal file
138
src/main/java/dev/loki/util/LocationUtil.java
Normal file
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/main/java/dev/loki/util/PlayerUtil.java
Normal file
152
src/main/java/dev/loki/util/PlayerUtil.java
Normal file
|
|
@ -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)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/main/java/dev/loki/util/TimeUtil.java
Normal file
135
src/main/java/dev/loki/util/TimeUtil.java
Normal file
|
|
@ -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 + " секунд";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/main/java/dev/loki/window/WindowFastVault.java
Normal file
120
src/main/java/dev/loki/window/WindowFastVault.java
Normal file
|
|
@ -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<UUID, Long> lastVaultTimes;
|
||||||
|
private final Map<UUID, Integer> 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Вы устали — следующий волт будет медленным!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -73,9 +73,9 @@ public class WindowVault {
|
||||||
Location target = window.getVaultTarget(player.isKiller());
|
Location target = window.getVaultTarget(player.isKiller());
|
||||||
|
|
||||||
bukkitPlayer.addPotionEffect(new PotionEffect(
|
bukkitPlayer.addPotionEffect(new PotionEffect(
|
||||||
PotionEffectType.SLOW, ticks, 10, false, false));
|
PotionEffectType.SLOWNESS, ticks, 10, false, false));
|
||||||
bukkitPlayer.addPotionEffect(new PotionEffect(
|
bukkitPlayer.addPotionEffect(new PotionEffect(
|
||||||
PotionEffectType.JUMP, ticks, -10, false, false));
|
PotionEffectType.JUMP_BOOST, ticks, -10, false, false));
|
||||||
|
|
||||||
bukkitPlayer.getWorld().playSound(bukkitPlayer.getLocation(),
|
bukkitPlayer.getWorld().playSound(bukkitPlayer.getLocation(),
|
||||||
Sound.BLOCK_LADDER_STEP, 0.5f, 1.0f);
|
Sound.BLOCK_LADDER_STEP, 0.5f, 1.0f);
|
||||||
|
|
@ -91,8 +91,8 @@ public class WindowVault {
|
||||||
|
|
||||||
bukkitPlayer.teleport(target);
|
bukkitPlayer.teleport(target);
|
||||||
bukkitPlayer.getWorld().playSound(target, Sound.BLOCK_LADDER_STEP, 0.5f, 0.8f);
|
bukkitPlayer.getWorld().playSound(target, Sound.BLOCK_LADDER_STEP, 0.5f, 0.8f);
|
||||||
bukkitPlayer.removePotionEffect(PotionEffectType.SLOW);
|
bukkitPlayer.removePotionEffect(PotionEffectType.SLOWNESS);
|
||||||
bukkitPlayer.removePotionEffect(PotionEffectType.JUMP);
|
bukkitPlayer.removePotionEffect(PotionEffectType.JUMP_BOOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VaultTask extends BukkitRunnable {
|
private class VaultTask extends BukkitRunnable {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue