Stage 12: Data storage system
This commit is contained in:
parent
82ed455cd2
commit
4fd7955d56
62 changed files with 5613 additions and 0 deletions
79
src/main/java/dev/loki/chest/Chest.java
Normal file
79
src/main/java/dev/loki/chest/Chest.java
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package dev.loki.chest;
|
||||
|
||||
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;
|
||||
|
||||
public class Chest {
|
||||
|
||||
private final UUID id;
|
||||
private final Location location;
|
||||
private boolean opened;
|
||||
private boolean empty;
|
||||
|
||||
public Chest(Location location) {
|
||||
this.id = UUID.randomUUID();
|
||||
this.location = location;
|
||||
this.opened = false;
|
||||
this.empty = false;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public boolean isOpened() {
|
||||
return opened;
|
||||
}
|
||||
|
||||
public void setOpened(boolean opened) {
|
||||
this.opened = opened;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
public void setEmpty(boolean empty) {
|
||||
this.empty = empty;
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return !opened && !empty;
|
||||
}
|
||||
|
||||
public void openVisual() {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
Block block = location.getBlock();
|
||||
BlockState state = block.getState();
|
||||
|
||||
if (state instanceof BukkitChest chest) {
|
||||
world.playSound(location, org.bukkit.Sound.BLOCK_CHEST_OPEN, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeVisual() {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
world.playSound(location, org.bukkit.Sound.BLOCK_CHEST_CLOSE, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
public double getDistance(Location playerLoc) {
|
||||
return location.distance(playerLoc);
|
||||
}
|
||||
|
||||
public boolean isInRange(Location playerLoc, double range) {
|
||||
return getDistance(playerLoc) <= range;
|
||||
}
|
||||
}
|
||||
93
src/main/java/dev/loki/chest/ChestManager.java
Normal file
93
src/main/java/dev/loki/chest/ChestManager.java
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package dev.loki.chest;
|
||||
|
||||
import dev.loki.config.LocationsConfig;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChestManager {
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final Map<UUID, Chest> chests;
|
||||
private final LocationsConfig locationsConfig;
|
||||
|
||||
public ChestManager(JavaPlugin plugin, LocationsConfig locationsConfig) {
|
||||
this.plugin = plugin;
|
||||
this.chests = new HashMap<>();
|
||||
this.locationsConfig = locationsConfig;
|
||||
loadChests();
|
||||
}
|
||||
|
||||
private void loadChests() {
|
||||
List<Location> locations = locationsConfig.getChestLocations();
|
||||
for (Location loc : locations) {
|
||||
Chest chest = new Chest(loc);
|
||||
chests.put(chest.getId(), chest);
|
||||
}
|
||||
plugin.getLogger().info("Loaded " + chests.size() + " chests");
|
||||
}
|
||||
|
||||
public Chest getChest(UUID id) {
|
||||
return chests.get(id);
|
||||
}
|
||||
|
||||
public List<Chest> getAllChests() {
|
||||
return new ArrayList<>(chests.values());
|
||||
}
|
||||
|
||||
public Chest getNearestChest(Location location, double maxRange) {
|
||||
Chest nearest = null;
|
||||
double minDistance = maxRange;
|
||||
|
||||
for (Chest chest : chests.values()) {
|
||||
double distance = chest.getDistance(location);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearest = chest;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public List<Chest> getAvailableChests() {
|
||||
List<Chest> available = new ArrayList<>();
|
||||
for (Chest chest : chests.values()) {
|
||||
if (chest.isAvailable()) {
|
||||
available.add(chest);
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
public void resetAllChests() {
|
||||
for (Chest chest : chests.values()) {
|
||||
chest.setOpened(false);
|
||||
chest.setEmpty(false);
|
||||
}
|
||||
plugin.getLogger().info("Reset all chests");
|
||||
}
|
||||
|
||||
public void clearChests() {
|
||||
chests.clear();
|
||||
}
|
||||
|
||||
public void addChest(Location location) {
|
||||
Chest chest = new Chest(location);
|
||||
chests.put(chest.getId(), chest);
|
||||
locationsConfig.addChestLocation(location);
|
||||
}
|
||||
|
||||
public int getTotalChests() {
|
||||
return chests.size();
|
||||
}
|
||||
|
||||
public int getRemainingChests() {
|
||||
return (int) chests.values().stream().filter(Chest::isAvailable).count();
|
||||
}
|
||||
}
|
||||
120
src/main/java/dev/loki/chest/ChestOpening.java
Normal file
120
src/main/java/dev/loki/chest/ChestOpening.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package dev.loki.chest;
|
||||
|
||||
import dev.loki.item.ChestLoot;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChestOpening {
|
||||
|
||||
private static final double OPEN_RANGE = 3.0;
|
||||
private static final long OPEN_TIME_TICKS = 60L;
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final ChestLoot chestLoot;
|
||||
private final Map<UUID, BukkitRunnable> activeOpenings;
|
||||
|
||||
public ChestOpening(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.chestLoot = new ChestLoot();
|
||||
this.activeOpenings = new HashMap<>();
|
||||
}
|
||||
|
||||
public boolean startOpening(Player player, Chest chest) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
|
||||
if (activeOpenings.containsKey(playerId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!chest.isAvailable()) {
|
||||
player.sendMessage("§cThis chest is empty!");
|
||||
return false;
|
||||
}
|
||||
|
||||
chest.setOpened(true);
|
||||
chest.openVisual();
|
||||
player.sendMessage("§eOpening chest...");
|
||||
playOpeningSound(player, chest.getLocation());
|
||||
|
||||
BukkitRunnable task = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
completeOpening(player, chest);
|
||||
}
|
||||
};
|
||||
|
||||
task.runTaskLater(plugin, OPEN_TIME_TICKS);
|
||||
activeOpenings.put(playerId, task);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cancelOpening(Player player) {
|
||||
UUID playerId = player.getUniqueId();
|
||||
BukkitRunnable task = activeOpenings.remove(playerId);
|
||||
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
player.sendMessage("§cOpening cancelled!");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOpening(Player player) {
|
||||
return activeOpenings.containsKey(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void cancelAll() {
|
||||
for (BukkitRunnable task : activeOpenings.values()) {
|
||||
task.cancel();
|
||||
}
|
||||
activeOpenings.clear();
|
||||
}
|
||||
|
||||
private void completeOpening(Player player, Chest chest) {
|
||||
activeOpenings.remove(player.getUniqueId());
|
||||
|
||||
if (!player.isOnline()) {
|
||||
chest.setOpened(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chest.isInRange(player.getLocation(), OPEN_RANGE)) {
|
||||
player.sendMessage("§cYou moved too far away!");
|
||||
chest.setOpened(false);
|
||||
chest.closeVisual();
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack loot = chestLoot.rollLoot();
|
||||
player.getInventory().addItem(loot);
|
||||
chest.setEmpty(true);
|
||||
chest.closeVisual();
|
||||
|
||||
player.sendMessage("§aYou found: §f" + loot.getItemMeta().getDisplayName());
|
||||
playSuccessSound(player, chest.getLocation());
|
||||
}
|
||||
|
||||
private void playOpeningSound(Player player, Location location) {
|
||||
World world = location.getWorld();
|
||||
if (world != null) {
|
||||
world.playSound(location, Sound.BLOCK_CHEST_LOCKED, 0.5f, 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
private void playSuccessSound(Player player, Location location) {
|
||||
World world = location.getWorld();
|
||||
if (world != null) {
|
||||
world.playSound(location, Sound.ENTITY_PLAYER_LEVELUP, 0.5f, 1.2f);
|
||||
player.playSound(player.getLocation(), Sound.BLOCK_CHEST_CLOSE, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/main/java/dev/loki/damage/AttackListener.java
Normal file
71
src/main/java/dev/loki/damage/AttackListener.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package dev.loki.damage;
|
||||
|
||||
import dev.loki.player.BasePlayer;
|
||||
import dev.loki.player.Killer;
|
||||
import dev.loki.player.PlayerManager;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
public class AttackListener implements Listener {
|
||||
private final PlayerManager playerManager;
|
||||
private final DamageHandler damageHandler;
|
||||
private final java.util.function.BiConsumer<Killer, Survivor> onAttackAttempt;
|
||||
|
||||
public AttackListener(PlayerManager playerManager, DamageHandler damageHandler,
|
||||
java.util.function.BiConsumer<Killer, Survivor> onAttackAttempt) {
|
||||
this.playerManager = playerManager;
|
||||
this.damageHandler = damageHandler;
|
||||
this.onAttackAttempt = onAttackAttempt;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onEntityDamage(EntityDamageByEntityEvent event) {
|
||||
if (!(event.getDamager() instanceof Player attacker)) return;
|
||||
if (!(event.getEntity() instanceof Player target)) return;
|
||||
|
||||
BasePlayer attackerBase = playerManager.getPlayer(attacker);
|
||||
BasePlayer targetBase = playerManager.getPlayer(target);
|
||||
|
||||
if (attackerBase == null || targetBase == null) return;
|
||||
if (!attackerBase.isKiller()) return;
|
||||
if (targetBase.isKiller()) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
Killer killer = (Killer) attackerBase;
|
||||
Survivor survivor = (Survivor) targetBase;
|
||||
|
||||
if (onAttackAttempt != null) {
|
||||
onAttackAttempt.accept(killer, survivor);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (event.getHand() != EquipmentSlot.HAND) return;
|
||||
if (!event.getAction().isLeftClick()) return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
BasePlayer basePlayer = playerManager.getPlayer(player);
|
||||
|
||||
if (basePlayer == null || !basePlayer.isKiller()) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
public void register(org.bukkit.plugin.Plugin plugin) {
|
||||
org.bukkit.Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
org.bukkit.event.HandlerList.unregisterAll(this);
|
||||
}
|
||||
}
|
||||
87
src/main/java/dev/loki/damage/DamageHandler.java
Normal file
87
src/main/java/dev/loki/damage/DamageHandler.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package dev.loki.damage;
|
||||
|
||||
import dev.loki.player.Survivor;
|
||||
import dev.loki.state.StateMachine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class DamageHandler {
|
||||
private final StateMachine stateMachine;
|
||||
private final Map<UUID, Long> lastDamageTime;
|
||||
private Consumer<Survivor> onSurvivorInjured;
|
||||
private Consumer<Survivor> onSurvivorDowned;
|
||||
private static final long DAMAGE_COOLDOWN_MS = 500;
|
||||
|
||||
public DamageHandler(StateMachine stateMachine) {
|
||||
this.stateMachine = stateMachine;
|
||||
this.lastDamageTime = new HashMap<>();
|
||||
}
|
||||
|
||||
public void setOnSurvivorInjured(Consumer<Survivor> callback) {
|
||||
this.onSurvivorInjured = callback;
|
||||
}
|
||||
|
||||
public void setOnSurvivorDowned(Consumer<Survivor> callback) {
|
||||
this.onSurvivorDowned = callback;
|
||||
}
|
||||
|
||||
public boolean applyDamage(Survivor survivor) {
|
||||
if (!canTakeDamage(survivor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkCooldown(survivor.getUuid())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean wasHealthy = survivor.getState().name().equals("HEALTHY");
|
||||
|
||||
if (!stateMachine.damageSurvivor(survivor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recordDamage(survivor.getUuid());
|
||||
|
||||
if (survivor.getState().name().equals("DOWNED")) {
|
||||
if (onSurvivorDowned != null) {
|
||||
onSurvivorDowned.accept(survivor);
|
||||
}
|
||||
} else if (wasHealthy && onSurvivorInjured != null) {
|
||||
onSurvivorInjured.accept(survivor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canTakeDamage(Survivor survivor) {
|
||||
return switch (survivor.getState()) {
|
||||
case HEALTHY, INJURED -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean checkCooldown(UUID uuid) {
|
||||
Long lastTime = lastDamageTime.get(uuid);
|
||||
if (lastTime == null) return true;
|
||||
return System.currentTimeMillis() - lastTime >= DAMAGE_COOLDOWN_MS;
|
||||
}
|
||||
|
||||
private void recordDamage(UUID uuid) {
|
||||
lastDamageTime.put(uuid, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public boolean isKillable(Survivor survivor) {
|
||||
return survivor.getHooksTaken() >= 2;
|
||||
}
|
||||
|
||||
public void clearCooldown(UUID uuid) {
|
||||
lastDamageTime.remove(uuid);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
lastDamageTime.clear();
|
||||
}
|
||||
}
|
||||
12
src/main/java/dev/loki/data/DataStorage.java
Normal file
12
src/main/java/dev/loki/data/DataStorage.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package dev.loki.data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface DataStorage {
|
||||
void savePlayerStats(UUID playerId, PlayerStats stats);
|
||||
PlayerStats loadPlayerStats(UUID playerId);
|
||||
void saveMatch(MatchResult result);
|
||||
List<MatchResult> loadPlayerMatchHistory(UUID playerId, int limit);
|
||||
void close();
|
||||
}
|
||||
33
src/main/java/dev/loki/data/MatchResult.java
Normal file
33
src/main/java/dev/loki/data/MatchResult.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package dev.loki.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MatchResult {
|
||||
private final long timestamp;
|
||||
private final UUID playerId;
|
||||
private final String role;
|
||||
private final boolean won;
|
||||
private final int bloodpointsEarned;
|
||||
private final int matchDurationSeconds;
|
||||
|
||||
public MatchResult(UUID playerId, String role, boolean won,
|
||||
int bloodpointsEarned, int matchDurationSeconds) {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.playerId = playerId;
|
||||
this.role = role;
|
||||
this.won = won;
|
||||
this.bloodpointsEarned = bloodpointsEarned;
|
||||
this.matchDurationSeconds = matchDurationSeconds;
|
||||
}
|
||||
|
||||
public long getTimestamp() { return timestamp; }
|
||||
public UUID getPlayerId() { return playerId; }
|
||||
public String getRole() { return role; }
|
||||
public boolean isWon() { return won; }
|
||||
public int getBloodpointsEarned() { return bloodpointsEarned; }
|
||||
public int getMatchDurationSeconds() { return matchDurationSeconds; }
|
||||
|
||||
public enum Role {
|
||||
SURVIVOR, KILLER
|
||||
}
|
||||
}
|
||||
47
src/main/java/dev/loki/data/PlayerStats.java
Normal file
47
src/main/java/dev/loki/data/PlayerStats.java
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package dev.loki.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerStats {
|
||||
private final UUID playerId;
|
||||
private int totalMatches;
|
||||
private int escapes;
|
||||
private int deaths;
|
||||
private int kills;
|
||||
private int bloodpoints;
|
||||
private long playtimeMinutes;
|
||||
|
||||
public PlayerStats(UUID playerId) {
|
||||
this.playerId = playerId;
|
||||
this.totalMatches = 0;
|
||||
this.escapes = 0;
|
||||
this.deaths = 0;
|
||||
this.kills = 0;
|
||||
this.bloodpoints = 0;
|
||||
this.playtimeMinutes = 0;
|
||||
}
|
||||
|
||||
public UUID getPlayerId() { return playerId; }
|
||||
public int getTotalMatches() { return totalMatches; }
|
||||
public void incrementMatches() { this.totalMatches++; }
|
||||
public int getEscapes() { return escapes; }
|
||||
public void incrementEscapes() { this.escapes++; }
|
||||
public int getDeaths() { return deaths; }
|
||||
public void incrementDeaths() { this.deaths++; }
|
||||
public int getKills() { return kills; }
|
||||
public void incrementKills() { this.kills++; }
|
||||
public int getBloodpoints() { return bloodpoints; }
|
||||
public void addBloodpoints(int amount) { this.bloodpoints += amount; }
|
||||
public boolean spendBloodpoints(int amount) {
|
||||
if (bloodpoints >= amount) {
|
||||
bloodpoints -= amount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public long getPlaytimeMinutes() { return playtimeMinutes; }
|
||||
public void addPlaytimeMinutes(long minutes) { this.playtimeMinutes += minutes; }
|
||||
public double getEscapeRate() {
|
||||
return totalMatches > 0 ? (double) escapes / totalMatches * 100 : 0;
|
||||
}
|
||||
}
|
||||
136
src/main/java/dev/loki/data/SQLiteStorage.java
Normal file
136
src/main/java/dev/loki/data/SQLiteStorage.java
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package dev.loki.data;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SQLiteStorage implements DataStorage {
|
||||
private final mdbd plugin;
|
||||
private Connection connection;
|
||||
|
||||
public SQLiteStorage(mdbd plugin) {
|
||||
this.plugin = plugin;
|
||||
initDatabase();
|
||||
}
|
||||
|
||||
private void initDatabase() {
|
||||
try {
|
||||
File dbFile = new File(plugin.getDataFolder(), "data.db");
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||
Statement stmt = connection.createStatement();
|
||||
stmt.execute("""
|
||||
CREATE TABLE IF NOT EXISTS player_stats (
|
||||
uuid TEXT PRIMARY KEY, matches INTEGER DEFAULT 0,
|
||||
escapes INTEGER DEFAULT 0, deaths INTEGER DEFAULT 0,
|
||||
kills INTEGER DEFAULT 0, bloodpoints INTEGER DEFAULT 0,
|
||||
playtime INTEGER DEFAULT 0
|
||||
)""");
|
||||
stmt.execute("""
|
||||
CREATE TABLE IF NOT EXISTS match_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_uuid TEXT, role TEXT, won INTEGER,
|
||||
bloodpoints INTEGER, duration INTEGER, timestamp INTEGER
|
||||
)""");
|
||||
stmt.close();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().severe("Failed to init SQLite: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePlayerStats(UUID playerId, PlayerStats stats) {
|
||||
String sql = """
|
||||
INSERT OR REPLACE INTO player_stats
|
||||
(uuid, matches, escapes, deaths, kills, bloodpoints, playtime)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setString(1, playerId.toString());
|
||||
ps.setInt(2, stats.getTotalMatches());
|
||||
ps.setInt(3, stats.getEscapes());
|
||||
ps.setInt(4, stats.getDeaths());
|
||||
ps.setInt(5, stats.getKills());
|
||||
ps.setInt(6, stats.getBloodpoints());
|
||||
ps.setLong(7, stats.getPlaytimeMinutes());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning("Failed to save stats: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStats loadPlayerStats(UUID playerId) {
|
||||
String sql = "SELECT * FROM player_stats WHERE uuid = ?";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setString(1, playerId.toString());
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
PlayerStats stats = new PlayerStats(playerId);
|
||||
for (int i = 0; i < rs.getInt("matches"); i++) stats.incrementMatches();
|
||||
for (int i = 0; i < rs.getInt("escapes"); i++) stats.incrementEscapes();
|
||||
for (int i = 0; i < rs.getInt("deaths"); i++) stats.incrementDeaths();
|
||||
for (int i = 0; i < rs.getInt("kills"); i++) stats.incrementKills();
|
||||
stats.addBloodpoints(rs.getInt("bloodpoints"));
|
||||
stats.addPlaytimeMinutes(rs.getLong("playtime"));
|
||||
return stats;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning("Failed to load stats: " + e.getMessage());
|
||||
}
|
||||
return new PlayerStats(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMatch(MatchResult result) {
|
||||
String sql = """
|
||||
INSERT INTO match_history
|
||||
(player_uuid, role, won, bloodpoints, duration, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setString(1, result.getPlayerId().toString());
|
||||
ps.setString(2, result.getRole());
|
||||
ps.setInt(3, result.isWon() ? 1 : 0);
|
||||
ps.setInt(4, result.getBloodpointsEarned());
|
||||
ps.setInt(5, result.getMatchDurationSeconds());
|
||||
ps.setLong(6, result.getTimestamp());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning("Failed to save match: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MatchResult> loadPlayerMatchHistory(UUID playerId, int limit) {
|
||||
List<MatchResult> results = new ArrayList<>();
|
||||
String sql = "SELECT * FROM match_history WHERE player_uuid = ? ORDER BY timestamp DESC LIMIT ?";
|
||||
try (PreparedStatement ps = connection.prepareStatement(sql)) {
|
||||
ps.setString(1, playerId.toString());
|
||||
ps.setInt(2, limit);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
results.add(new MatchResult(
|
||||
playerId, rs.getString("role"),
|
||||
rs.getInt("won") == 1, rs.getInt("bloodpoints"),
|
||||
rs.getInt("duration")
|
||||
));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning("Failed to load history: " + e.getMessage());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning("Failed to close SQLite: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
109
src/main/java/dev/loki/data/YamlStorage.java
Normal file
109
src/main/java/dev/loki/data/YamlStorage.java
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package dev.loki.data;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class YamlStorage implements DataStorage {
|
||||
private final mdbd plugin;
|
||||
private final File dataFolder;
|
||||
|
||||
public YamlStorage(mdbd plugin) {
|
||||
this.plugin = plugin;
|
||||
this.dataFolder = new File(plugin.getDataFolder(), "playerdata");
|
||||
if (!dataFolder.exists()) {
|
||||
dataFolder.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void savePlayerStats(UUID playerId, PlayerStats stats) {
|
||||
File file = getPlayerFile(playerId);
|
||||
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
config.set("uuid", playerId.toString());
|
||||
config.set("matches", stats.getTotalMatches());
|
||||
config.set("escapes", stats.getEscapes());
|
||||
config.set("deaths", stats.getDeaths());
|
||||
config.set("kills", stats.getKills());
|
||||
config.set("bloodpoints", stats.getBloodpoints());
|
||||
config.set("playtime", stats.getPlaytimeMinutes());
|
||||
save(file, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStats loadPlayerStats(UUID playerId) {
|
||||
File file = getPlayerFile(playerId);
|
||||
if (!file.exists()) {
|
||||
return new PlayerStats(playerId);
|
||||
}
|
||||
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
PlayerStats stats = new PlayerStats(playerId);
|
||||
stats.incrementMatches(config.getInt("matches", 0));
|
||||
for (int i = 0; i < config.getInt("escapes", 0); i++) stats.incrementEscapes();
|
||||
for (int i = 0; i < config.getInt("deaths", 0); i++) stats.incrementDeaths();
|
||||
for (int i = 0; i < config.getInt("kills", 0); i++) stats.incrementKills();
|
||||
stats.addBloodpoints(config.getInt("bloodpoints", 0));
|
||||
stats.addPlaytimeMinutes(config.getLong("playtime", 0));
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMatch(MatchResult result) {
|
||||
File file = getHistoryFile(result.getPlayerId());
|
||||
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
String path = "history." + result.getTimestamp();
|
||||
config.set(path + ".role", result.getRole());
|
||||
config.set(path + ".won", result.isWon());
|
||||
config.set(path + ".bp", result.getBloodpointsEarned());
|
||||
config.set(path + ".duration", result.getMatchDurationSeconds());
|
||||
save(file, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MatchResult> loadPlayerMatchHistory(UUID playerId, int limit) {
|
||||
List<MatchResult> results = new ArrayList<>();
|
||||
File file = getHistoryFile(playerId);
|
||||
if (!file.exists()) return results;
|
||||
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
if (config.contains("history")) {
|
||||
for (String key : config.getConfigurationSection("history").getKeys(false)) {
|
||||
String path = "history." + key;
|
||||
results.add(new MatchResult(
|
||||
playerId,
|
||||
config.getString(path + ".role", "UNKNOWN"),
|
||||
config.getBoolean(path + ".won", false),
|
||||
config.getInt(path + ".bp", 0),
|
||||
config.getInt(path + ".duration", 0)
|
||||
));
|
||||
}
|
||||
}
|
||||
return results.stream().limit(limit).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// YAML doesn't need explicit closing
|
||||
}
|
||||
|
||||
private File getPlayerFile(UUID playerId) {
|
||||
return new File(dataFolder, playerId.toString() + ".yml");
|
||||
}
|
||||
|
||||
private File getHistoryFile(UUID playerId) {
|
||||
return new File(dataFolder, playerId.toString() + "_history.yml");
|
||||
}
|
||||
|
||||
private void save(File file, FileConfiguration config) {
|
||||
try {
|
||||
config.save(file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to save data: " + file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/main/java/dev/loki/hook/Hook.java
Normal file
70
src/main/java/dev/loki/hook/Hook.java
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Hook {
|
||||
private final UUID id;
|
||||
private final Location location;
|
||||
private Survivor hookedSurvivor;
|
||||
private HookStage currentStage;
|
||||
private int stageProgress;
|
||||
private boolean occupied;
|
||||
private boolean basement;
|
||||
|
||||
public Hook(UUID id, Location location, boolean basement) {
|
||||
this.id = id;
|
||||
this.location = location;
|
||||
this.basement = basement;
|
||||
this.currentStage = HookStage.STAGE_1;
|
||||
this.stageProgress = 0;
|
||||
this.occupied = false;
|
||||
}
|
||||
|
||||
public void hookSurvivor(Survivor survivor) {
|
||||
this.hookedSurvivor = survivor;
|
||||
this.occupied = true;
|
||||
this.currentStage = HookStage.STAGE_1;
|
||||
this.stageProgress = 0;
|
||||
}
|
||||
|
||||
public void unhookSurvivor() {
|
||||
this.hookedSurvivor = null;
|
||||
this.occupied = false;
|
||||
this.currentStage = HookStage.STAGE_1;
|
||||
this.stageProgress = 0;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (!occupied || hookedSurvivor == null) return;
|
||||
stageProgress++;
|
||||
int stageDuration = currentStage.getDurationSeconds() * 20;
|
||||
if (stageProgress >= stageDuration && currentStage != HookStage.SACRIFICE) {
|
||||
advanceStage();
|
||||
}
|
||||
}
|
||||
|
||||
private void advanceStage() {
|
||||
currentStage = currentStage.next();
|
||||
stageProgress = 0;
|
||||
}
|
||||
|
||||
public double getStageProgressPercent() {
|
||||
if (currentStage == HookStage.SACRIFICE) return 100;
|
||||
int stageDuration = currentStage.getDurationSeconds() * 20;
|
||||
return (double) stageProgress / stageDuration * 100;
|
||||
}
|
||||
|
||||
public boolean isInStruggle() {
|
||||
return currentStage == HookStage.STAGE_2;
|
||||
}
|
||||
|
||||
public UUID getId() { return id; }
|
||||
public Location getLocation() { return location; }
|
||||
public Survivor getHookedSurvivor() { return hookedSurvivor; }
|
||||
public HookStage getCurrentStage() { return currentStage; }
|
||||
public boolean isOccupied() { return occupied; }
|
||||
public boolean isBasement() { return basement; }
|
||||
}
|
||||
110
src/main/java/dev/loki/hook/HookManager.java
Normal file
110
src/main/java/dev/loki/hook/HookManager.java
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
import dev.loki.config.LocationsConfig;
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class HookManager {
|
||||
private final mdbd plugin;
|
||||
private final Map<UUID, Hook> hooks;
|
||||
private BukkitTask tickTask;
|
||||
|
||||
public HookManager(mdbd plugin) {
|
||||
this.plugin = plugin;
|
||||
this.hooks = new HashMap<>();
|
||||
}
|
||||
|
||||
public void loadHooks(LocationsConfig config) {
|
||||
hooks.clear();
|
||||
List<Location> hookLocations = config.getHookLocations();
|
||||
for (int i = 0; i < hookLocations.size(); i++) {
|
||||
Location loc = hookLocations.get(i);
|
||||
boolean basement = i < 4;
|
||||
UUID id = UUID.nameUUIDFromBytes(("hook_" + i).getBytes());
|
||||
hooks.put(id, new Hook(id, loc, basement));
|
||||
}
|
||||
}
|
||||
|
||||
public void startTicking() {
|
||||
tickTask = plugin.getServer().getScheduler().runTaskTimer(
|
||||
plugin, this::tickAllHooks, 1L, 1L
|
||||
);
|
||||
}
|
||||
|
||||
public void stopTicking() {
|
||||
if (tickTask != null) {
|
||||
tickTask.cancel();
|
||||
tickTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void tickAllHooks() {
|
||||
for (Hook hook : hooks.values()) {
|
||||
hook.tick();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Hook> findAvailableHook() {
|
||||
return hooks.values().stream()
|
||||
.filter(h -> !h.isOccupied())
|
||||
.min(Comparator.comparingInt(h -> h.isBasement() ? 1 : 0));
|
||||
}
|
||||
|
||||
public Hook findNearestUnoccupiedHook(Location location, double maxDistance) {
|
||||
Hook nearest = null;
|
||||
double minDist = maxDistance;
|
||||
for (Hook hook : hooks.values()) {
|
||||
if (hook.isOccupied()) continue;
|
||||
double dist = hook.getLocation().distance(location);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
nearest = hook;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public Hook findNearestOccupiedHook(Location location, double maxDistance) {
|
||||
Hook nearest = null;
|
||||
double minDist = maxDistance;
|
||||
for (Hook hook : hooks.values()) {
|
||||
if (!hook.isOccupied()) continue;
|
||||
double dist = hook.getLocation().distance(location);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
nearest = hook;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public boolean tryHookSurvivor(Survivor survivor, Location killerLoc) {
|
||||
Hook nearest = findNearestUnoccupiedHook(killerLoc, 3.0);
|
||||
if (nearest == null) {
|
||||
nearest = findAvailableHook().orElse(null);
|
||||
}
|
||||
if (nearest != null) {
|
||||
nearest.hookSurvivor(survivor);
|
||||
survivor.incrementHooks();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Collection<Hook> getAllHooks() {
|
||||
return hooks.values();
|
||||
}
|
||||
|
||||
public long getOccupiedHookCount() {
|
||||
return hooks.values().stream().filter(Hook::isOccupied).count();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
stopTicking();
|
||||
hooks.clear();
|
||||
}
|
||||
}
|
||||
143
src/main/java/dev/loki/hook/HookRescue.java
Normal file
143
src/main/java/dev/loki/hook/HookRescue.java
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import dev.loki.ui.ActionBar;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HookRescue {
|
||||
private final mdbd plugin;
|
||||
private final ActionBar actionBar;
|
||||
private final Map<UUID, RescueSession> activeRescues;
|
||||
private static final int RESCUE_DURATION_TICKS = 40;
|
||||
private static final double RESCUE_RANGE = 3.0;
|
||||
|
||||
public HookRescue(mdbd plugin, ActionBar actionBar) {
|
||||
this.plugin = plugin;
|
||||
this.actionBar = actionBar;
|
||||
this.activeRescues = new HashMap<>();
|
||||
}
|
||||
|
||||
public boolean startRescue(Survivor rescuer, Hook hook) {
|
||||
UUID uuid = rescuer.getUuid();
|
||||
if (activeRescues.containsKey(uuid)) return false;
|
||||
if (!hook.isOccupied() || hook.getHookedSurvivor() == null) return false;
|
||||
|
||||
Location hookLoc = hook.getLocation();
|
||||
Player player = rescuer.getPlayer();
|
||||
if (player == null || player.getLocation().distance(hookLoc) > RESCUE_RANGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RescueSession session = new RescueSession(rescuer, hook, RESCUE_DURATION_TICKS);
|
||||
activeRescues.put(uuid, session);
|
||||
|
||||
BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(
|
||||
plugin,
|
||||
() -> tickRescue(uuid),
|
||||
1L, 1L
|
||||
);
|
||||
session.setTask(task);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void tickRescue(UUID uuid) {
|
||||
RescueSession session = activeRescues.get(uuid);
|
||||
if (session == null) return;
|
||||
|
||||
Survivor rescuer = session.getRescuer();
|
||||
Hook hook = session.getHook();
|
||||
Player player = rescuer.getPlayer();
|
||||
|
||||
if (player == null || !player.isOnline()) {
|
||||
cancelRescue(uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getLocation().distance(hook.getLocation()) > RESCUE_RANGE) {
|
||||
cancelRescue(uuid);
|
||||
actionBar.sendMessage(player, "<red>Rescue cancelled - too far!");
|
||||
return;
|
||||
}
|
||||
|
||||
session.incrementProgress();
|
||||
double percent = session.getProgressPercent();
|
||||
actionBar.showProgress(player, "Rescuing...", percent);
|
||||
|
||||
if (session.isComplete()) {
|
||||
completeRescue(session);
|
||||
activeRescues.remove(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeRescue(RescueSession session) {
|
||||
Survivor rescuer = session.getRescuer();
|
||||
Hook hook = session.getHook();
|
||||
Survivor victim = hook.getHookedSurvivor();
|
||||
|
||||
hook.unhookSurvivor();
|
||||
if (victim != null) {
|
||||
victim.setState(PlayerState.INJURED);
|
||||
Player victimPlayer = victim.getPlayer();
|
||||
if (victimPlayer != null) {
|
||||
victimPlayer.teleport(hook.getLocation().clone().add(1, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
Player rescuerPlayer = rescuer.getPlayer();
|
||||
if (rescuerPlayer != null) {
|
||||
actionBar.sendMessage(rescuerPlayer, "<green>Rescue complete!");
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelRescue(UUID uuid) {
|
||||
RescueSession session = activeRescues.remove(uuid);
|
||||
if (session != null) {
|
||||
session.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRescuing(UUID uuid) {
|
||||
return activeRescues.containsKey(uuid);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (RescueSession session : activeRescues.values()) {
|
||||
session.cancel();
|
||||
}
|
||||
activeRescues.clear();
|
||||
}
|
||||
|
||||
private static class RescueSession {
|
||||
private final Survivor rescuer;
|
||||
private final Hook hook;
|
||||
private final int durationTicks;
|
||||
private int progressTicks;
|
||||
private BukkitTask task;
|
||||
|
||||
RescueSession(Survivor rescuer, Hook hook, int durationTicks) {
|
||||
this.rescuer = rescuer;
|
||||
this.hook = hook;
|
||||
this.durationTicks = durationTicks;
|
||||
this.progressTicks = 0;
|
||||
}
|
||||
|
||||
void incrementProgress() { progressTicks++; }
|
||||
boolean isComplete() { return progressTicks >= durationTicks; }
|
||||
double getProgressPercent() { return (double) progressTicks / durationTicks * 100; }
|
||||
|
||||
void setTask(BukkitTask task) { this.task = task; }
|
||||
void cancel() { if (task != null) task.cancel(); }
|
||||
|
||||
Survivor getRescuer() { return rescuer; }
|
||||
Hook getHook() { return hook; }
|
||||
}
|
||||
}
|
||||
71
src/main/java/dev/loki/hook/HookSacrifice.java
Normal file
71
src/main/java/dev/loki/hook/HookSacrifice.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import dev.loki.ui.TitleUI;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class HookSacrifice {
|
||||
private final mdbd plugin;
|
||||
private final TitleUI titleUI;
|
||||
|
||||
public HookSacrifice(mdbd plugin, TitleUI titleUI) {
|
||||
this.plugin = plugin;
|
||||
this.titleUI = titleUI;
|
||||
}
|
||||
|
||||
public void sacrifice(Survivor survivor, Hook hook) {
|
||||
Player player = survivor.getPlayer();
|
||||
if (player != null && player.isOnline()) {
|
||||
playSacrificeEffects(player);
|
||||
}
|
||||
|
||||
survivor.setState(PlayerState.DEAD);
|
||||
hook.unhookSurvivor();
|
||||
|
||||
announceSacrifice(survivor);
|
||||
}
|
||||
|
||||
private void playSacrificeEffects(Player player) {
|
||||
Title.Times times = Title.Times.times(
|
||||
Duration.ofMillis(500),
|
||||
Duration.ofSeconds(3),
|
||||
Duration.ofMillis(500)
|
||||
);
|
||||
|
||||
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),
|
||||
times
|
||||
);
|
||||
|
||||
player.showTitle(title);
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_WITHER_DEATH, 1.0f, 0.5f);
|
||||
player.playSound(player.getLocation(), Sound.AMBIENT_CAVE, 1.0f, 0.8f);
|
||||
}
|
||||
|
||||
private void announceSacrifice(Survivor survivor) {
|
||||
String name = "Unknown";
|
||||
Player player = survivor.getPlayer();
|
||||
if (player != null) {
|
||||
name = player.getName();
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
public void sacrificeIfExpired(Survivor survivor, Hook hook) {
|
||||
if (hook.getCurrentStage() == HookStage.SACRIFICE) {
|
||||
sacrifice(survivor, hook);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/main/java/dev/loki/hook/HookStage.java
Normal file
25
src/main/java/dev/loki/hook/HookStage.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
public enum HookStage {
|
||||
STAGE_1(60),
|
||||
STAGE_2(60),
|
||||
SACRIFICE(0);
|
||||
|
||||
private final int durationSeconds;
|
||||
|
||||
HookStage(int durationSeconds) {
|
||||
this.durationSeconds = durationSeconds;
|
||||
}
|
||||
|
||||
public int getDurationSeconds() {
|
||||
return durationSeconds;
|
||||
}
|
||||
|
||||
public HookStage next() {
|
||||
return switch (this) {
|
||||
case STAGE_1 -> STAGE_2;
|
||||
case STAGE_2 -> SACRIFICE;
|
||||
default -> SACRIFICE;
|
||||
};
|
||||
}
|
||||
}
|
||||
103
src/main/java/dev/loki/hook/HookStruggle.java
Normal file
103
src/main/java/dev/loki/hook/HookStruggle.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package dev.loki.hook;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import dev.loki.ui.TitleUI;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class HookStruggle {
|
||||
private final mdbd plugin;
|
||||
private final TitleUI titleUI;
|
||||
private final Map<UUID, BukkitTask> struggleTasks;
|
||||
private final Map<UUID, Integer> struggleProgress;
|
||||
private static final int STRUGGLE_INTERVAL_TICKS = 40;
|
||||
private static final int STRUGGLE_WINDOW_TICKS = 20;
|
||||
|
||||
public HookStruggle(mdbd plugin, TitleUI titleUI) {
|
||||
this.plugin = plugin;
|
||||
this.titleUI = titleUI;
|
||||
this.struggleTasks = new HashMap<>();
|
||||
this.struggleProgress = new HashMap<>();
|
||||
}
|
||||
|
||||
public void startStruggle(Survivor survivor, Hook hook) {
|
||||
UUID uuid = survivor.getUuid();
|
||||
if (struggleTasks.containsKey(uuid)) return;
|
||||
|
||||
BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(
|
||||
plugin,
|
||||
() -> triggerSkillCheck(survivor, hook),
|
||||
STRUGGLE_INTERVAL_TICKS,
|
||||
STRUGGLE_INTERVAL_TICKS
|
||||
);
|
||||
struggleTasks.put(uuid, task);
|
||||
struggleProgress.put(uuid, 0);
|
||||
}
|
||||
|
||||
public void stopStruggle(Survivor survivor) {
|
||||
UUID uuid = survivor.getUuid();
|
||||
BukkitTask task = struggleTasks.remove(uuid);
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
struggleProgress.remove(uuid);
|
||||
}
|
||||
|
||||
private void triggerSkillCheck(Survivor survivor, Hook hook) {
|
||||
UUID uuid = survivor.getUuid();
|
||||
var player = survivor.getPlayer();
|
||||
if (player == null || !player.isOnline()) {
|
||||
stopStruggle(survivor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hook.getCurrentStage() != HookStage.STAGE_2) {
|
||||
stopStruggle(survivor);
|
||||
return;
|
||||
}
|
||||
|
||||
titleUI.showSkillCheck(player, STRUGGLE_WINDOW_TICKS, (success) -> {
|
||||
if (success) {
|
||||
incrementProgress(survivor);
|
||||
} else {
|
||||
failStruggle(survivor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void incrementProgress(Survivor survivor) {
|
||||
Integer progress = struggleProgress.getOrDefault(survivor.getUuid(), 0);
|
||||
struggleProgress.put(survivor.getUuid(), progress + 1);
|
||||
}
|
||||
|
||||
private void failStruggle(Survivor survivor) {
|
||||
UUID uuid = survivor.getUuid();
|
||||
struggleProgress.put(uuid, struggleProgress.getOrDefault(uuid, 0) - 2);
|
||||
}
|
||||
|
||||
public void sacrifice(Survivor survivor) {
|
||||
stopStruggle(survivor);
|
||||
survivor.setState(PlayerState.DEAD);
|
||||
var player = survivor.getPlayer();
|
||||
if (player != null) {
|
||||
player.setHealth(0);
|
||||
}
|
||||
}
|
||||
|
||||
public int getStruggleProgress(UUID uuid) {
|
||||
return struggleProgress.getOrDefault(uuid, 0);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (BukkitTask task : struggleTasks.values()) {
|
||||
task.cancel();
|
||||
}
|
||||
struggleTasks.clear();
|
||||
struggleProgress.clear();
|
||||
}
|
||||
}
|
||||
25
src/main/java/dev/loki/item/BaseItem.java
Normal file
25
src/main/java/dev/loki/item/BaseItem.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public interface BaseItem {
|
||||
|
||||
ItemType getType();
|
||||
|
||||
ItemStack createItemStack();
|
||||
|
||||
boolean canUse(Player player);
|
||||
|
||||
void onUse(Player player);
|
||||
|
||||
void onDeplete(Player player);
|
||||
|
||||
int getCharges();
|
||||
|
||||
void setCharges(int charges);
|
||||
|
||||
boolean isDepleted();
|
||||
|
||||
String getItemId();
|
||||
}
|
||||
77
src/main/java/dev/loki/item/ChestLoot.java
Normal file
77
src/main/java/dev/loki/item/ChestLoot.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class ChestLoot {
|
||||
|
||||
private final Random random;
|
||||
|
||||
public ChestLoot() {
|
||||
this.random = ThreadLocalRandom.current();
|
||||
}
|
||||
|
||||
public ItemStack rollLoot() {
|
||||
int roll = random.nextInt(100);
|
||||
int currentWeight = 0;
|
||||
|
||||
currentWeight += 30;
|
||||
if (roll < currentWeight) {
|
||||
return createMedkit();
|
||||
}
|
||||
|
||||
currentWeight += 25;
|
||||
if (roll < currentWeight) {
|
||||
return createFlashlight();
|
||||
}
|
||||
|
||||
currentWeight += 25;
|
||||
if (roll < currentWeight) {
|
||||
return createToolbox();
|
||||
}
|
||||
|
||||
currentWeight += 12;
|
||||
if (roll < currentWeight) {
|
||||
return createKey();
|
||||
}
|
||||
|
||||
return createMap();
|
||||
}
|
||||
|
||||
private ItemStack createMedkit() {
|
||||
int charges = 12 + random.nextInt(8);
|
||||
return new ItemMedkit(charges).createItemStack();
|
||||
}
|
||||
|
||||
private ItemStack createFlashlight() {
|
||||
int charges = 6 + random.nextInt(6);
|
||||
return new ItemFlashlight(charges).createItemStack();
|
||||
}
|
||||
|
||||
private ItemStack createToolbox() {
|
||||
int charges = 10 + random.nextInt(10);
|
||||
return new ItemToolbox(charges).createItemStack();
|
||||
}
|
||||
|
||||
private ItemStack createKey() {
|
||||
int charges = 24 + random.nextInt(16);
|
||||
return new ItemKey(charges).createItemStack();
|
||||
}
|
||||
|
||||
private ItemStack createMap() {
|
||||
int charges = 8 + random.nextInt(12);
|
||||
return new ItemMap(charges).createItemStack();
|
||||
}
|
||||
|
||||
public int getRarity(ItemType type) {
|
||||
return switch (type) {
|
||||
case MEDKIT -> 30;
|
||||
case FLASHLIGHT -> 25;
|
||||
case TOOLBOX -> 25;
|
||||
case KEY -> 12;
|
||||
case MAP -> 8;
|
||||
};
|
||||
}
|
||||
}
|
||||
96
src/main/java/dev/loki/item/ItemFlashlight.java
Normal file
96
src/main/java/dev/loki/item/ItemFlashlight.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ItemFlashlight implements BaseItem {
|
||||
|
||||
private static final NamespacedKey ITEM_KEY = new NamespacedKey("mdbd", "item_type");
|
||||
private static final NamespacedKey CHARGES_KEY = new NamespacedKey("mdbd", "charges");
|
||||
|
||||
private final int charges;
|
||||
|
||||
public ItemFlashlight() {
|
||||
this(ItemType.FLASHLIGHT.getDefaultCharges());
|
||||
}
|
||||
|
||||
public ItemFlashlight(int charges) {
|
||||
this.charges = charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemType getType() {
|
||||
return ItemType.FLASHLIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createItemStack() {
|
||||
ItemStack item = new ItemStack(Material.TORCH);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§e" + getType().getDisplayName());
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7" + getType().getDescription(),
|
||||
"§eCharges: §f" + charges + "/" + getType().getDefaultCharges(),
|
||||
"§7Aim at Killer and click to blind"
|
||||
));
|
||||
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(ITEM_KEY, PersistentDataType.STRING, getItemId());
|
||||
pdc.set(CHARGES_KEY, PersistentDataType.INTEGER, charges);
|
||||
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Player player) {
|
||||
return charges > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUse(Player player) {
|
||||
if (charges > 0) {
|
||||
player.sendMessage("§eYou flash your flashlight!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeplete(Player player) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
player.sendMessage("§cYour flashlight batteries died!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharges() {
|
||||
return charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharges(int charges) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDepleted() {
|
||||
return charges <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return "FLASHLIGHT";
|
||||
}
|
||||
|
||||
public static boolean isFlashlight(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return false;
|
||||
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
||||
return "FLASHLIGHT".equals(pdc.get(ITEM_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
}
|
||||
96
src/main/java/dev/loki/item/ItemKey.java
Normal file
96
src/main/java/dev/loki/item/ItemKey.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ItemKey implements BaseItem {
|
||||
|
||||
private static final NamespacedKey ITEM_KEY = new NamespacedKey("mdbd", "item_type");
|
||||
private static final NamespacedKey CHARGES_KEY = new NamespacedKey("mdbd", "charges");
|
||||
|
||||
private final int charges;
|
||||
|
||||
public ItemKey() {
|
||||
this(ItemType.KEY.getDefaultCharges());
|
||||
}
|
||||
|
||||
public ItemKey(int charges) {
|
||||
this.charges = charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemType getType() {
|
||||
return ItemType.KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createItemStack() {
|
||||
ItemStack item = new ItemStack(Material.TRIPWIRE_HOOK);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§6" + getType().getDisplayName());
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7" + getType().getDescription(),
|
||||
"§eCharges: §f" + charges + "/" + getType().getDefaultCharges(),
|
||||
"§7Opens the Hatch instantly"
|
||||
));
|
||||
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(ITEM_KEY, PersistentDataType.STRING, getItemId());
|
||||
pdc.set(CHARGES_KEY, PersistentDataType.INTEGER, charges);
|
||||
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Player player) {
|
||||
return charges > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUse(Player player) {
|
||||
if (charges > 0) {
|
||||
player.sendMessage("§6You use the Key on the Hatch...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeplete(Player player) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
player.sendMessage("§cYour key breaks!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharges() {
|
||||
return charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharges(int charges) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDepleted() {
|
||||
return charges <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return "KEY";
|
||||
}
|
||||
|
||||
public static boolean isKey(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return false;
|
||||
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
||||
return "KEY".equals(pdc.get(ITEM_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
}
|
||||
96
src/main/java/dev/loki/item/ItemMap.java
Normal file
96
src/main/java/dev/loki/item/ItemMap.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ItemMap implements BaseItem {
|
||||
|
||||
private static final NamespacedKey ITEM_KEY = new NamespacedKey("mdbd", "item_type");
|
||||
private static final NamespacedKey CHARGES_KEY = new NamespacedKey("mdbd", "charges");
|
||||
|
||||
private final int charges;
|
||||
|
||||
public ItemMap() {
|
||||
this(ItemType.MAP.getDefaultCharges());
|
||||
}
|
||||
|
||||
public ItemMap(int charges) {
|
||||
this.charges = charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemType getType() {
|
||||
return ItemType.MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createItemStack() {
|
||||
ItemStack item = new ItemStack(Material.MAP);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§d" + getType().getDisplayName());
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7" + getType().getDescription(),
|
||||
"§eCharges: §f" + charges + "/" + getType().getDefaultCharges(),
|
||||
"§7Right-click to reveal generators"
|
||||
));
|
||||
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(ITEM_KEY, PersistentDataType.STRING, getItemId());
|
||||
pdc.set(CHARGES_KEY, PersistentDataType.INTEGER, charges);
|
||||
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Player player) {
|
||||
return charges > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUse(Player player) {
|
||||
if (charges > 0) {
|
||||
player.sendMessage("§dThe Map reveals nearby generators!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeplete(Player player) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
player.sendMessage("§cYour map has faded!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharges() {
|
||||
return charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharges(int charges) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDepleted() {
|
||||
return charges <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return "MAP";
|
||||
}
|
||||
|
||||
public static boolean isMap(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return false;
|
||||
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
||||
return "MAP".equals(pdc.get(ITEM_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
}
|
||||
96
src/main/java/dev/loki/item/ItemMedkit.java
Normal file
96
src/main/java/dev/loki/item/ItemMedkit.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ItemMedkit implements BaseItem {
|
||||
|
||||
private static final NamespacedKey ITEM_KEY = new NamespacedKey("mdbd", "item_type");
|
||||
private static final NamespacedKey CHARGES_KEY = new NamespacedKey("mdbd", "charges");
|
||||
|
||||
private final int charges;
|
||||
|
||||
public ItemMedkit() {
|
||||
this(ItemType.MEDKIT.getDefaultCharges());
|
||||
}
|
||||
|
||||
public ItemMedkit(int charges) {
|
||||
this.charges = charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemType getType() {
|
||||
return ItemType.MEDKIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createItemStack() {
|
||||
ItemStack item = new ItemStack(Material.PAPER);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§a" + getType().getDisplayName());
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7" + getType().getDescription(),
|
||||
"§eCharges: §f" + charges + "/" + getType().getDefaultCharges(),
|
||||
"§7Right-click to heal"
|
||||
));
|
||||
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(ITEM_KEY, PersistentDataType.STRING, getItemId());
|
||||
pdc.set(CHARGES_KEY, PersistentDataType.INTEGER, charges);
|
||||
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Player player) {
|
||||
return charges > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUse(Player player) {
|
||||
if (charges > 0) {
|
||||
player.sendMessage("§aYou start healing...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeplete(Player player) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
player.sendMessage("§cYour medkit has been depleted!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharges() {
|
||||
return charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharges(int charges) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDepleted() {
|
||||
return charges <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return "MEDKIT";
|
||||
}
|
||||
|
||||
public static boolean isMedkit(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return false;
|
||||
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
||||
return "MEDKIT".equals(pdc.get(ITEM_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
}
|
||||
98
src/main/java/dev/loki/item/ItemToolbox.java
Normal file
98
src/main/java/dev/loki/item/ItemToolbox.java
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package dev.loki.item;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ItemToolbox implements BaseItem {
|
||||
|
||||
private static final NamespacedKey ITEM_KEY = new NamespacedKey("mdbd", "item_type");
|
||||
private static final NamespacedKey CHARGES_KEY = new NamespacedKey("mdbd", "charges");
|
||||
|
||||
private final int charges;
|
||||
|
||||
public ItemToolbox() {
|
||||
this(ItemType.TOOLBOX.getDefaultCharges());
|
||||
}
|
||||
|
||||
public ItemToolbox(int charges) {
|
||||
this.charges = charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemType getType() {
|
||||
return ItemType.TOOLBOX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack createItemStack() {
|
||||
ItemStack item = new ItemStack(Material.CHEST);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta != null) {
|
||||
meta.setDisplayName("§b" + getType().getDisplayName());
|
||||
meta.setLore(Arrays.asList(
|
||||
"§7" + getType().getDescription(),
|
||||
"§eCharges: §f" + charges + "/" + getType().getDefaultCharges(),
|
||||
"§7Hold while repairing for bonus speed"
|
||||
));
|
||||
|
||||
PersistentDataContainer pdc = meta.getPersistentDataContainer();
|
||||
pdc.set(ITEM_KEY, PersistentDataType.STRING, getItemId());
|
||||
pdc.set(CHARGES_KEY, PersistentDataType.INTEGER, charges);
|
||||
|
||||
item.setItemMeta(meta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUse(Player player) {
|
||||
return charges > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUse(Player player) {
|
||||
player.sendMessage("§bYour toolbox speeds up the repair!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeplete(Player player) {
|
||||
player.getInventory().setItemInMainHand(null);
|
||||
player.sendMessage("§cYour toolbox is empty!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCharges() {
|
||||
return charges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharges(int charges) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDepleted() {
|
||||
return charges <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return "TOOLBOX";
|
||||
}
|
||||
|
||||
public static boolean isToolbox(ItemStack item) {
|
||||
if (item == null || !item.hasItemMeta()) return false;
|
||||
PersistentDataContainer pdc = item.getItemMeta().getPersistentDataContainer();
|
||||
return "TOOLBOX".equals(pdc.get(ITEM_KEY, PersistentDataType.STRING));
|
||||
}
|
||||
|
||||
public static double getRepairBonus() {
|
||||
return 0.15;
|
||||
}
|
||||
}
|
||||
31
src/main/java/dev/loki/item/ItemType.java
Normal file
31
src/main/java/dev/loki/item/ItemType.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package dev.loki.item;
|
||||
|
||||
public enum ItemType {
|
||||
MEDKIT("Medkit", "Heals one health state", 16),
|
||||
FLASHLIGHT("Flashlight", "Blinds the Killer", 8),
|
||||
TOOLBOX("Toolbox", "Speeds up repairs", 16),
|
||||
KEY("Key", "Opens the Hatch", 32),
|
||||
MAP("Map", "Reveals objectives", 16);
|
||||
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
private final int defaultCharges;
|
||||
|
||||
ItemType(String displayName, String description, int defaultCharges) {
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
this.defaultCharges = defaultCharges;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getDefaultCharges() {
|
||||
return defaultCharges;
|
||||
}
|
||||
}
|
||||
99
src/main/java/dev/loki/killer/HitValidator.java
Normal file
99
src/main/java/dev/loki/killer/HitValidator.java
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package dev.loki.killer;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class HitValidator {
|
||||
private static final double ATTACK_ANGLE = 60.0;
|
||||
private static final double MAX_VERTICAL_ANGLE = 45.0;
|
||||
|
||||
public static boolean isValidHit(Player killer, Player target, double maxRange) {
|
||||
if (!target.isOnline() || target.isDead()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Location killerLoc = killer.getEyeLocation();
|
||||
Location targetLoc = target.getEyeLocation();
|
||||
|
||||
double distance = killerLoc.distance(targetLoc);
|
||||
if (distance > maxRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasLineOfSight(killer, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isWithinAttackAngle(killer, targetLoc);
|
||||
}
|
||||
|
||||
private static boolean hasLineOfSight(Player from, Player to) {
|
||||
Location eyeLoc = from.getEyeLocation();
|
||||
Vector direction = to.getEyeLocation().toVector().subtract(eyeLoc.toVector());
|
||||
|
||||
double distance = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
RayTraceResult result = from.getWorld().rayTraceBlocks(
|
||||
eyeLoc,
|
||||
direction,
|
||||
distance,
|
||||
org.bukkit.FluidCollisionMode.NEVER,
|
||||
true
|
||||
);
|
||||
|
||||
return result == null || result.getHitBlock() == null;
|
||||
}
|
||||
|
||||
private static boolean isWithinAttackAngle(Player killer, Location target) {
|
||||
Vector killerDir = killer.getEyeLocation().getDirection();
|
||||
Vector toTarget = target.toVector().subtract(killer.getEyeLocation().toVector());
|
||||
|
||||
double horizontalAngle = Math.toDegrees(killerDir.clone().setY(0).angle(toTarget.clone().setY(0)));
|
||||
if (horizontalAngle > ATTACK_ANGLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double verticalDiff = toTarget.getY();
|
||||
double horizontalDist = Math.sqrt(toTarget.getX() * toTarget.getX() + toTarget.getZ() * toTarget.getZ());
|
||||
double verticalAngle = Math.toDegrees(Math.atan2(verticalDiff, horizontalDist));
|
||||
|
||||
return Math.abs(verticalAngle) <= MAX_VERTICAL_ANGLE;
|
||||
}
|
||||
|
||||
public static boolean isValidCarryPosition(Player killer, Player carried) {
|
||||
Location kLoc = killer.getLocation();
|
||||
Location cLoc = carried.getLocation();
|
||||
|
||||
double horizontalDist = Math.sqrt(
|
||||
Math.pow(kLoc.getX() - cLoc.getX(), 2) +
|
||||
Math.pow(kLoc.getZ() - cLoc.getZ(), 2)
|
||||
);
|
||||
|
||||
double yDiff = Math.abs(kLoc.getY() - cLoc.getY());
|
||||
|
||||
return horizontalDist <= 2.0 && yDiff <= 1.5;
|
||||
}
|
||||
|
||||
public static boolean hasClearPath(Player from, Location to) {
|
||||
Location eyeLoc = from.getEyeLocation();
|
||||
Vector direction = to.toVector().subtract(eyeLoc.toVector());
|
||||
double distance = direction.length();
|
||||
direction.normalize();
|
||||
|
||||
RayTraceResult result = from.getWorld().rayTraceBlocks(
|
||||
eyeLoc,
|
||||
direction,
|
||||
distance,
|
||||
org.bukkit.FluidCollisionMode.NEVER,
|
||||
true
|
||||
);
|
||||
|
||||
return result == null || result.getHitBlock() == null;
|
||||
}
|
||||
}
|
||||
126
src/main/java/dev/loki/killer/KillerAttack.java
Normal file
126
src/main/java/dev/loki/killer/KillerAttack.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package dev.loki.killer;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.damage.DamageHandler;
|
||||
import dev.loki.player.Killer;
|
||||
import dev.loki.player.PlayerManager;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KillerAttack {
|
||||
private final Plugin plugin;
|
||||
private final PlayerManager playerManager;
|
||||
private final DamageHandler damageHandler;
|
||||
private final GameConfig config;
|
||||
private final Map<UUID, Long> cooldowns;
|
||||
private BukkitTask cleanupTask;
|
||||
|
||||
public KillerAttack(Plugin plugin, PlayerManager playerManager,
|
||||
DamageHandler damageHandler, GameConfig config) {
|
||||
this.plugin = plugin;
|
||||
this.playerManager = playerManager;
|
||||
this.damageHandler = damageHandler;
|
||||
this.config = config;
|
||||
this.cooldowns = new HashMap<>();
|
||||
startCleanupTask();
|
||||
}
|
||||
|
||||
public boolean tryAttack(Killer killer, Survivor target) {
|
||||
if (!canAttack(killer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player killerPlayer = killer.getPlayer();
|
||||
Player targetPlayer = target.getPlayer();
|
||||
|
||||
if (killerPlayer == null || targetPlayer == null) return false;
|
||||
if (!targetPlayer.isOnline()) return false;
|
||||
|
||||
if (!HitValidator.isValidHit(killerPlayer, targetPlayer, config.getAttackRange())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!damageHandler.applyDamage(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
applyCooldown(killer);
|
||||
playAttackEffects(killerPlayer, targetPlayer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canAttack(Killer killer) {
|
||||
Long lastAttack = cooldowns.get(killer.getUuid());
|
||||
if (lastAttack == null) return true;
|
||||
|
||||
int cooldownTicks = config.getAttackCooldown();
|
||||
long currentTick = Bukkit.getCurrentTick();
|
||||
return currentTick - lastAttack >= cooldownTicks;
|
||||
}
|
||||
|
||||
private void applyCooldown(Killer killer) {
|
||||
cooldowns.put(killer.getUuid(), Bukkit.getCurrentTick());
|
||||
}
|
||||
|
||||
public double getCooldownProgress(Killer killer) {
|
||||
Long lastAttack = cooldowns.get(killer.getUuid());
|
||||
if (lastAttack == null) return 1.0;
|
||||
|
||||
int cooldownTicks = config.getAttackCooldown();
|
||||
long currentTick = Bukkit.getCurrentTick();
|
||||
long elapsed = currentTick - lastAttack;
|
||||
|
||||
return Math.min(1.0, (double) elapsed / cooldownTicks);
|
||||
}
|
||||
|
||||
public boolean isOnCooldown(Killer killer) {
|
||||
return !canAttack(killer);
|
||||
}
|
||||
|
||||
private void playAttackEffects(Player killer, Player target) {
|
||||
killer.getWorld().playSound(killer.getLocation(),
|
||||
org.bukkit.Sound.ENTITY_PLAYER_ATTACK_STRONG, 1.0f, 0.8f);
|
||||
|
||||
target.getWorld().playSound(target.getLocation(),
|
||||
org.bukkit.Sound.ENTITY_PLAYER_HURT, 1.0f, 1.0f);
|
||||
|
||||
org.bukkit.Particle particle = target.getHealth() <= 4 ?
|
||||
org.bukkit.Particle.DAMAGE_INDICATOR : org.bukkit.Particle.CRIT;
|
||||
|
||||
target.getWorld().spawnParticle(particle, target.getLocation().add(0, 1, 0),
|
||||
10, 0.3, 0.5, 0.3, 0.1);
|
||||
}
|
||||
|
||||
public void attackNearest(Killer killer) {
|
||||
Player killerPlayer = killer.getPlayer();
|
||||
if (killerPlayer == null) return;
|
||||
|
||||
Survivor nearest = playerManager.getNearestSurvivor(killerPlayer, config.getAttackRange());
|
||||
if (nearest != null) {
|
||||
tryAttack(killer, nearest);
|
||||
}
|
||||
}
|
||||
|
||||
private void startCleanupTask() {
|
||||
cleanupTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
long currentTick = Bukkit.getCurrentTick();
|
||||
int cooldownTicks = config.getAttackCooldown();
|
||||
cooldowns.entrySet().removeIf(e -> currentTick - e.getValue() > cooldownTicks * 2);
|
||||
}, 1200L, 1200L);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (cleanupTask != null) {
|
||||
cleanupTask.cancel();
|
||||
}
|
||||
cooldowns.clear();
|
||||
}
|
||||
}
|
||||
189
src/main/java/dev/loki/killer/KillerCarry.java
Normal file
189
src/main/java/dev/loki/killer/KillerCarry.java
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package dev.loki.killer;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.player.Killer;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KillerCarry {
|
||||
private final Plugin plugin;
|
||||
private final GameConfig config;
|
||||
private final Map<UUID, CarryData> activeCarries;
|
||||
private BukkitTask carryTask;
|
||||
|
||||
private static class CarryData {
|
||||
final Killer killer;
|
||||
final Survivor survivor;
|
||||
double wiggleProgress;
|
||||
|
||||
CarryData(Killer killer, Survivor survivor) {
|
||||
this.killer = killer;
|
||||
this.survivor = survivor;
|
||||
this.wiggleProgress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public KillerCarry(Plugin plugin, GameConfig config) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.activeCarries = new HashMap<>();
|
||||
}
|
||||
|
||||
public boolean tryPickup(Killer killer, Survivor survivor) {
|
||||
if (!canPickup(killer, survivor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player killerPlayer = killer.getPlayer();
|
||||
Player survivorPlayer = survivor.getPlayer();
|
||||
|
||||
if (killerPlayer == null || survivorPlayer == null) return false;
|
||||
|
||||
if (!HitValidator.isValidCarryPosition(killerPlayer, survivorPlayer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
startCarry(killer, survivor);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canPickup(Killer killer, Survivor survivor) {
|
||||
if (killer.isCarrying()) return false;
|
||||
if (!survivor.getState().name().equals("DOWNED")) return false;
|
||||
|
||||
Player killerPlayer = killer.getPlayer();
|
||||
Player survivorPlayer = survivor.getPlayer();
|
||||
|
||||
return killerPlayer != null && survivorPlayer != null && survivorPlayer.isOnline();
|
||||
}
|
||||
|
||||
private void startCarry(Killer killer, Survivor survivor) {
|
||||
killer.setCarrying(true);
|
||||
|
||||
Player survivorPlayer = survivor.getPlayer();
|
||||
if (survivorPlayer != null) {
|
||||
survivorPlayer.setInvisible(true);
|
||||
}
|
||||
|
||||
CarryData data = new CarryData(killer, survivor);
|
||||
activeCarries.put(killer.getUuid(), data);
|
||||
|
||||
if (carryTask == null) {
|
||||
startCarryTask();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean drop(Killer killer) {
|
||||
CarryData data = activeCarries.remove(killer.getUuid());
|
||||
if (data == null) return false;
|
||||
|
||||
stopCarry(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void stopCarry(CarryData data) {
|
||||
data.killer.setCarrying(false);
|
||||
|
||||
Player survivorPlayer = data.survivor.getPlayer();
|
||||
if (survivorPlayer != null) {
|
||||
survivorPlayer.setInvisible(false);
|
||||
survivorPlayer.teleport(data.killer.getPlayer().getLocation());
|
||||
}
|
||||
|
||||
if (activeCarries.isEmpty() && carryTask != null) {
|
||||
carryTask.cancel();
|
||||
carryTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void startCarryTask() {
|
||||
carryTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
activeCarries.values().removeIf(data -> {
|
||||
if (!isValidCarry(data)) {
|
||||
stopCarry(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
updateCarryPosition(data);
|
||||
updateWiggle(data);
|
||||
|
||||
return false;
|
||||
});
|
||||
}, 1L, 1L);
|
||||
}
|
||||
|
||||
private boolean isValidCarry(CarryData data) {
|
||||
Player killerPlayer = data.killer.getPlayer();
|
||||
Player survivorPlayer = data.survivor.getPlayer();
|
||||
|
||||
if (killerPlayer == null || survivorPlayer == null) return false;
|
||||
if (!killerPlayer.isOnline() || !survivorPlayer.isOnline()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateCarryPosition(CarryData data) {
|
||||
Player killerPlayer = data.killer.getPlayer();
|
||||
Player survivorPlayer = data.survivor.getPlayer();
|
||||
|
||||
Location carryLoc = killerPlayer.getLocation().clone().add(0, -0.5, 0);
|
||||
survivorPlayer.teleport(carryLoc);
|
||||
}
|
||||
|
||||
private void updateWiggle(CarryData data) {
|
||||
Player survivorPlayer = data.survivor.getPlayer();
|
||||
if (survivorPlayer == null) return;
|
||||
|
||||
if (survivorPlayer.isSneaking()) {
|
||||
data.wiggleProgress += 1.5;
|
||||
}
|
||||
|
||||
if (data.wiggleProgress >= 100) {
|
||||
forceDrop(data.killer);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceDrop(Killer killer) {
|
||||
CarryData data = activeCarries.remove(killer.getUuid());
|
||||
if (data == null) return;
|
||||
|
||||
Player survivorPlayer = data.survivor.getPlayer();
|
||||
if (survivorPlayer != null) {
|
||||
Location dropLoc = survivorPlayer.getLocation();
|
||||
dropLoc.add(killer.getPlayer().getLocation().getDirection().multiply(1.5));
|
||||
survivorPlayer.teleport(dropLoc);
|
||||
}
|
||||
|
||||
stopCarry(data);
|
||||
}
|
||||
|
||||
public boolean isCarrying(Killer killer) {
|
||||
return activeCarries.containsKey(killer.getUuid());
|
||||
}
|
||||
|
||||
public Survivor getCarried(Killer killer) {
|
||||
CarryData data = activeCarries.get(killer.getUuid());
|
||||
return data != null ? data.survivor : null;
|
||||
}
|
||||
|
||||
public double getWiggleProgress(Killer killer) {
|
||||
CarryData data = activeCarries.get(killer.getUuid());
|
||||
return data != null ? data.wiggleProgress : 0;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (carryTask != null) {
|
||||
carryTask.cancel();
|
||||
}
|
||||
activeCarries.values().forEach(this::stopCarry);
|
||||
activeCarries.clear();
|
||||
}
|
||||
}
|
||||
126
src/main/java/dev/loki/killer/KillerController.java
Normal file
126
src/main/java/dev/loki/killer/KillerController.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package dev.loki.killer;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.damage.AttackListener;
|
||||
import dev.loki.damage.DamageHandler;
|
||||
import dev.loki.player.Killer;
|
||||
import dev.loki.player.PlayerManager;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class KillerController {
|
||||
private final Plugin plugin;
|
||||
private final PlayerManager playerManager;
|
||||
private final DamageHandler damageHandler;
|
||||
private final GameConfig config;
|
||||
|
||||
private final KillerAttack attack;
|
||||
private final KillerCarry carry;
|
||||
private final KillerMovement movement;
|
||||
private AttackListener attackListener;
|
||||
|
||||
public KillerController(Plugin plugin, PlayerManager playerManager,
|
||||
DamageHandler damageHandler, GameConfig config) {
|
||||
this.plugin = plugin;
|
||||
this.playerManager = playerManager;
|
||||
this.damageHandler = damageHandler;
|
||||
this.config = config;
|
||||
|
||||
this.attack = new KillerAttack(plugin, playerManager, damageHandler, config);
|
||||
this.carry = new KillerCarry(plugin, config);
|
||||
this.movement = new KillerMovement(plugin, config);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
attackListener = new AttackListener(playerManager, damageHandler,
|
||||
(killer, survivor) -> tryAttack(killer, survivor));
|
||||
attackListener.register(plugin);
|
||||
|
||||
for (Killer killer : playerManager.getKillers()) {
|
||||
movement.startTracking(killer);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (attackListener != null) {
|
||||
attackListener.unregister();
|
||||
}
|
||||
attack.shutdown();
|
||||
carry.shutdown();
|
||||
movement.shutdown();
|
||||
}
|
||||
|
||||
public boolean tryAttack(Killer killer, Survivor target) {
|
||||
boolean success = attack.tryAttack(killer, target);
|
||||
if (success) {
|
||||
movement.applyAttackSlowdown(killer);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean tryPickup(Killer killer, Survivor target) {
|
||||
boolean success = carry.tryPickup(killer, target);
|
||||
if (success) {
|
||||
movement.updateSpeed(killer);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean tryDrop(Killer killer) {
|
||||
boolean success = carry.drop(killer);
|
||||
if (success) {
|
||||
movement.updateSpeed(killer);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public void onKillerJoin(Killer killer) {
|
||||
movement.startTracking(killer);
|
||||
}
|
||||
|
||||
public void onKillerLeave(Killer killer) {
|
||||
movement.stopTracking(killer);
|
||||
carry.drop(killer);
|
||||
}
|
||||
|
||||
public void registerKiller(Killer killer) {
|
||||
movement.startTracking(killer);
|
||||
}
|
||||
|
||||
public void unregisterKiller(Killer killer) {
|
||||
carry.drop(killer);
|
||||
movement.stopTracking(killer);
|
||||
}
|
||||
|
||||
public boolean isOnCooldown(Killer killer) {
|
||||
return attack.isOnCooldown(killer);
|
||||
}
|
||||
|
||||
public double getCooldownProgress(Killer killer) {
|
||||
return attack.getCooldownProgress(killer);
|
||||
}
|
||||
|
||||
public boolean isCarrying(Killer killer) {
|
||||
return carry.isCarrying(killer);
|
||||
}
|
||||
|
||||
public Survivor getCarried(Killer killer) {
|
||||
return carry.getCarried(killer);
|
||||
}
|
||||
|
||||
public double getWiggleProgress(Killer killer) {
|
||||
return carry.getWiggleProgress(killer);
|
||||
}
|
||||
|
||||
public KillerAttack getAttack() {
|
||||
return attack;
|
||||
}
|
||||
|
||||
public KillerCarry getCarry() {
|
||||
return carry;
|
||||
}
|
||||
|
||||
public KillerMovement getMovement() {
|
||||
return movement;
|
||||
}
|
||||
}
|
||||
119
src/main/java/dev/loki/killer/KillerMovement.java
Normal file
119
src/main/java/dev/loki/killer/KillerMovement.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package dev.loki.killer;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.player.Killer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KillerMovement {
|
||||
private final Plugin plugin;
|
||||
private final GameConfig config;
|
||||
private final Map<UUID, BukkitTask> slowdownTasks;
|
||||
private BukkitTask updateTask;
|
||||
|
||||
private static final double BASE_SPEED = 0.1;
|
||||
|
||||
public KillerMovement(Plugin plugin, GameConfig config) {
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
this.slowdownTasks = new HashMap<>();
|
||||
}
|
||||
|
||||
public void startTracking(Killer killer) {
|
||||
if (updateTask == null) {
|
||||
startUpdateTask();
|
||||
}
|
||||
updateSpeed(killer);
|
||||
}
|
||||
|
||||
public void stopTracking(Killer killer) {
|
||||
BukkitTask task = slowdownTasks.remove(killer.getUuid());
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
|
||||
Player player = killer.getPlayer();
|
||||
if (player != null) {
|
||||
resetSpeed(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void startUpdateTask() {
|
||||
updateTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
}, 20L, 20L);
|
||||
}
|
||||
|
||||
public void updateSpeed(Killer killer) {
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null || !player.isOnline()) return;
|
||||
|
||||
double multiplier = killer.isCarrying() ?
|
||||
config.getKillerCarrySpeed() : config.getKillerSpeed();
|
||||
|
||||
applySpeed(player, multiplier);
|
||||
}
|
||||
|
||||
private void applySpeed(Player player, double multiplier) {
|
||||
AttributeInstance speedAttr = player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
|
||||
if (speedAttr == null) return;
|
||||
|
||||
double newSpeed = BASE_SPEED * multiplier;
|
||||
speedAttr.setBaseValue(newSpeed);
|
||||
}
|
||||
|
||||
private void resetSpeed(Player player) {
|
||||
AttributeInstance speedAttr = player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED);
|
||||
if (speedAttr == null) return;
|
||||
|
||||
speedAttr.setBaseValue(BASE_SPEED);
|
||||
}
|
||||
|
||||
public void applyTemporarySlowdown(Killer killer, double multiplier, int ticks) {
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
cancelExistingSlowdown(killer);
|
||||
|
||||
BukkitTask task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
slowdownTasks.remove(killer.getUuid());
|
||||
updateSpeed(killer);
|
||||
}, ticks);
|
||||
|
||||
slowdownTasks.put(killer.getUuid(), task);
|
||||
applySpeed(player, multiplier);
|
||||
}
|
||||
|
||||
private void cancelExistingSlowdown(Killer killer) {
|
||||
BukkitTask task = slowdownTasks.remove(killer.getUuid());
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void applyAttackSlowdown(Killer killer) {
|
||||
applyTemporarySlowdown(killer, 0.85, 30);
|
||||
}
|
||||
|
||||
public double getSpeedMultiplier(Killer killer) {
|
||||
if (slowdownTasks.containsKey(killer.getUuid())) {
|
||||
return 0.85;
|
||||
}
|
||||
return killer.isCarrying() ? config.getKillerCarrySpeed() : config.getKillerSpeed();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel();
|
||||
}
|
||||
slowdownTasks.values().forEach(BukkitTask::cancel);
|
||||
slowdownTasks.clear();
|
||||
}
|
||||
}
|
||||
117
src/main/java/dev/loki/objective/Generator.java
Normal file
117
src/main/java/dev/loki/objective/Generator.java
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package dev.loki.objective;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Generator {
|
||||
private final int id;
|
||||
private final Location location;
|
||||
private double progress;
|
||||
private boolean isCompleted;
|
||||
private boolean isBlocked;
|
||||
private UUID currentRepairer;
|
||||
private int regressionTicks;
|
||||
private boolean wasKicked;
|
||||
|
||||
public Generator(int id, Location location) {
|
||||
this.id = id;
|
||||
this.location = location;
|
||||
this.progress = 0.0;
|
||||
this.isCompleted = false;
|
||||
this.isBlocked = false;
|
||||
this.currentRepairer = null;
|
||||
this.regressionTicks = 0;
|
||||
this.wasKicked = false;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(double progress) {
|
||||
this.progress = Math.max(0, Math.min(100, progress));
|
||||
if (this.progress >= 100) {
|
||||
this.progress = 100;
|
||||
this.isCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void addProgress(double amount) {
|
||||
setProgress(progress + amount);
|
||||
}
|
||||
|
||||
public void removeProgress(double amount) {
|
||||
setProgress(progress - amount);
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return isBlocked;
|
||||
}
|
||||
|
||||
public void setBlocked(boolean blocked) {
|
||||
this.isBlocked = blocked;
|
||||
}
|
||||
|
||||
public UUID getCurrentRepairer() {
|
||||
return currentRepairer;
|
||||
}
|
||||
|
||||
public void setCurrentRepairer(UUID repairer) {
|
||||
this.currentRepairer = repairer;
|
||||
}
|
||||
|
||||
public boolean isBeingRepaired() {
|
||||
return currentRepairer != null;
|
||||
}
|
||||
|
||||
public int getRegressionTicks() {
|
||||
return regressionTicks;
|
||||
}
|
||||
|
||||
public void setRegressionTicks(int ticks) {
|
||||
this.regressionTicks = ticks;
|
||||
}
|
||||
|
||||
public void decrementRegression() {
|
||||
if (regressionTicks > 0) {
|
||||
regressionTicks--;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUnderRegression() {
|
||||
return regressionTicks > 0 && !isCompleted && !isBeingRepaired();
|
||||
}
|
||||
|
||||
public boolean wasKicked() {
|
||||
return wasKicked;
|
||||
}
|
||||
|
||||
public void setKicked(boolean kicked) {
|
||||
this.wasKicked = kicked;
|
||||
}
|
||||
|
||||
public boolean canBeRepaired() {
|
||||
return !isCompleted && !isBlocked;
|
||||
}
|
||||
|
||||
public Block getBlock() {
|
||||
return location.getBlock();
|
||||
}
|
||||
|
||||
public double getDistance(Location loc) {
|
||||
return location.distance(loc);
|
||||
}
|
||||
}
|
||||
123
src/main/java/dev/loki/objective/GeneratorManager.java
Normal file
123
src/main/java/dev/loki/objective/GeneratorManager.java
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package dev.loki.objective;
|
||||
|
||||
import dev.loki.config.LocationsConfig;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeneratorManager {
|
||||
private final Map<Integer, Generator> generators;
|
||||
private final int requiredCount;
|
||||
private int completedCount;
|
||||
|
||||
public GeneratorManager(LocationsConfig locationsConfig, int requiredCount) {
|
||||
this.generators = new HashMap<>();
|
||||
this.requiredCount = requiredCount;
|
||||
this.completedCount = 0;
|
||||
loadGenerators(locationsConfig);
|
||||
}
|
||||
|
||||
private void loadGenerators(LocationsConfig config) {
|
||||
List<Location> locations = config.getGenerators();
|
||||
int id = 0;
|
||||
for (Location loc : locations) {
|
||||
generators.put(id, new Generator(id, loc));
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
public Generator getGenerator(int id) {
|
||||
return generators.get(id);
|
||||
}
|
||||
|
||||
public Generator getGeneratorAt(Location location) {
|
||||
for (Generator gen : generators.values()) {
|
||||
if (gen.getLocation().distance(location) < 2.0) {
|
||||
return gen;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Generator> getAllGenerators() {
|
||||
return new ArrayList<>(generators.values());
|
||||
}
|
||||
|
||||
public List<Generator> getIncompleteGenerators() {
|
||||
List<Generator> incomplete = new ArrayList<>();
|
||||
for (Generator gen : generators.values()) {
|
||||
if (!gen.isCompleted()) {
|
||||
incomplete.add(gen);
|
||||
}
|
||||
}
|
||||
return incomplete;
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return generators.size();
|
||||
}
|
||||
|
||||
public int getRequiredCount() {
|
||||
return requiredCount;
|
||||
}
|
||||
|
||||
public int getCompletedCount() {
|
||||
return completedCount;
|
||||
}
|
||||
|
||||
public int getRemainingRequired() {
|
||||
return Math.max(0, requiredCount - completedCount);
|
||||
}
|
||||
|
||||
public void onGeneratorCompleted(Generator generator) {
|
||||
if (generator.isCompleted() && !wasCompletedBefore(generator)) {
|
||||
completedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wasCompletedBefore(Generator generator) {
|
||||
return completedCount >= countCompleted();
|
||||
}
|
||||
|
||||
private int countCompleted() {
|
||||
int count = 0;
|
||||
for (Generator gen : generators.values()) {
|
||||
if (gen.isCompleted()) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean areAllRequiredCompleted() {
|
||||
return completedCount >= requiredCount;
|
||||
}
|
||||
|
||||
public double getAverageProgress() {
|
||||
if (generators.isEmpty()) return 0;
|
||||
double total = 0;
|
||||
for (Generator gen : generators.values()) {
|
||||
total += gen.getProgress();
|
||||
}
|
||||
return total / generators.size();
|
||||
}
|
||||
|
||||
public int getActiveRepairCount() {
|
||||
int count = 0;
|
||||
for (Generator gen : generators.values()) {
|
||||
if (gen.isBeingRepaired()) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
for (Generator gen : generators.values()) {
|
||||
if (gen.isUnderRegression()) {
|
||||
double rate = gen.wasKicked() ? 0.125 : 0.0125;
|
||||
gen.removeProgress(rate);
|
||||
}
|
||||
gen.decrementRegression();
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/main/java/dev/loki/objective/GeneratorRepair.java
Normal file
186
src/main/java/dev/loki/objective/GeneratorRepair.java
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package dev.loki.objective;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.Survivor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GeneratorRepair {
|
||||
private final mdbd plugin;
|
||||
private final GeneratorManager manager;
|
||||
private final Map<UUID, RepairSession> sessions;
|
||||
private final SkillCheck skillCheck;
|
||||
|
||||
private static final double REPAIR_PER_TICK = 100.0 / 1600.0;
|
||||
private static final double REPAIR_RANGE = 3.0;
|
||||
|
||||
public GeneratorRepair(mdbd plugin, GeneratorManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
this.sessions = new HashMap<>();
|
||||
this.skillCheck = new SkillCheck(plugin);
|
||||
}
|
||||
|
||||
public void startRepair(Player player, Generator generator) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
if (sessions.containsKey(uuid)) {
|
||||
stopRepair(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!generator.canBeRepaired()) {
|
||||
player.sendMessage(Component.text("Генератор не может быть починен!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (generator.isBeingRepaired() && !generator.getCurrentRepairer().equals(uuid)) {
|
||||
player.sendMessage(Component.text("Кто-то уже чинит этот генератор!"));
|
||||
return;
|
||||
}
|
||||
|
||||
generator.setCurrentRepairer(uuid);
|
||||
RepairSession session = new RepairSession(player, generator);
|
||||
sessions.put(uuid, session);
|
||||
generator.setBlocked(true);
|
||||
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<gray>Зажмите <yellow>ПКМ</yellow> для починки"));
|
||||
}
|
||||
|
||||
public void stopRepair(Player player) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
RepairSession session = sessions.remove(uuid);
|
||||
|
||||
if (session != null) {
|
||||
session.generator.setCurrentRepairer(null);
|
||||
session.generator.setBlocked(false);
|
||||
player.sendActionBar(Component.empty());
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
skillCheck.tick();
|
||||
|
||||
for (RepairSession session : new HashMap<>(sessions).values()) {
|
||||
if (!updateRepair(session)) {
|
||||
stopRepair(session.player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateRepair(RepairSession session) {
|
||||
Player player = session.player;
|
||||
Generator generator = session.generator;
|
||||
|
||||
if (!player.isOnline()) return false;
|
||||
if (!player.isSneaking()) return false;
|
||||
if (generator.getDistance(player.getLocation()) > REPAIR_RANGE) return false;
|
||||
if (generator.isCompleted()) return false;
|
||||
|
||||
if (skillCheck.isActive(player.getUniqueId())) {
|
||||
showSkillCheckUI(player, session);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (session.skillCheckCooldown > 0) {
|
||||
session.skillCheckCooldown--;
|
||||
} else if (shouldTriggerSkillCheck()) {
|
||||
skillCheck.start(player.getUniqueId(), generator);
|
||||
session.skillCheckCooldown = 100 + (int)(Math.random() * 200);
|
||||
return true;
|
||||
}
|
||||
|
||||
generator.addProgress(REPAIR_PER_TICK);
|
||||
showProgressBar(player, session);
|
||||
|
||||
if (generator.isCompleted()) {
|
||||
onGeneratorComplete(generator, player);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showSkillCheckUI(Player player, RepairSession session) {
|
||||
SkillCheck.CheckState state = skillCheck.getState(player.getUniqueId());
|
||||
if (state != null) {
|
||||
String visual = state.getVisualRepresentation();
|
||||
player.sendActionBar(MiniMessage.miniMessage().deserialize(
|
||||
"<yellow>Skill Check! " + visual
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void showProgressBar(Player player, RepairSession session) {
|
||||
double progress = session.generator.getProgress();
|
||||
String bar = renderProgressBar(progress);
|
||||
player.sendActionBar(MiniMessage.miniMessage().deserialize(
|
||||
"<gray>Починка: " + bar
|
||||
));
|
||||
}
|
||||
|
||||
private String renderProgressBar(double progress) {
|
||||
int filled = (int) (10 * progress / 100.0);
|
||||
int empty = 10 - filled;
|
||||
StringBuilder bar = new StringBuilder("<yellow>");
|
||||
bar.append("█".repeat(Math.max(0, filled)));
|
||||
bar.append("<gray>");
|
||||
bar.append("░".repeat(Math.max(0, empty)));
|
||||
bar.append(" <white>").append((int)progress).append("%");
|
||||
return bar.toString();
|
||||
}
|
||||
|
||||
private void onGeneratorComplete(Generator generator, Player player) {
|
||||
manager.onGeneratorCompleted(generator);
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>Генератор починен! <gray>(" + manager.getCompletedCount() + "/" + manager.getRequiredCount() + ")"
|
||||
));
|
||||
}
|
||||
|
||||
private boolean shouldTriggerSkillCheck() {
|
||||
return Math.random() < 0.02;
|
||||
}
|
||||
|
||||
public void onSkillCheckResult(UUID playerId, boolean success, Generator generator) {
|
||||
Player player = Bukkit.getPlayer(playerId);
|
||||
if (player == null) return;
|
||||
|
||||
if (success) {
|
||||
generator.addProgress(5.0);
|
||||
player.sendActionBar(MiniMessage.miniMessage().deserialize("<green>Отлично! +5%"));
|
||||
} else {
|
||||
generator.removeProgress(10.0);
|
||||
generator.setKicked(true);
|
||||
generator.setRegressionTicks(600);
|
||||
player.sendMessage(MiniMessage.miniMessage().deserialize("<red>Ошибка! Потеря прогресса!"));
|
||||
stopRepair(player);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRepairing(UUID uuid) {
|
||||
return sessions.containsKey(uuid);
|
||||
}
|
||||
|
||||
public RepairSession getSession(UUID uuid) {
|
||||
return sessions.get(uuid);
|
||||
}
|
||||
|
||||
public static class RepairSession {
|
||||
final Player player;
|
||||
final Generator generator;
|
||||
int skillCheckCooldown;
|
||||
|
||||
RepairSession(Player player, Generator generator) {
|
||||
this.player = player;
|
||||
this.generator = generator;
|
||||
this.skillCheckCooldown = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/main/java/dev/loki/objective/SkillCheck.java
Normal file
150
src/main/java/dev/loki/objective/SkillCheck.java
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
package dev.loki.objective;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SkillCheck {
|
||||
private final mdbd plugin;
|
||||
private final Map<UUID, CheckState> activeChecks;
|
||||
|
||||
private static final double ZONE_SIZE = 0.15;
|
||||
private static final double ROTATION_SPEED = 3.0;
|
||||
|
||||
public SkillCheck(mdbd plugin) {
|
||||
this.plugin = plugin;
|
||||
this.activeChecks = new HashMap<>();
|
||||
}
|
||||
|
||||
public void start(UUID playerId, Generator generator) {
|
||||
double zoneStart = Math.random() * 0.7;
|
||||
double zoneEnd = zoneStart + ZONE_SIZE;
|
||||
|
||||
CheckState state = new CheckState(
|
||||
playerId,
|
||||
generator,
|
||||
zoneStart,
|
||||
zoneEnd,
|
||||
0.0,
|
||||
ROTATION_SPEED
|
||||
);
|
||||
activeChecks.put(playerId, state);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
for (CheckState state : new HashMap<>(activeChecks).values()) {
|
||||
state.update();
|
||||
|
||||
if (state.isExpired()) {
|
||||
onFail(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean tryComplete(UUID playerId, boolean isSuccessZone) {
|
||||
CheckState state = activeChecks.get(playerId);
|
||||
if (state == null) return false;
|
||||
|
||||
if (isSuccessZone) {
|
||||
onSuccess(state);
|
||||
} else {
|
||||
onFail(state);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSuccess(CheckState state) {
|
||||
activeChecks.remove(state.playerId);
|
||||
GeneratorRepair repair = getGeneratorRepair();
|
||||
if (repair != null) {
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
repair.onSkillCheckResult(state.playerId, true, state.generator)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void onFail(CheckState state) {
|
||||
activeChecks.remove(state.playerId);
|
||||
GeneratorRepair repair = getGeneratorRepair();
|
||||
if (repair != null) {
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
repair.onSkillCheckResult(state.playerId, false, state.generator)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private GeneratorRepair getGeneratorRepair() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isActive(UUID playerId) {
|
||||
return activeChecks.containsKey(playerId);
|
||||
}
|
||||
|
||||
public CheckState getState(UUID playerId) {
|
||||
return activeChecks.get(playerId);
|
||||
}
|
||||
|
||||
public static class CheckState {
|
||||
final UUID playerId;
|
||||
final Generator generator;
|
||||
final double zoneStart;
|
||||
final double zoneEnd;
|
||||
double pointerPosition;
|
||||
final double rotationSpeed;
|
||||
int ticks;
|
||||
|
||||
CheckState(UUID playerId, Generator generator, double zoneStart, double zoneEnd,
|
||||
double pointerPosition, double rotationSpeed) {
|
||||
this.playerId = playerId;
|
||||
this.generator = generator;
|
||||
this.zoneStart = zoneStart;
|
||||
this.zoneEnd = zoneEnd;
|
||||
this.pointerPosition = pointerPosition;
|
||||
this.rotationSpeed = rotationSpeed;
|
||||
this.ticks = 0;
|
||||
}
|
||||
|
||||
void update() {
|
||||
pointerPosition += rotationSpeed * 0.01;
|
||||
if (pointerPosition >= 1.0) {
|
||||
pointerPosition = 0.0;
|
||||
}
|
||||
ticks++;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return ticks > 100;
|
||||
}
|
||||
|
||||
boolean isInSuccessZone() {
|
||||
return pointerPosition >= zoneStart && pointerPosition <= zoneEnd;
|
||||
}
|
||||
|
||||
public String getVisualRepresentation() {
|
||||
int zoneStartPos = (int) (zoneStart * 20);
|
||||
int zoneEndPos = (int) (zoneEnd * 20);
|
||||
int pointerPos = (int) (pointerPosition * 20);
|
||||
|
||||
StringBuilder sb = new StringBuilder("<white>[");
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (i == pointerPos) {
|
||||
sb.append("<red>◆");
|
||||
} else if (i >= zoneStartPos && i <= zoneEndPos) {
|
||||
sb.append("<green>█");
|
||||
} else {
|
||||
sb.append("<gray>░");
|
||||
}
|
||||
}
|
||||
sb.append("<white>]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public double getPointerPosition() {
|
||||
return pointerPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/main/java/dev/loki/objective/SkillCheckUI.java
Normal file
120
src/main/java/dev/loki/objective/SkillCheckUI.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
package dev.loki.objective;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SkillCheckUI {
|
||||
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
|
||||
|
||||
public static void showSkillCheck(Player player, SkillCheck.CheckState state) {
|
||||
String visual = buildVisualDisplay(state);
|
||||
Component title = MINI_MESSAGE.deserialize(visual);
|
||||
Component subtitle = MINI_MESSAGE.deserialize("<gray>Нажмите <yellow>ПКМ</yellow> в зеленой зоне!");
|
||||
|
||||
player.showTitle(Title.title(title, subtitle));
|
||||
}
|
||||
|
||||
public static void showActionBarSkillCheck(Player player, SkillCheck.CheckState state) {
|
||||
String visual = buildCompactVisual(state);
|
||||
player.sendActionBar(MiniMessage.miniMessage().deserialize(visual));
|
||||
}
|
||||
|
||||
public static void showSuccess(Player player) {
|
||||
player.showTitle(Title.title(
|
||||
MINI_MESSAGE.deserialize("<green>✓ Отлично!"),
|
||||
Component.empty(),
|
||||
Title.Times.times(java.time.Duration.ZERO, java.time.Duration.ofMillis(500), java.time.Duration.ZERO)
|
||||
));
|
||||
}
|
||||
|
||||
public static void showFail(Player player) {
|
||||
player.showTitle(Title.title(
|
||||
MINI_MESSAGE.deserialize("<red>✗ Провал!"),
|
||||
MINI_MESSAGE.deserialize("<gray>Потеря прогресса..."),
|
||||
Title.Times.times(java.time.Duration.ZERO, java.time.Duration.ofMillis(800), java.time.Duration.ZERO)
|
||||
));
|
||||
}
|
||||
|
||||
public static void clear(Player player) {
|
||||
player.clearTitle();
|
||||
player.sendActionBar(Component.empty());
|
||||
}
|
||||
|
||||
private static String buildVisualDisplay(SkillCheck.CheckState state) {
|
||||
double zoneStart = state.zoneStart;
|
||||
double zoneEnd = state.zoneEnd;
|
||||
double pointer = state.getPointerPosition();
|
||||
|
||||
int displayWidth = 15;
|
||||
int zoneStartPos = (int) (zoneStart * displayWidth);
|
||||
int zoneEndPos = (int) (zoneEnd * displayWidth);
|
||||
int pointerPos = (int) (pointer * displayWidth);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<gray>");
|
||||
|
||||
for (int i = 0; i < displayWidth; i++) {
|
||||
if (i == pointerPos) {
|
||||
sb.append("<white>◆");
|
||||
} else if (i >= zoneStartPos && i <= zoneEndPos) {
|
||||
sb.append("<green>█");
|
||||
} else {
|
||||
sb.append("<dark_gray>▒");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("<gray>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String buildCompactVisual(SkillCheck.CheckState state) {
|
||||
double zoneStart = state.zoneStart;
|
||||
double zoneEnd = state.zoneEnd;
|
||||
double pointer = state.getPointerPosition();
|
||||
|
||||
int displayWidth = 10;
|
||||
int zoneStartPos = (int) (zoneStart * displayWidth);
|
||||
int zoneEndPos = (int) (zoneEnd * displayWidth);
|
||||
int pointerPos = (int) (pointer * displayWidth);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<yellow>QTE: <white>[");
|
||||
|
||||
for (int i = 0; i < displayWidth; i++) {
|
||||
if (i == pointerPos) {
|
||||
sb.append("<red>♦");
|
||||
} else if (i >= zoneStartPos && i <= zoneEndPos) {
|
||||
sb.append("<green>█");
|
||||
} else {
|
||||
sb.append("<gray>░");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("<white>]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getSkillCheckText(UUID playerId, double pointer, double zoneStart, double zoneEnd) {
|
||||
int width = 12;
|
||||
int pPos = (int) (pointer * width);
|
||||
int zStart = (int) (zoneStart * width);
|
||||
int zEnd = (int) (zoneEnd * width);
|
||||
|
||||
StringBuilder sb = new StringBuilder("<gray>[");
|
||||
for (int i = 0; i < width; i++) {
|
||||
if (i == pPos) {
|
||||
sb.append("<white>◆");
|
||||
} else if (i >= zStart && i <= zEnd) {
|
||||
sb.append("<green>█");
|
||||
} else {
|
||||
sb.append("<dark_gray>░");
|
||||
}
|
||||
}
|
||||
sb.append("<gray>]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
85
src/main/java/dev/loki/pallet/Pallet.java
Normal file
85
src/main/java/dev/loki/pallet/Pallet.java
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package dev.loki.pallet;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
public class Pallet {
|
||||
private final Location location;
|
||||
private PalletState state;
|
||||
private Block topBlock;
|
||||
private Block bottomBlock;
|
||||
|
||||
public enum PalletState {
|
||||
STANDING,
|
||||
DROPPED,
|
||||
BROKEN
|
||||
}
|
||||
|
||||
public Pallet(Location location) {
|
||||
this.location = location;
|
||||
this.state = PalletState.STANDING;
|
||||
this.topBlock = location.clone().add(0, 1, 0).getBlock();
|
||||
this.bottomBlock = location.getBlock();
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public PalletState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public boolean isStanding() {
|
||||
return state == PalletState.STANDING;
|
||||
}
|
||||
|
||||
public boolean isDropped() {
|
||||
return state == PalletState.DROPPED;
|
||||
}
|
||||
|
||||
public boolean isBroken() {
|
||||
return state == PalletState.BROKEN;
|
||||
}
|
||||
|
||||
public void setDropped() {
|
||||
this.state = PalletState.DROPPED;
|
||||
updateVisuals();
|
||||
}
|
||||
|
||||
public void setBroken() {
|
||||
this.state = PalletState.BROKEN;
|
||||
clearBlocks();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.state = PalletState.STANDING;
|
||||
restoreBlocks();
|
||||
}
|
||||
|
||||
private void updateVisuals() {
|
||||
if (state == PalletState.DROPPED) {
|
||||
bottomBlock.setType(Material.OAK_PLANKS);
|
||||
topBlock.setType(Material.OAK_PLANKS);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearBlocks() {
|
||||
bottomBlock.setType(Material.AIR);
|
||||
topBlock.setType(Material.AIR);
|
||||
}
|
||||
|
||||
private void restoreBlocks() {
|
||||
bottomBlock.setType(Material.OAK_FENCE);
|
||||
topBlock.setType(Material.AIR);
|
||||
}
|
||||
|
||||
public Location getCenter() {
|
||||
return location.clone().add(0.5, 0.5, 0.5);
|
||||
}
|
||||
|
||||
public double distanceTo(Location loc) {
|
||||
return getCenter().distance(loc);
|
||||
}
|
||||
}
|
||||
135
src/main/java/dev/loki/pallet/PalletBreak.java
Normal file
135
src/main/java/dev/loki/pallet/PalletBreak.java
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package dev.loki.pallet;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.player.Killer;
|
||||
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 PalletBreak {
|
||||
private final int breakTicks;
|
||||
private final Map<UUID, BreakingProgress> breakingKillers;
|
||||
|
||||
public PalletBreak(GameConfig config) {
|
||||
this.breakTicks = config.getPalletBreakTicks();
|
||||
this.breakingKillers = new HashMap<>();
|
||||
}
|
||||
|
||||
public boolean startBreaking(Killer killer, Pallet pallet, org.bukkit.plugin.Plugin plugin) {
|
||||
if (!canBreak(killer, pallet)) return false;
|
||||
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null) return false;
|
||||
|
||||
BreakingProgress progress = new BreakingProgress(pallet, breakTicks);
|
||||
breakingKillers.put(killer.getUuid(), progress);
|
||||
|
||||
player.sendMessage("§cЛомаем паллет...");
|
||||
playBreakStartEffect(player, pallet);
|
||||
|
||||
new BreakTask(killer, progress, plugin).runTaskTimer(plugin, 0L, 1L);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void stopBreaking(Killer killer) {
|
||||
BreakingProgress progress = breakingKillers.remove(killer.getUuid());
|
||||
if (progress != null) {
|
||||
Player player = killer.getPlayer();
|
||||
if (player != null) {
|
||||
player.sendMessage("§7Прервано!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBreaking(Killer killer) {
|
||||
return breakingKillers.containsKey(killer.getUuid());
|
||||
}
|
||||
|
||||
private boolean canBreak(Killer killer, Pallet pallet) {
|
||||
if (!pallet.isDropped()) return false;
|
||||
if (isBreaking(killer)) return false;
|
||||
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null) return false;
|
||||
|
||||
return pallet.distanceTo(player.getLocation()) <= 3.0;
|
||||
}
|
||||
|
||||
private void playBreakStartEffect(Player player, Pallet pallet) {
|
||||
player.getWorld().playSound(pallet.getLocation(),
|
||||
Sound.BLOCK_WOOD_HIT, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
private void playBreakEffect(Player player, Pallet pallet, int progress) {
|
||||
if (progress % 10 == 0) {
|
||||
player.getWorld().playSound(pallet.getLocation(),
|
||||
Sound.BLOCK_WOOD_HIT, 0.5f, 0.8f + (progress / (float) breakTicks) * 0.4f);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeBreak(Killer killer, Pallet pallet) {
|
||||
Player player = killer.getPlayer();
|
||||
if (player != null) {
|
||||
player.getWorld().playSound(pallet.getLocation(),
|
||||
Sound.BLOCK_WOOD_BREAK, 1.0f, 0.6f);
|
||||
player.sendMessage("§cПаллет сломан!");
|
||||
}
|
||||
pallet.setBroken();
|
||||
breakingKillers.remove(killer.getUuid());
|
||||
}
|
||||
|
||||
private class BreakingProgress {
|
||||
final Pallet pallet;
|
||||
final int totalTicks;
|
||||
int currentTicks;
|
||||
|
||||
BreakingProgress(Pallet pallet, int totalTicks) {
|
||||
this.pallet = pallet;
|
||||
this.totalTicks = totalTicks;
|
||||
this.currentTicks = 0;
|
||||
}
|
||||
|
||||
boolean tick() {
|
||||
currentTicks++;
|
||||
return currentTicks >= totalTicks;
|
||||
}
|
||||
}
|
||||
|
||||
private class BreakTask extends BukkitRunnable {
|
||||
private final Killer killer;
|
||||
private final BreakingProgress progress;
|
||||
|
||||
BreakTask(Killer killer, BreakingProgress progress, org.bukkit.plugin.Plugin plugin) {
|
||||
this.killer = killer;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null || !player.isOnline()) {
|
||||
stopBreaking(killer);
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canBreak(killer, progress.pallet)) {
|
||||
stopBreaking(killer);
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
playBreakEffect(player, progress.pallet, progress.currentTicks);
|
||||
|
||||
if (progress.tick()) {
|
||||
completeBreak(killer, progress.pallet);
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/main/java/dev/loki/pallet/PalletDrop.java
Normal file
72
src/main/java/dev/loki/pallet/PalletDrop.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package dev.loki.pallet;
|
||||
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
public class PalletDrop {
|
||||
private static final double DROP_RADIUS = 2.5;
|
||||
private static final double INTERACTION_RADIUS = 3.0;
|
||||
|
||||
private final PalletManager palletManager;
|
||||
|
||||
public PalletDrop(PalletManager palletManager) {
|
||||
this.palletManager = palletManager;
|
||||
}
|
||||
|
||||
public boolean tryDrop(Survivor survivor) {
|
||||
Player player = survivor.getPlayer();
|
||||
if (player == null) return false;
|
||||
|
||||
Location loc = player.getLocation();
|
||||
Pallet pallet = palletManager.findNearestStandingPallet(loc, INTERACTION_RADIUS);
|
||||
|
||||
if (pallet == null) {
|
||||
player.sendMessage("§cНет паллета поблизости!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canDropFromPosition(player, pallet)) {
|
||||
player.sendMessage("§cНеправильная сторона паллета!");
|
||||
return false;
|
||||
}
|
||||
|
||||
dropPallet(survivor, pallet);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean canDropFromPosition(Player player, Pallet pallet) {
|
||||
Location playerLoc = player.getLocation();
|
||||
Location palletLoc = pallet.getLocation();
|
||||
return playerLoc.distance(palletLoc) <= INTERACTION_RADIUS;
|
||||
}
|
||||
|
||||
private void dropPallet(Survivor survivor, Pallet pallet) {
|
||||
Player player = survivor.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
pallet.setDropped();
|
||||
|
||||
player.getWorld().playSound(pallet.getLocation(), Sound.BLOCK_WOOD_BREAK, 1.0f, 0.8f);
|
||||
player.getWorld().spawnParticle(org.bukkit.Particle.BLOCK_DUST,
|
||||
pallet.getCenter(), 20, 0.5, 0.5, 0.5, 0.1,
|
||||
org.bukkit.Material.OAK_PLANKS.createBlockData());
|
||||
|
||||
player.sendMessage("§aВы бросили паллет!");
|
||||
applyExhaustion(player);
|
||||
}
|
||||
|
||||
private void applyExhaustion(Player player) {
|
||||
player.addPotionEffect(new PotionEffect(
|
||||
PotionEffectType.SLOW, 20, 2, false, false));
|
||||
}
|
||||
|
||||
public boolean isNearDroppedPallet(Player player) {
|
||||
return palletManager.getAllPallets().stream()
|
||||
.filter(Pallet::isDropped)
|
||||
.anyMatch(p -> p.distanceTo(player.getLocation()) <= DROP_RADIUS);
|
||||
}
|
||||
}
|
||||
80
src/main/java/dev/loki/pallet/PalletManager.java
Normal file
80
src/main/java/dev/loki/pallet/PalletManager.java
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package dev.loki.pallet;
|
||||
|
||||
import dev.loki.config.LocationsConfig;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PalletManager {
|
||||
private final List<Pallet> pallets;
|
||||
|
||||
public PalletManager(LocationsConfig locations) {
|
||||
this.pallets = new ArrayList<>();
|
||||
loadPallets(locations);
|
||||
}
|
||||
|
||||
private void loadPallets(LocationsConfig locations) {
|
||||
for (Location loc : locations.getPallets()) {
|
||||
pallets.add(new Pallet(loc));
|
||||
}
|
||||
}
|
||||
|
||||
public void addPallet(Pallet pallet) {
|
||||
pallets.add(pallet);
|
||||
}
|
||||
|
||||
public void removePallet(Pallet pallet) {
|
||||
pallets.remove(pallet);
|
||||
}
|
||||
|
||||
public List<Pallet> getAllPallets() {
|
||||
return new ArrayList<>(pallets);
|
||||
}
|
||||
|
||||
public Pallet getPalletAt(Location location, double radius) {
|
||||
for (Pallet pallet : pallets) {
|
||||
if (pallet.distanceTo(location) <= radius) {
|
||||
return pallet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Pallet findNearestStandingPallet(Location location, double maxRadius) {
|
||||
Pallet nearest = null;
|
||||
double minDistance = maxRadius;
|
||||
|
||||
for (Pallet pallet : pallets) {
|
||||
if (!pallet.isStanding()) continue;
|
||||
double dist = pallet.distanceTo(location);
|
||||
if (dist < minDistance) {
|
||||
minDistance = dist;
|
||||
nearest = pallet;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public void resetAll() {
|
||||
for (Pallet pallet : pallets) {
|
||||
pallet.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBroken() {
|
||||
pallets.removeIf(Pallet::isBroken);
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return pallets.size();
|
||||
}
|
||||
|
||||
public int getDroppedCount() {
|
||||
return (int) pallets.stream().filter(Pallet::isDropped).count();
|
||||
}
|
||||
|
||||
public int getBrokenCount() {
|
||||
return (int) pallets.stream().filter(Pallet::isBroken).count();
|
||||
}
|
||||
}
|
||||
74
src/main/java/dev/loki/pallet/PalletStun.java
Normal file
74
src/main/java/dev/loki/pallet/PalletStun.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package dev.loki.pallet;
|
||||
|
||||
import dev.loki.config.GameConfig;
|
||||
import dev.loki.player.Killer;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PalletStun {
|
||||
private final int stunTicks;
|
||||
private final Set<UUID> stunnedKillers;
|
||||
|
||||
public PalletStun(GameConfig config) {
|
||||
this.stunTicks = config.getPalletStunTicks();
|
||||
this.stunnedKillers = new HashSet<>();
|
||||
}
|
||||
|
||||
public boolean isStunned(Killer killer) {
|
||||
return stunnedKillers.contains(killer.getUuid());
|
||||
}
|
||||
|
||||
public void stun(Killer killer, Pallet pallet, org.bukkit.plugin.Plugin plugin) {
|
||||
if (isStunned(killer)) return;
|
||||
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
stunnedKillers.add(killer.getUuid());
|
||||
|
||||
player.addPotionEffect(new PotionEffect(
|
||||
PotionEffectType.SLOW, stunTicks, 10, false, false));
|
||||
player.addPotionEffect(new PotionEffect(
|
||||
PotionEffectType.BLINDNESS, stunTicks, 1, false, false));
|
||||
|
||||
player.getWorld().playSound(player.getLocation(),
|
||||
Sound.ENTITY_PLAYER_HURT, 1.0f, 0.5f);
|
||||
player.sendTitle("§c§lСТАН!", "§7Паллет сбил вас с ног", 0, stunTicks, 10);
|
||||
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stunnedKillers.remove(killer.getUuid());
|
||||
if (player.isOnline()) {
|
||||
player.sendMessage("§aВы восстановились!");
|
||||
}
|
||||
}
|
||||
}.runTaskLater(plugin, stunTicks);
|
||||
}
|
||||
|
||||
public void stunIfHitByDropped(Killer killer, Pallet pallet, org.bukkit.plugin.Plugin plugin) {
|
||||
if (!pallet.isDropped()) return;
|
||||
|
||||
Player player = killer.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
if (isInStunZone(player, pallet)) {
|
||||
stun(killer, pallet, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInStunZone(Player player, Pallet pallet) {
|
||||
return pallet.distanceTo(player.getLocation()) <= 2.0;
|
||||
}
|
||||
|
||||
public void clearStun(Killer killer) {
|
||||
stunnedKillers.remove(killer.getUuid());
|
||||
}
|
||||
}
|
||||
30
src/main/java/dev/loki/perk/IPerk.java
Normal file
30
src/main/java/dev/loki/perk/IPerk.java
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package dev.loki.perk;
|
||||
|
||||
import dev.loki.player.BasePlayer;
|
||||
|
||||
public interface IPerk {
|
||||
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
||||
String getDescription();
|
||||
|
||||
PerkType getType();
|
||||
|
||||
void onEquip(BasePlayer player);
|
||||
|
||||
void onTrigger(BasePlayer player, PerkTrigger trigger, Object context);
|
||||
|
||||
void onRemove(BasePlayer player);
|
||||
|
||||
boolean isPassive();
|
||||
|
||||
int getCooldownSeconds();
|
||||
|
||||
boolean isOnCooldown();
|
||||
|
||||
void startCooldown();
|
||||
|
||||
void resetCooldown();
|
||||
}
|
||||
23
src/main/java/dev/loki/perk/PerkTrigger.java
Normal file
23
src/main/java/dev/loki/perk/PerkTrigger.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package dev.loki.perk;
|
||||
|
||||
public enum PerkTrigger {
|
||||
ON_HIT,
|
||||
ON_INJURED,
|
||||
ON_DOWNED,
|
||||
ON_HOOKED,
|
||||
ON_UNHOOKED,
|
||||
ON_HEAL_START,
|
||||
ON_HEAL_COMPLETE,
|
||||
ON_GENERATOR_START,
|
||||
ON_GENERATOR_COMPLETE,
|
||||
ON_SPRINT_START,
|
||||
ON_CHASE_START,
|
||||
ON_CHASE_END,
|
||||
ON_TOTEM_BLESSED,
|
||||
ON_TOTEM_CLEANSED,
|
||||
ON_KILLER_HOOK,
|
||||
ON_KILLER_HIT,
|
||||
ON_KILLER_DASH,
|
||||
ON_EXHAUSTION_START,
|
||||
ON_EXHAUSTION_END
|
||||
}
|
||||
16
src/main/java/dev/loki/perk/PerkType.java
Normal file
16
src/main/java/dev/loki/perk/PerkType.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package dev.loki.perk;
|
||||
|
||||
public enum PerkType {
|
||||
SURVIVOR("Выживший"),
|
||||
KILLER("Убийца");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
PerkType(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
107
src/main/java/dev/loki/player/PlayerManager.java
Normal file
107
src/main/java/dev/loki/player/PlayerManager.java
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package dev.loki.player;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PlayerManager {
|
||||
private final Map<UUID, BasePlayer> players;
|
||||
|
||||
public PlayerManager() {
|
||||
this.players = new HashMap<>();
|
||||
}
|
||||
|
||||
public void register(BasePlayer player) {
|
||||
players.put(player.getUuid(), player);
|
||||
}
|
||||
|
||||
public void unregister(UUID uuid) {
|
||||
players.remove(uuid);
|
||||
}
|
||||
|
||||
public BasePlayer getPlayer(UUID uuid) {
|
||||
return players.get(uuid);
|
||||
}
|
||||
|
||||
public BasePlayer getPlayer(Player player) {
|
||||
return players.get(player.getUniqueId());
|
||||
}
|
||||
|
||||
public boolean isRegistered(UUID uuid) {
|
||||
return players.containsKey(uuid);
|
||||
}
|
||||
|
||||
public Collection<BasePlayer> getAllPlayers() {
|
||||
return players.values();
|
||||
}
|
||||
|
||||
public Collection<Survivor> getSurvivors() {
|
||||
return players.values().stream()
|
||||
.filter(p -> !p.isKiller())
|
||||
.map(p -> (Survivor) p)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Collection<Killer> getKillers() {
|
||||
return players.values().stream()
|
||||
.filter(BasePlayer::isKiller)
|
||||
.map(p -> (Killer) p)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Killer getKiller() {
|
||||
return players.values().stream()
|
||||
.filter(BasePlayer::isKiller)
|
||||
.map(p -> (Killer) p)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Collection<Survivor> getLivingSurvivors() {
|
||||
return getSurvivors().stream()
|
||||
.filter(s -> s.getState() != PlayerState.DEAD && s.getState() != PlayerState.ESCAPED)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int getLivingSurvivorCount() {
|
||||
return (int) getSurvivors().stream()
|
||||
.filter(s -> s.getState() != PlayerState.DEAD && s.getState() != PlayerState.ESCAPED)
|
||||
.count();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
players.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return players.size();
|
||||
}
|
||||
|
||||
public Survivor findSurvivorByState(PlayerState state) {
|
||||
return getSurvivors().stream()
|
||||
.filter(s -> s.getState() == state)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Survivor getNearestSurvivor(Player from, double maxDistance) {
|
||||
Survivor nearest = null;
|
||||
double nearestDist = maxDistance;
|
||||
|
||||
for (Survivor survivor : getLivingSurvivors()) {
|
||||
Player sPlayer = survivor.getPlayer();
|
||||
if (sPlayer == null || !sPlayer.isOnline()) continue;
|
||||
|
||||
double dist = from.getLocation().distance(sPlayer.getLocation());
|
||||
if (dist < nearestDist) {
|
||||
nearestDist = dist;
|
||||
nearest = survivor;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,8 @@ public enum PlayerState {
|
|||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public boolean isDeadOrEscaped() {
|
||||
return this == DEAD || this == ESCAPED;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
src/main/java/dev/loki/sound/GeneratorSound.java
Normal file
94
src/main/java/dev/loki/sound/GeneratorSound.java
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package dev.loki.sound;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GeneratorSound extends BukkitRunnable {
|
||||
|
||||
private static final long AMBIENT_INTERVAL = 100L;
|
||||
private static final float AMBIENT_VOLUME = 0.4f;
|
||||
private static final float AMBIENT_PITCH = 0.8f;
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final SoundManager soundManager;
|
||||
private final Map<UUID, Location> activeGenerators;
|
||||
|
||||
public GeneratorSound(JavaPlugin plugin, SoundManager soundManager) {
|
||||
this.plugin = plugin;
|
||||
this.soundManager = soundManager;
|
||||
this.activeGenerators = new HashMap<>();
|
||||
}
|
||||
|
||||
public void addGenerator(UUID id, Location location) {
|
||||
activeGenerators.put(id, location);
|
||||
}
|
||||
|
||||
public void removeGenerator(UUID id) {
|
||||
activeGenerators.remove(id);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
runTaskTimer(plugin, 0L, AMBIENT_INTERVAL);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cancel();
|
||||
activeGenerators.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (Location location : activeGenerators.values()) {
|
||||
playAmbientGeneratorSound(location);
|
||||
}
|
||||
}
|
||||
|
||||
private void playAmbientGeneratorSound(Location location) {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
world.playSound(location, Sound.BLOCK_REDSTONE_TORCH_BURNOUT, AMBIENT_VOLUME, AMBIENT_PITCH);
|
||||
}
|
||||
|
||||
public void playGeneratorProgress(Location location, double progressPercent) {
|
||||
float pitch = 0.8f + (float) (progressPercent / 100.0f * 0.4f);
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_NOTE_BLOCK_PLING, 0.5f, pitch);
|
||||
}
|
||||
|
||||
public void playGeneratorComplete(Location location) {
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_BEACON_POWER_SELECT, 1.0f, 1.0f);
|
||||
playGeneratorStartSound(location);
|
||||
}
|
||||
|
||||
public void playGeneratorStartSound(Location location) {
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_PISTON_EXTEND, 0.8f, 0.5f);
|
||||
}
|
||||
|
||||
public void playGeneratorStopSound(Location location) {
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_PISTON_CONTRACT, 0.6f, 0.5f);
|
||||
}
|
||||
|
||||
public void playGeneratorExplosion(Location location) {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
world.playSound(location, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f);
|
||||
world.playSound(location, Sound.ENTITY_TNT_PRIMED, 0.8f, 0.6f);
|
||||
}
|
||||
|
||||
public void playSkillCheckSuccess(Location location) {
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_NOTE_BLOCK_CHIME, 0.6f, 1.5f);
|
||||
}
|
||||
|
||||
public void playSkillCheckFail(Location location) {
|
||||
soundManager.playWithVolumeAt(location, Sound.BLOCK_NOTE_BLOCK_BASS, 0.8f, 0.5f);
|
||||
soundManager.playWithVolumeAt(location, Sound.ENTITY_ITEM_BREAK, 0.4f, 0.8f);
|
||||
}
|
||||
}
|
||||
34
src/main/java/dev/loki/sound/SoundLevels.java
Normal file
34
src/main/java/dev/loki/sound/SoundLevels.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package dev.loki.sound;
|
||||
|
||||
public enum SoundLevels {
|
||||
LOW(0.3f, 0.8f),
|
||||
MEDIUM(0.6f, 1.0f),
|
||||
HIGH(1.0f, 1.2f);
|
||||
|
||||
private final float volume;
|
||||
private final float pitch;
|
||||
|
||||
SoundLevels(float volume, float pitch) {
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public float getPitch() {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
public static SoundLevels fromDistance(double distance, double maxRange) {
|
||||
double ratio = distance / maxRange;
|
||||
if (ratio < 0.33) {
|
||||
return HIGH;
|
||||
} else if (ratio < 0.66) {
|
||||
return MEDIUM;
|
||||
} else {
|
||||
return LOW;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/main/java/dev/loki/sound/SoundManager.java
Normal file
75
src/main/java/dev/loki/sound/SoundManager.java
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package dev.loki.sound;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class SoundManager {
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
public SoundManager(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void playSound(Player player, Location location, Sound sound, SoundLevels level) {
|
||||
player.playSound(location, sound, level.getVolume(), level.getPitch());
|
||||
}
|
||||
|
||||
public void playSound(Player player, Sound sound, SoundLevels level) {
|
||||
player.playSound(player.getLocation(), sound, level.getVolume(), level.getPitch());
|
||||
}
|
||||
|
||||
public void playSoundAt(Location location, Sound sound, SoundLevels level) {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
world.playSound(location, sound, level.getVolume(), level.getPitch());
|
||||
}
|
||||
|
||||
public void playSoundToAll(Collection<Player> players, Sound sound, SoundLevels level) {
|
||||
for (Player player : players) {
|
||||
if (player.isOnline()) {
|
||||
playSound(player, sound, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void playWithVolume(Player player, Sound sound, float volume, float pitch) {
|
||||
player.playSound(player.getLocation(), sound, volume, pitch);
|
||||
}
|
||||
|
||||
public void playWithVolumeAt(Location location, Sound sound, float volume, float pitch) {
|
||||
World world = location.getWorld();
|
||||
if (world == null) return;
|
||||
|
||||
world.playSound(location, sound, volume, pitch);
|
||||
}
|
||||
|
||||
public void playAmbientSound(Location location, Sound sound, float volume, float pitch, int intervalTicks) {
|
||||
}
|
||||
|
||||
public void stopSound(Player player, Sound sound) {
|
||||
player.stopSound(sound);
|
||||
}
|
||||
|
||||
public void playHeartbeat(Player player, SoundLevels level) {
|
||||
playSound(player, Sound.ENTITY_WARDEN_HEARTBEAT, level);
|
||||
}
|
||||
|
||||
public void playGeneratorStart(Location location) {
|
||||
playWithVolumeAt(location, Sound.BLOCK_PISTON_EXTEND, 0.8f, 0.5f);
|
||||
}
|
||||
|
||||
public void playGeneratorStop(Location location) {
|
||||
playWithVolumeAt(location, Sound.BLOCK_PISTON_CONTRACT, 0.6f, 0.5f);
|
||||
}
|
||||
|
||||
public void playGeneratorExplosion(Location location) {
|
||||
playWithVolumeAt(location, Sound.ENTITY_GENERIC_EXPLODE, 1.0f, 0.8f);
|
||||
}
|
||||
}
|
||||
92
src/main/java/dev/loki/sound/TerrorRadiusSound.java
Normal file
92
src/main/java/dev/loki/sound/TerrorRadiusSound.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package dev.loki.sound;
|
||||
|
||||
import dev.loki.player.Killer;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class TerrorRadiusSound extends BukkitRunnable {
|
||||
|
||||
private static final double TERROR_RADIUS = 32.0;
|
||||
private static final long TICK_INTERVAL = 20L;
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final SoundManager soundManager;
|
||||
private Killer killer;
|
||||
private Collection<Survivor> survivors;
|
||||
|
||||
public TerrorRadiusSound(JavaPlugin plugin, SoundManager soundManager) {
|
||||
this.plugin = plugin;
|
||||
this.soundManager = soundManager;
|
||||
}
|
||||
|
||||
public void setTargets(Killer killer, Collection<Survivor> survivors) {
|
||||
this.killer = killer;
|
||||
this.survivors = survivors;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
runTaskTimer(plugin, 0L, TICK_INTERVAL);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (killer == null || !killer.isOnline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player killerPlayer = killer.getPlayer();
|
||||
if (killerPlayer == null) return;
|
||||
|
||||
Location killerLoc = killerPlayer.getLocation();
|
||||
|
||||
for (Survivor survivor : survivors) {
|
||||
if (!survivor.isOnline()) continue;
|
||||
if (survivor.getState().isDeadOrEscaped()) continue;
|
||||
|
||||
Player survPlayer = survivor.getPlayer();
|
||||
if (survPlayer == null) continue;
|
||||
|
||||
Location survLoc = survPlayer.getLocation();
|
||||
double distance = killerLoc.distance(survLoc);
|
||||
|
||||
if (distance <= TERROR_RADIUS) {
|
||||
SoundLevels level = SoundLevels.fromDistance(distance, TERROR_RADIUS);
|
||||
playTerrorSound(survPlayer, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void playTerrorSound(Player player, SoundLevels level) {
|
||||
soundManager.playHeartbeat(player, level);
|
||||
}
|
||||
|
||||
public double getTerrorRadius() {
|
||||
return TERROR_RADIUS;
|
||||
}
|
||||
|
||||
public SoundLevels getTerrorLevel(double distance) {
|
||||
if (distance > TERROR_RADIUS) {
|
||||
return null;
|
||||
}
|
||||
return SoundLevels.fromDistance(distance, TERROR_RADIUS);
|
||||
}
|
||||
|
||||
public void playImmediateTerror(Survivor survivor, SoundLevels level) {
|
||||
if (!survivor.isOnline()) return;
|
||||
|
||||
Player player = survivor.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
playTerrorSound(player, level);
|
||||
}
|
||||
}
|
||||
89
src/main/java/dev/loki/state/StateHandler.java
Normal file
89
src/main/java/dev/loki/state/StateHandler.java
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package dev.loki.state;
|
||||
|
||||
import dev.loki.player.BasePlayer;
|
||||
import dev.loki.player.PlayerState;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StateHandler {
|
||||
private BiConsumer<BasePlayer, PlayerState> onStateChange;
|
||||
private Consumer<BasePlayer> onPlayerDowned;
|
||||
private Consumer<BasePlayer> onPlayerHooked;
|
||||
private Consumer<BasePlayer> onPlayerKilled;
|
||||
private Consumer<BasePlayer> onPlayerEscaped;
|
||||
|
||||
public void setOnStateChange(BiConsumer<BasePlayer, PlayerState> callback) {
|
||||
this.onStateChange = callback;
|
||||
}
|
||||
|
||||
public void setOnPlayerDowned(Consumer<BasePlayer> callback) {
|
||||
this.onPlayerDowned = callback;
|
||||
}
|
||||
|
||||
public void setOnPlayerHooked(Consumer<BasePlayer> callback) {
|
||||
this.onPlayerHooked = callback;
|
||||
}
|
||||
|
||||
public void setOnPlayerKilled(Consumer<BasePlayer> callback) {
|
||||
this.onPlayerKilled = callback;
|
||||
}
|
||||
|
||||
public void setOnPlayerEscaped(Consumer<BasePlayer> callback) {
|
||||
this.onPlayerEscaped = callback;
|
||||
}
|
||||
|
||||
public boolean handleStateChange(BasePlayer player, PlayerState newState) {
|
||||
PlayerState oldState = player.getState();
|
||||
|
||||
if (!StateTransition.isAllowed(oldState, newState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
player.setState(newState);
|
||||
applyStateEffects(player, newState);
|
||||
|
||||
if (onStateChange != null) {
|
||||
onStateChange.accept(player, newState);
|
||||
}
|
||||
|
||||
triggerStateEvent(player, newState);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyStateEffects(BasePlayer player, PlayerState state) {
|
||||
Player bukkitPlayer = player.getPlayer();
|
||||
if (bukkitPlayer == null || !bukkitPlayer.isOnline()) return;
|
||||
|
||||
switch (state) {
|
||||
case HEALTHY -> bukkitPlayer.setHealth(20);
|
||||
case INJURED -> bukkitPlayer.setHealth(10);
|
||||
case DOWNED, HOOKED -> bukkitPlayer.setHealth(2);
|
||||
case CARRIED -> bukkitPlayer.setHealth(2);
|
||||
case DEAD -> bukkitPlayer.setHealth(0);
|
||||
case ESCAPED -> {
|
||||
bukkitPlayer.setHealth(20);
|
||||
bukkitPlayer.setInvisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerStateEvent(BasePlayer player, PlayerState newState) {
|
||||
switch (newState) {
|
||||
case DOWNED -> {
|
||||
if (onPlayerDowned != null) onPlayerDowned.accept(player);
|
||||
}
|
||||
case HOOKED -> {
|
||||
if (onPlayerHooked != null) onPlayerHooked.accept(player);
|
||||
}
|
||||
case DEAD -> {
|
||||
if (onPlayerKilled != null) onPlayerKilled.accept(player);
|
||||
}
|
||||
case ESCAPED -> {
|
||||
if (onPlayerEscaped != null) onPlayerEscaped.accept(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/main/java/dev/loki/state/StateMachine.java
Normal file
90
src/main/java/dev/loki/state/StateMachine.java
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package dev.loki.state;
|
||||
|
||||
import dev.loki.player.BasePlayer;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class StateMachine {
|
||||
private final StateHandler handler;
|
||||
private final Map<UUID, PlayerState> pendingTransitions;
|
||||
|
||||
public StateMachine() {
|
||||
this.handler = new StateHandler();
|
||||
this.pendingTransitions = new HashMap<>();
|
||||
setupCallbacks();
|
||||
}
|
||||
|
||||
private void setupCallbacks() {
|
||||
handler.setOnStateChange((player, state) -> {
|
||||
pendingTransitions.remove(player.getUuid());
|
||||
});
|
||||
}
|
||||
|
||||
public boolean transitionTo(BasePlayer player, PlayerState newState) {
|
||||
return handler.handleStateChange(player, newState);
|
||||
}
|
||||
|
||||
public boolean damageSurvivor(Survivor survivor) {
|
||||
PlayerState current = survivor.getState();
|
||||
|
||||
if (!StateTransition.canTakeDamage(current)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerState nextState = switch (current) {
|
||||
case HEALTHY -> PlayerState.INJURED;
|
||||
case INJURED -> PlayerState.DOWNED;
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (nextState == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return transitionTo(survivor, nextState);
|
||||
}
|
||||
|
||||
public boolean pickUp(Survivor survivor) {
|
||||
return transitionTo(survivor, PlayerState.CARRIED);
|
||||
}
|
||||
|
||||
public boolean drop(Survivor survivor) {
|
||||
return transitionTo(survivor, PlayerState.DOWNED);
|
||||
}
|
||||
|
||||
public boolean hook(Survivor survivor) {
|
||||
if (survivor.getState() == PlayerState.CARRIED || survivor.getState() == PlayerState.DOWNED) {
|
||||
survivor.incrementHooks();
|
||||
return transitionTo(survivor, PlayerState.HOOKED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean escape(Survivor survivor) {
|
||||
return transitionTo(survivor, PlayerState.ESCAPED);
|
||||
}
|
||||
|
||||
public boolean sacrifice(Survivor survivor) {
|
||||
return transitionTo(survivor, PlayerState.DEAD);
|
||||
}
|
||||
|
||||
public boolean isDead(Survivor survivor) {
|
||||
return survivor.getState() == PlayerState.DEAD || survivor.getState() == PlayerState.ESCAPED;
|
||||
}
|
||||
|
||||
public boolean isVulnerable(Survivor survivor) {
|
||||
return survivor.getState() == PlayerState.DOWNED || survivor.getState() == PlayerState.INJURED;
|
||||
}
|
||||
|
||||
public StateHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
pendingTransitions.clear();
|
||||
}
|
||||
}
|
||||
50
src/main/java/dev/loki/state/StateTransition.java
Normal file
50
src/main/java/dev/loki/state/StateTransition.java
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package dev.loki.state;
|
||||
|
||||
import dev.loki.player.PlayerState;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class StateTransition {
|
||||
private static final Map<PlayerState, Set<PlayerState>> allowedTransitions = new HashMap<>();
|
||||
|
||||
static {
|
||||
allowedTransitions.put(PlayerState.HEALTHY, EnumSet.of(
|
||||
PlayerState.INJURED, PlayerState.DOWNED, PlayerState.CARRIED, PlayerState.ESCAPED
|
||||
));
|
||||
allowedTransitions.put(PlayerState.INJURED, EnumSet.of(
|
||||
PlayerState.HEALTHY, PlayerState.DOWNED, PlayerState.CARRIED, PlayerState.ESCAPED
|
||||
));
|
||||
allowedTransitions.put(PlayerState.DOWNED, EnumSet.of(
|
||||
PlayerState.CARRIED, PlayerState.HEALTHY, PlayerState.HOOKED, PlayerState.DEAD, PlayerState.ESCAPED
|
||||
));
|
||||
allowedTransitions.put(PlayerState.CARRIED, EnumSet.of(
|
||||
PlayerState.DOWNED, PlayerState.HOOKED
|
||||
));
|
||||
allowedTransitions.put(PlayerState.HOOKED, EnumSet.of(
|
||||
PlayerState.DOWNED, PlayerState.DEAD, PlayerState.CARRIED
|
||||
));
|
||||
allowedTransitions.put(PlayerState.DEAD, EnumSet.noneOf(PlayerState.class));
|
||||
allowedTransitions.put(PlayerState.ESCAPED, EnumSet.noneOf(PlayerState.class));
|
||||
}
|
||||
|
||||
public static boolean isAllowed(PlayerState from, PlayerState to) {
|
||||
Set<PlayerState> allowed = allowedTransitions.get(from);
|
||||
return allowed != null && allowed.contains(to);
|
||||
}
|
||||
|
||||
public static boolean isTerminal(PlayerState state) {
|
||||
return state == PlayerState.DEAD || state == PlayerState.ESCAPED;
|
||||
}
|
||||
|
||||
public static boolean isDamageTransition(PlayerState from, PlayerState to) {
|
||||
return from == PlayerState.HEALTHY && to == PlayerState.INJURED
|
||||
|| from == PlayerState.INJURED && to == PlayerState.DOWNED;
|
||||
}
|
||||
|
||||
public static boolean canTakeDamage(PlayerState state) {
|
||||
return state == PlayerState.HEALTHY || state == PlayerState.INJURED;
|
||||
}
|
||||
}
|
||||
77
src/main/java/dev/loki/survivor/SurvivorController.java
Normal file
77
src/main/java/dev/loki/survivor/SurvivorController.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package dev.loki.survivor;
|
||||
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SurvivorController {
|
||||
private final Survivor survivor;
|
||||
private final SurvivorMovement movement;
|
||||
private final SurvivorHealing healing;
|
||||
|
||||
public SurvivorController(Survivor survivor) {
|
||||
this.survivor = survivor;
|
||||
this.movement = new SurvivorMovement(this);
|
||||
this.healing = new SurvivorHealing(this);
|
||||
}
|
||||
|
||||
public void onSprintStart() {
|
||||
movement.onSprintStart();
|
||||
}
|
||||
|
||||
public void onSprintEnd() {
|
||||
movement.onSprintEnd();
|
||||
}
|
||||
|
||||
public void onHealAttempt(Player target) {
|
||||
healing.startHealing(target);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
movement.tick();
|
||||
healing.tick();
|
||||
updateSpeed();
|
||||
}
|
||||
|
||||
private void updateSpeed() {
|
||||
Player player = survivor.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
float speed = movement.getCurrentSpeed();
|
||||
player.setWalkSpeed(speed);
|
||||
}
|
||||
|
||||
public void onStateChange(PlayerState newState) {
|
||||
movement.onStateChange(newState);
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return survivor.getUuid();
|
||||
}
|
||||
|
||||
public Survivor getSurvivor() {
|
||||
return survivor;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return survivor.getPlayer();
|
||||
}
|
||||
|
||||
public PlayerState getState() {
|
||||
return survivor.getState();
|
||||
}
|
||||
|
||||
public boolean isDowned() {
|
||||
return survivor.getState() == PlayerState.DOWNED;
|
||||
}
|
||||
|
||||
public boolean isInjured() {
|
||||
return survivor.getState() == PlayerState.INJURED;
|
||||
}
|
||||
|
||||
public boolean isHealthy() {
|
||||
return survivor.getState() == PlayerState.HEALTHY;
|
||||
}
|
||||
}
|
||||
156
src/main/java/dev/loki/survivor/SurvivorHealing.java
Normal file
156
src/main/java/dev/loki/survivor/SurvivorHealing.java
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package dev.loki.survivor;
|
||||
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SurvivorHealing {
|
||||
private final SurvivorController controller;
|
||||
private Player healingTarget;
|
||||
private int healingTicks;
|
||||
private int totalHealingTicks;
|
||||
private boolean isSelfHeal;
|
||||
|
||||
private static final int SELF_HEAL_TICKS = 600;
|
||||
private static final int OTHER_HEAL_TICKS = 320;
|
||||
private static final double HEAL_RANGE = 3.0;
|
||||
|
||||
public SurvivorHealing(SurvivorController controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public void startHealing(Player target) {
|
||||
if (isHealing()) return;
|
||||
if (controller.isDowned()) return;
|
||||
|
||||
Player healer = controller.getPlayer();
|
||||
if (healer == null) return;
|
||||
|
||||
UUID targetUuid = target.getUniqueId();
|
||||
Survivor targetSurvivor = getSurvivorFromPlayer(target);
|
||||
if (targetSurvivor == null) return;
|
||||
|
||||
PlayerState targetState = targetSurvivor.getState();
|
||||
if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) return;
|
||||
|
||||
boolean selfHeal = healer.getUniqueId().equals(targetUuid);
|
||||
if (selfHeal && targetState == PlayerState.DOWNED) return;
|
||||
|
||||
this.healingTarget = target;
|
||||
this.isSelfHeal = selfHeal;
|
||||
this.totalHealingTicks = selfHeal ? SELF_HEAL_TICKS : OTHER_HEAL_TICKS;
|
||||
this.healingTicks = totalHealingTicks;
|
||||
|
||||
sendProgressMessage();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (!isHealing()) return;
|
||||
|
||||
Player healer = controller.getPlayer();
|
||||
if (healer == null || healingTarget == null) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!healingTarget.isOnline()) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
||||
Survivor targetSurvivor = getSurvivorFromPlayer(healingTarget);
|
||||
if (targetSurvivor == null) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerState targetState = targetSurvivor.getState();
|
||||
if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) {
|
||||
completeHealing(targetSurvivor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > HEAL_RANGE) {
|
||||
cancelHealing();
|
||||
return;
|
||||
}
|
||||
|
||||
healingTicks--;
|
||||
sendProgressMessage();
|
||||
|
||||
if (healingTicks <= 0) {
|
||||
completeHealing(targetSurvivor);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeHealing(Survivor target) {
|
||||
PlayerState currentState = target.getState();
|
||||
PlayerState newState = (currentState == PlayerState.DOWNED) ? PlayerState.INJURED : PlayerState.HEALTHY;
|
||||
target.setState(newState);
|
||||
|
||||
Player targetPlayer = healingTarget;
|
||||
if (targetPlayer != null && targetPlayer.isOnline()) {
|
||||
String message = newState == PlayerState.HEALTHY ? "<green>Вы полностью вылечились!"
|
||||
: "<yellow>Вы встали на ноги!";
|
||||
targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(message));
|
||||
}
|
||||
|
||||
cancelHealing();
|
||||
}
|
||||
|
||||
public void cancelHealing() {
|
||||
if (healingTarget != null && healingTarget.isOnline() && isHealing()) {
|
||||
healingTarget.sendActionBar(Component.empty());
|
||||
}
|
||||
healingTarget = null;
|
||||
healingTicks = 0;
|
||||
}
|
||||
|
||||
private void sendProgressMessage() {
|
||||
if (healingTarget == null) return;
|
||||
|
||||
double progress = 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||
String bar = renderProgressBar(progress);
|
||||
Component message = MiniMessage.miniMessage().deserialize(
|
||||
isSelfHeal ? "<gray>Лечение себя: " + bar
|
||||
: "<gray>Лечение союзника: " + bar
|
||||
);
|
||||
healingTarget.sendActionBar(message);
|
||||
}
|
||||
|
||||
private String renderProgressBar(double progress) {
|
||||
int filled = (int) (10 * progress);
|
||||
int empty = 10 - filled;
|
||||
StringBuilder bar = new StringBuilder("<green>");
|
||||
bar.append("█".repeat(filled));
|
||||
bar.append("<gray>");
|
||||
bar.append("░".repeat(empty));
|
||||
bar.append(" <white>").append((int)(progress * 100)).append("%");
|
||||
return bar.toString();
|
||||
}
|
||||
|
||||
private Survivor getSurvivorFromPlayer(Player player) {
|
||||
if (controller.getPlayer() != null && controller.getPlayer().equals(player)) {
|
||||
return controller.getSurvivor();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isHealing() {
|
||||
return healingTicks > 0;
|
||||
}
|
||||
|
||||
public Player getHealingTarget() {
|
||||
return healingTarget;
|
||||
}
|
||||
|
||||
public double getProgress() {
|
||||
if (!isHealing()) return 0;
|
||||
return 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||
}
|
||||
}
|
||||
121
src/main/java/dev/loki/survivor/SurvivorMovement.java
Normal file
121
src/main/java/dev/loki/survivor/SurvivorMovement.java
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package dev.loki.survivor;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import dev.loki.player.PlayerState;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
public class SurvivorMovement {
|
||||
private final SurvivorController controller;
|
||||
private boolean isSprinting;
|
||||
private boolean isExhausted;
|
||||
private int exhaustionTicks;
|
||||
private int exhaustionCooldownTicks;
|
||||
private boolean isCrawling;
|
||||
|
||||
private static final float WALK_SPEED = 0.2f;
|
||||
private static final float SPRINT_SPEED = 0.3f;
|
||||
private static final float CRAWL_SPEED = 0.06f;
|
||||
private static final int EXHAUSTION_DURATION = 60;
|
||||
private static final int EXHAUSTION_COOLDOWN = 100;
|
||||
|
||||
public SurvivorMovement(SurvivorController controller) {
|
||||
this.controller = controller;
|
||||
this.isSprinting = false;
|
||||
this.isExhausted = false;
|
||||
this.isCrawling = false;
|
||||
}
|
||||
|
||||
public void onSprintStart() {
|
||||
if (isExhausted || controller.isDowned()) return;
|
||||
isSprinting = true;
|
||||
}
|
||||
|
||||
public void onSprintEnd() {
|
||||
if (!isSprinting) return;
|
||||
isSprinting = false;
|
||||
if (!isExhausted && !controller.isDowned()) {
|
||||
isExhausted = true;
|
||||
exhaustionTicks = EXHAUSTION_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (isExhausted) {
|
||||
if (exhaustionTicks > 0) {
|
||||
exhaustionTicks--;
|
||||
} else {
|
||||
exhaustionCooldownTicks = EXHAUSTION_COOLDOWN;
|
||||
isExhausted = false;
|
||||
}
|
||||
} else if (exhaustionCooldownTicks > 0) {
|
||||
exhaustionCooldownTicks--;
|
||||
}
|
||||
|
||||
updateCrawlState();
|
||||
applyEffects();
|
||||
}
|
||||
|
||||
private void updateCrawlState() {
|
||||
Player player = controller.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
boolean shouldCrawl = controller.isDowned();
|
||||
if (shouldCrawl != isCrawling) {
|
||||
isCrawling = shouldCrawl;
|
||||
if (isCrawling) {
|
||||
player.setSneaking(true);
|
||||
} else {
|
||||
player.setSneaking(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyEffects() {
|
||||
Player player = controller.getPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
if (isCrawling) {
|
||||
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, 5, 2, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
public float getCurrentSpeed() {
|
||||
if (controller.isDowned()) {
|
||||
return CRAWL_SPEED;
|
||||
}
|
||||
if (isExhausted) {
|
||||
return WALK_SPEED;
|
||||
}
|
||||
if (isSprinting) {
|
||||
return SPRINT_SPEED;
|
||||
}
|
||||
return WALK_SPEED;
|
||||
}
|
||||
|
||||
public void onStateChange(PlayerState newState) {
|
||||
if (newState == PlayerState.DOWNED) {
|
||||
isSprinting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSprinting() {
|
||||
return isSprinting;
|
||||
}
|
||||
|
||||
public boolean isExhausted() {
|
||||
return isExhausted;
|
||||
}
|
||||
|
||||
public boolean canSprint() {
|
||||
return !isExhausted && exhaustionCooldownTicks <= 0 && !controller.isDowned();
|
||||
}
|
||||
|
||||
public double getExhaustionPercent() {
|
||||
if (isExhausted) {
|
||||
return (double) exhaustionTicks / EXHAUSTION_DURATION;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
133
src/main/java/dev/loki/ui/ActionBar.java
Normal file
133
src/main/java/dev/loki/ui/ActionBar.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package dev.loki.ui;
|
||||
|
||||
import dev.loki.hook.Hook;
|
||||
import dev.loki.hook.HookStage;
|
||||
import dev.loki.player.PlayerState;
|
||||
import dev.loki.player.Survivor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ActionBar {
|
||||
|
||||
public void sendMessage(Player player, String message) {
|
||||
player.sendActionBar(Component.text(message));
|
||||
}
|
||||
|
||||
public void sendComponent(Player player, Component component) {
|
||||
player.sendActionBar(component);
|
||||
}
|
||||
|
||||
public void showProgress(Player player, String label, double percent) {
|
||||
Component bar = ProgressBar.createWithLabel(label, percent);
|
||||
player.sendActionBar(bar);
|
||||
}
|
||||
|
||||
public void showRepairProgress(Player player, double percent, boolean skillCheckActive) {
|
||||
Component bar = ProgressBar.create(percent);
|
||||
TextComponent message = Component.text("Repairing ")
|
||||
.color(NamedTextColor.WHITE)
|
||||
.append(bar)
|
||||
.append(Component.text(" " + (int) percent + "%").color(NamedTextColor.GRAY));
|
||||
|
||||
if (skillCheckActive) {
|
||||
message = message.append(Component.text(" ⚠ SKILL CHECK!").color(NamedTextColor.YELLOW));
|
||||
}
|
||||
|
||||
player.sendActionBar(message);
|
||||
}
|
||||
|
||||
public void showHealProgress(Player player, double percent, boolean selfHeal) {
|
||||
String label = selfHeal ? "Healing self" : "Healing ally";
|
||||
Component bar = ProgressBar.create(percent);
|
||||
Component message = Component.text(label + " ")
|
||||
.color(NamedTextColor.WHITE)
|
||||
.append(bar)
|
||||
.append(Component.text(" " + (int) percent + "%").color(NamedTextColor.GRAY));
|
||||
player.sendActionBar(message);
|
||||
}
|
||||
|
||||
public void showRescueProgress(Player player, double percent) {
|
||||
Component bar = ProgressBar.create(percent);
|
||||
Component message = Component.text("Rescuing ")
|
||||
.color(NamedTextColor.WHITE)
|
||||
.append(bar)
|
||||
.append(Component.text(" " + (int) percent + "%").color(NamedTextColor.GRAY));
|
||||
player.sendActionBar(message);
|
||||
}
|
||||
|
||||
public void showHookStatus(Survivor survivor, Hook hook) {
|
||||
Player player = survivor.getPlayer();
|
||||
if (player == null || !player.isOnline()) return;
|
||||
|
||||
HookStage stage = hook.getCurrentStage();
|
||||
double progress = hook.getStageProgressPercent();
|
||||
|
||||
Component stageIcon;
|
||||
NamedTextColor stageColor;
|
||||
String stageName;
|
||||
|
||||
if (stage == HookStage.STAGE_1) {
|
||||
stageIcon = StatusIcons.HOOK;
|
||||
stageColor = NamedTextColor.YELLOW;
|
||||
stageName = "Stage 1";
|
||||
} else if (stage == HookStage.STAGE_2) {
|
||||
stageIcon = StatusIcons.HOOK;
|
||||
stageColor = NamedTextColor.RED;
|
||||
stageName = "Stage 2 - STRUGGLE!";
|
||||
} else {
|
||||
stageIcon = StatusIcons.DEATH_SKULL;
|
||||
stageColor = NamedTextColor.DARK_RED;
|
||||
stageName = "SACRIFICE IMMINENT";
|
||||
}
|
||||
|
||||
Component bar = stage == HookStage.STAGE_2
|
||||
? ProgressBar.createReversed(progress, 10)
|
||||
: ProgressBar.create(progress, 10);
|
||||
|
||||
Component message = stageIcon
|
||||
.append(Component.text(" " + stageName + " ").color(stageColor))
|
||||
.append(bar);
|
||||
|
||||
if (stage == HookStage.STAGE_2) {
|
||||
message = message.append(Component.text(" CROUCH to struggle!").color(NamedTextColor.YELLOW));
|
||||
}
|
||||
|
||||
player.sendActionBar(message);
|
||||
}
|
||||
|
||||
public void showPlayerStatus(Player player, PlayerState state, int hookCount) {
|
||||
Component status = formatStatus(state);
|
||||
Component hooks = StatusIcons.statusIcon(hookCount);
|
||||
|
||||
Component message = hooks.append(Component.text(" ")).append(status);
|
||||
player.sendActionBar(message);
|
||||
}
|
||||
|
||||
private Component formatStatus(PlayerState state) {
|
||||
return switch (state) {
|
||||
case HEALTHY -> StatusIcons.HEALTHY_HEART
|
||||
.append(Component.text(" Healthy").color(NamedTextColor.GREEN));
|
||||
case INJURED -> StatusIcons.INJURED_HEART
|
||||
.append(Component.text(" Injured").color(NamedTextColor.YELLOW));
|
||||
case DOWNED -> Component.text("🆘 Downed - need help!").color(NamedTextColor.RED);
|
||||
case HOOKED -> StatusIcons.HOOK
|
||||
.append(Component.text(" Hooked!").color(NamedTextColor.DARK_RED));
|
||||
case CARRIED -> Component.text("🎒 Being carried").color(NamedTextColor.RED);
|
||||
default -> Component.text("");
|
||||
};
|
||||
}
|
||||
|
||||
public void showCarrying(Player killer, String survivorName) {
|
||||
Component message = Component.text("Carrying: ")
|
||||
.color(NamedTextColor.WHITE)
|
||||
.append(Component.text(survivorName).color(NamedTextColor.YELLOW))
|
||||
.append(Component.text(" - Find a hook!").color(NamedTextColor.GRAY));
|
||||
killer.sendActionBar(message);
|
||||
}
|
||||
|
||||
public void clear(Player player) {
|
||||
player.sendActionBar(Component.empty());
|
||||
}
|
||||
}
|
||||
76
src/main/java/dev/loki/ui/ProgressBar.java
Normal file
76
src/main/java/dev/loki/ui/ProgressBar.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package dev.loki.ui;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
public final class ProgressBar {
|
||||
|
||||
private static final char FILLED_BLOCK = '█';
|
||||
private static final char EMPTY_BLOCK = '░';
|
||||
private static final int DEFAULT_LENGTH = 10;
|
||||
|
||||
private ProgressBar() {}
|
||||
|
||||
public static Component create(double percent) {
|
||||
return create(percent, DEFAULT_LENGTH);
|
||||
}
|
||||
|
||||
public static Component create(double percent, int length) {
|
||||
int filled = (int) Math.round(percent / 100.0 * length);
|
||||
filled = Math.clamp(filled, 0, length);
|
||||
int empty = length - filled;
|
||||
|
||||
StringBuilder bar = new StringBuilder();
|
||||
bar.append("[");
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append(FILLED_BLOCK);
|
||||
}
|
||||
for (int i = 0; i < empty; i++) {
|
||||
bar.append(EMPTY_BLOCK);
|
||||
}
|
||||
bar.append("]");
|
||||
|
||||
NamedTextColor color;
|
||||
if (percent < 25) {
|
||||
color = NamedTextColor.RED;
|
||||
} else if (percent < 50) {
|
||||
color = NamedTextColor.GOLD;
|
||||
} else if (percent < 75) {
|
||||
color = NamedTextColor.YELLOW;
|
||||
} else {
|
||||
color = NamedTextColor.GREEN;
|
||||
}
|
||||
|
||||
return Component.text(bar.toString()).color(color);
|
||||
}
|
||||
|
||||
public static Component createWithLabel(String label, double percent) {
|
||||
return createWithLabel(label, percent, DEFAULT_LENGTH);
|
||||
}
|
||||
|
||||
public static Component createWithLabel(String label, double percent, int length) {
|
||||
TextComponent labelComp = Component.text(label + " ").color(NamedTextColor.WHITE);
|
||||
Component bar = create(percent, length);
|
||||
TextComponent percentComp = Component.text(" " + (int) percent + "%").color(NamedTextColor.GRAY);
|
||||
return labelComp.append(bar).append(percentComp);
|
||||
}
|
||||
|
||||
public static Component createReversed(double percent, int length) {
|
||||
int empty = (int) Math.round(percent / 100.0 * length);
|
||||
empty = Math.clamp(empty, 0, length);
|
||||
int filled = length - empty;
|
||||
|
||||
StringBuilder bar = new StringBuilder();
|
||||
bar.append("[");
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append(FILLED_BLOCK);
|
||||
}
|
||||
for (int i = 0; i < empty; i++) {
|
||||
bar.append(EMPTY_BLOCK);
|
||||
}
|
||||
bar.append("]");
|
||||
|
||||
return Component.text(bar.toString()).color(NamedTextColor.RED);
|
||||
}
|
||||
}
|
||||
130
src/main/java/dev/loki/ui/Sidebar.java
Normal file
130
src/main/java/dev/loki/ui/Sidebar.java
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package dev.loki.ui;
|
||||
|
||||
import dev.loki.core.GameSession;
|
||||
import dev.loki.player.BasePlayer;
|
||||
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.scoreboard.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Sidebar {
|
||||
private static final String OBJECTIVE_NAME = "dbd_stats";
|
||||
private final Map<UUID, Scoreboard> scoreboards;
|
||||
|
||||
public Sidebar() {
|
||||
this.scoreboards = new HashMap<>();
|
||||
}
|
||||
|
||||
public void setupPlayer(Player player) {
|
||||
ScoreboardManager manager = Bukkit.getScoreboardManager();
|
||||
if (manager == null) return;
|
||||
|
||||
Scoreboard board = manager.getNewScoreboard();
|
||||
Objective objective = board.registerNewObjective(
|
||||
OBJECTIVE_NAME,
|
||||
Criteria.DUMMY,
|
||||
Component.text("☠ DEAD BY DAYLIGHT").color(NamedTextColor.DARK_RED)
|
||||
);
|
||||
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
|
||||
player.setScoreboard(board);
|
||||
scoreboards.put(player.getUniqueId(), board);
|
||||
}
|
||||
|
||||
public void updateGameStats(Player player, GameSession session) {
|
||||
Scoreboard board = scoreboards.get(player.getUniqueId());
|
||||
if (board == null) return;
|
||||
|
||||
Objective objective = board.getObjective(OBJECTIVE_NAME);
|
||||
if (objective == null) return;
|
||||
|
||||
clearScores(board);
|
||||
|
||||
int line = 15;
|
||||
setScore(objective, " ", line--);
|
||||
|
||||
int completedGens = getCompletedGenerators(session);
|
||||
int requiredGens = session.getConfig().getGeneratorRequired();
|
||||
int totalGens = session.getConfig().getGeneratorTotal();
|
||||
|
||||
setScore(objective, "§eGenerators:", line--);
|
||||
setScore(objective, " §f" + completedGens + "/" + requiredGens + " completed", line--);
|
||||
setScore(objective, " §7(" + totalGens + " total)", line--);
|
||||
setScore(objective, " ", line--);
|
||||
|
||||
long survivorsAlive = countSurvivorsInState(session, PlayerState.HEALTHY, PlayerState.INJURED, PlayerState.DOWNED);
|
||||
long survivorsHooked = countSurvivorsInState(session, PlayerState.HOOKED);
|
||||
long survivorsDead = countSurvivorsInState(session, PlayerState.DEAD);
|
||||
long survivorsEscaped = countSurvivorsInState(session, PlayerState.ESCAPED);
|
||||
|
||||
setScore(objective, "§aSurvivors:", line--);
|
||||
setScore(objective, " §f" + survivorsAlive + " alive", line--);
|
||||
if (survivorsHooked > 0) {
|
||||
setScore(objective, " §c" + survivorsHooked + " hooked", line--);
|
||||
}
|
||||
if (survivorsDead > 0) {
|
||||
setScore(objective, " §7" + survivorsDead + " dead", line--);
|
||||
}
|
||||
if (survivorsEscaped > 0) {
|
||||
setScore(objective, " §2" + survivorsEscaped + " escaped", line--);
|
||||
}
|
||||
setScore(objective, " ", line--);
|
||||
|
||||
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
||||
if (bp instanceof Survivor surv && !bp.isKiller()) {
|
||||
int hooks = surv.getHooksTaken();
|
||||
String hookBar = "§f[" + "§c⚓".repeat(hooks) + "§7○".repeat(Math.max(0, 3 - hooks)) + "§f]";
|
||||
setScore(objective, "Your hooks: " + hookBar, line--);
|
||||
}
|
||||
}
|
||||
|
||||
private void setScore(Objective objective, String text, int score) {
|
||||
objective.getScore(text).setScore(score);
|
||||
}
|
||||
|
||||
private void clearScores(Scoreboard board) {
|
||||
for (String entry : board.getEntries()) {
|
||||
board.resetScores(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private long countSurvivorsInState(GameSession session, PlayerState... states) {
|
||||
return session.getPlayers().values().stream()
|
||||
.filter(p -> !p.isKiller())
|
||||
.filter(p -> {
|
||||
for (PlayerState state : states) {
|
||||
if (p.getState() == state) return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.count();
|
||||
}
|
||||
|
||||
private int getCompletedGenerators(GameSession session) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void removePlayer(Player player) {
|
||||
Scoreboard board = scoreboards.remove(player.getUniqueId());
|
||||
if (board != null) {
|
||||
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (Map.Entry<UUID, Scoreboard> entry : scoreboards.entrySet()) {
|
||||
Player player = Bukkit.getPlayer(entry.getKey());
|
||||
if (player != null) {
|
||||
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
|
||||
}
|
||||
}
|
||||
scoreboards.clear();
|
||||
}
|
||||
}
|
||||
57
src/main/java/dev/loki/ui/StatusIcons.java
Normal file
57
src/main/java/dev/loki/ui/StatusIcons.java
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package dev.loki.ui;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
public final class StatusIcons {
|
||||
|
||||
private StatusIcons() {}
|
||||
|
||||
public static final TextComponent HEALTHY_HEART = Component.text("❤")
|
||||
.color(NamedTextColor.RED);
|
||||
|
||||
public static final TextComponent INJURED_HEART = Component.text("🩸")
|
||||
.color(NamedTextColor.DARK_RED);
|
||||
|
||||
public static final TextComponent DEATH_SKULL = Component.text("☠")
|
||||
.color(NamedTextColor.DARK_GRAY);
|
||||
|
||||
public static final TextComponent HOOK = Component.text("⚓")
|
||||
.color(NamedTextColor.GRAY);
|
||||
|
||||
public static final TextComponent BLOOD_DROP = Component.text("🩸")
|
||||
.color(NamedTextColor.RED);
|
||||
|
||||
public static final TextComponent GENERATOR = Component.text("⚡")
|
||||
.color(NamedTextColor.YELLOW);
|
||||
|
||||
public static final TextComponent GENERATOR_BROKEN = Component.text("⚡")
|
||||
.color(NamedTextColor.DARK_RED);
|
||||
|
||||
public static final TextComponent ESCAPED = Component.text("✓")
|
||||
.color(NamedTextColor.GREEN);
|
||||
|
||||
public static final TextComponent SKILL_CHECK = Component.text("◆")
|
||||
.color(NamedTextColor.WHITE);
|
||||
|
||||
public static final TextComponent SKILL_CHECK_ZONE = Component.text("◆")
|
||||
.color(NamedTextColor.GREEN);
|
||||
|
||||
public static Component hearts(int count, NamedTextColor color) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
sb.append("❤");
|
||||
}
|
||||
return Component.text(sb.toString()).color(color);
|
||||
}
|
||||
|
||||
public static Component statusIcon(int hookCount) {
|
||||
return switch (hookCount) {
|
||||
case 0 -> Component.text(" ");
|
||||
case 1 -> Component.text("⚓ ").color(NamedTextColor.GRAY);
|
||||
case 2 -> Component.text("⚓⚓ ").color(NamedTextColor.GRAY);
|
||||
default -> Component.text("⚓⚓⚓").color(NamedTextColor.RED);
|
||||
};
|
||||
}
|
||||
}
|
||||
168
src/main/java/dev/loki/ui/TitleUI.java
Normal file
168
src/main/java/dev/loki/ui/TitleUI.java
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package dev.loki.ui;
|
||||
|
||||
import dev.loki.mdbd;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TitleUI {
|
||||
private final mdbd plugin;
|
||||
private final Map<UUID, SkillCheckState> activeSkillChecks;
|
||||
|
||||
public TitleUI(mdbd plugin) {
|
||||
this.plugin = plugin;
|
||||
this.activeSkillChecks = new HashMap<>();
|
||||
}
|
||||
|
||||
public void showGameStart(Player player, boolean isKiller) {
|
||||
Title.Times times = Title.Times.times(
|
||||
Duration.ofMillis(500),
|
||||
Duration.ofSeconds(3),
|
||||
Duration.ofMillis(500)
|
||||
);
|
||||
|
||||
Component title;
|
||||
Component subtitle;
|
||||
|
||||
if (isKiller) {
|
||||
title = Component.text("KILLER")
|
||||
.color(NamedTextColor.DARK_RED)
|
||||
.decoration(TextDecoration.BOLD, true);
|
||||
subtitle = Component.text("Sacrifice them all")
|
||||
.color(NamedTextColor.RED);
|
||||
} else {
|
||||
title = Component.text("SURVIVOR")
|
||||
.color(NamedTextColor.DARK_GREEN)
|
||||
.decoration(TextDecoration.BOLD, true);
|
||||
subtitle = Component.text("Repair generators and escape")
|
||||
.color(NamedTextColor.GREEN);
|
||||
}
|
||||
|
||||
player.showTitle(Title.title(title, subtitle, times));
|
||||
}
|
||||
|
||||
public void showVictory(Player player, boolean escaped) {
|
||||
Title.Times times = Title.Times.times(
|
||||
Duration.ofMillis(500),
|
||||
Duration.ofSeconds(5),
|
||||
Duration.ofSeconds(1)
|
||||
);
|
||||
|
||||
Component title = escaped
|
||||
? Component.text("ESCAPED!")
|
||||
.color(NamedTextColor.GREEN)
|
||||
.decoration(TextDecoration.BOLD, true)
|
||||
: Component.text("VICTORY!")
|
||||
.color(NamedTextColor.DARK_RED)
|
||||
.decoration(TextDecoration.BOLD, true);
|
||||
|
||||
Component subtitle = escaped
|
||||
? Component.text("You survived the night")
|
||||
.color(NamedTextColor.DARK_GREEN)
|
||||
: Component.text("All survivors sacrificed")
|
||||
.color(NamedTextColor.RED);
|
||||
|
||||
player.showTitle(Title.title(title, subtitle, times));
|
||||
}
|
||||
|
||||
public void showDefeat(Player player) {
|
||||
Title.Times times = Title.Times.times(
|
||||
Duration.ofMillis(500),
|
||||
Duration.ofSeconds(5),
|
||||
Duration.ofSeconds(1)
|
||||
);
|
||||
|
||||
Component title = Component.text("DEFEAT")
|
||||
.color(NamedTextColor.DARK_RED)
|
||||
.decoration(TextDecoration.BOLD, true);
|
||||
|
||||
Component subtitle = Component.text("The Entity claims all")
|
||||
.color(NamedTextColor.RED);
|
||||
|
||||
player.showTitle(Title.title(title, subtitle, times));
|
||||
}
|
||||
|
||||
public void showSkillCheck(Player player, int windowTicks, Consumer<Boolean> callback) {
|
||||
UUID uuid = player.getUniqueId();
|
||||
|
||||
if (activeSkillChecks.containsKey(uuid)) {
|
||||
callback.accept(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int startTick = plugin.getServer().getCurrentTick();
|
||||
int successStart = startTick + (windowTicks / 2);
|
||||
int successEnd = successStart + (windowTicks / 2);
|
||||
|
||||
SkillCheckState state = new SkillCheckState(startTick, successStart, successEnd, callback);
|
||||
activeSkillChecks.put(uuid, state);
|
||||
|
||||
plugin.getServer().getScheduler().runTaskTimer(plugin, task -> {
|
||||
int currentTick = plugin.getServer().getCurrentTick();
|
||||
SkillCheckState checkState = activeSkillChecks.get(uuid);
|
||||
|
||||
if (checkState == null || player.isSneaking()) {
|
||||
task.cancel();
|
||||
boolean success = checkState != null && currentTick >= checkState.successStart
|
||||
&& currentTick <= checkState.successEnd;
|
||||
activeSkillChecks.remove(uuid);
|
||||
callback.accept(success);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTick > checkState.successEnd + 5) {
|
||||
task.cancel();
|
||||
activeSkillChecks.remove(uuid);
|
||||
callback.accept(false);
|
||||
return;
|
||||
}
|
||||
|
||||
renderSkillCheck(player, checkState, currentTick);
|
||||
}, 1L, 1L);
|
||||
}
|
||||
|
||||
private void renderSkillCheck(Player player, SkillCheckState state, int currentTick) {
|
||||
double progress = (currentTick - state.startTick) / 40.0;
|
||||
int position = (int) (progress * 20) % 21;
|
||||
|
||||
StringBuilder visual = new StringBuilder();
|
||||
int successStartPos = (int) ((state.successStart - state.startTick) / 40.0 * 20);
|
||||
int successEndPos = (int) ((state.successEnd - state.startTick) / 40.0 * 20);
|
||||
|
||||
for (int i = 0; i <= 20; i++) {
|
||||
if (i == position) {
|
||||
visual.append("◆");
|
||||
} else if (i >= successStartPos && i <= successEndPos) {
|
||||
visual.append("░");
|
||||
} else {
|
||||
visual.append("─");
|
||||
}
|
||||
}
|
||||
|
||||
Component title = Component.text(visual.toString())
|
||||
.color(NamedTextColor.WHITE);
|
||||
|
||||
Component subtitle = Component.text("CROUCH when in the zone!")
|
||||
.color(NamedTextColor.YELLOW);
|
||||
|
||||
Title.Times times = Title.Times.times(Duration.ZERO, Duration.ofMillis(100), Duration.ZERO);
|
||||
player.showTitle(Title.title(title, subtitle, times));
|
||||
}
|
||||
|
||||
public void clearSkillChecks() {
|
||||
for (SkillCheckState state : activeSkillChecks.values()) {
|
||||
state.callback().accept(false);
|
||||
}
|
||||
activeSkillChecks.clear();
|
||||
}
|
||||
|
||||
private record SkillCheckState(int startTick, int successStart, int successEnd, Consumer<Boolean> callback) {}
|
||||
}
|
||||
72
src/main/java/dev/loki/window/Window.java
Normal file
72
src/main/java/dev/loki/window/Window.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package dev.loki.window;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
public class Window {
|
||||
private final Location bottomLocation;
|
||||
private final Location topLocation;
|
||||
private boolean blocked;
|
||||
|
||||
public Window(Location bottomLocation) {
|
||||
this.bottomLocation = bottomLocation;
|
||||
this.topLocation = bottomLocation.clone().add(0, 1, 0);
|
||||
this.blocked = false;
|
||||
}
|
||||
|
||||
public Location getBottomLocation() {
|
||||
return bottomLocation;
|
||||
}
|
||||
|
||||
public Location getTopLocation() {
|
||||
return topLocation;
|
||||
}
|
||||
|
||||
public Location getVaultTarget(boolean killerVault) {
|
||||
if (killerVault) {
|
||||
return bottomLocation.clone().add(0, -0.5, 0);
|
||||
}
|
||||
return topLocation.clone().add(0, 0.5, 0);
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public void setBlocked(boolean blocked) {
|
||||
this.blocked = blocked;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
Block bottom = bottomLocation.getBlock();
|
||||
Block top = topLocation.getBlock();
|
||||
return bottom.getType().isSolid() || bottom.getType() == Material.AIR;
|
||||
}
|
||||
|
||||
public boolean isVaultable(PlayerContext ctx) {
|
||||
if (blocked && !ctx.isKiller()) return false;
|
||||
if (!isValid()) return false;
|
||||
|
||||
Location playerLoc = ctx.getLocation();
|
||||
return getDistance(playerLoc) <= 2.5;
|
||||
}
|
||||
|
||||
public double getDistance(Location location) {
|
||||
return bottomLocation.distance(location);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.blocked = false;
|
||||
}
|
||||
|
||||
public record PlayerContext(boolean isKiller, Location location) {
|
||||
public boolean isKiller() {
|
||||
return isKiller;
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/main/java/dev/loki/window/WindowManager.java
Normal file
82
src/main/java/dev/loki/window/WindowManager.java
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package dev.loki.window;
|
||||
|
||||
import dev.loki.config.LocationsConfig;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WindowManager {
|
||||
private final List<Window> windows;
|
||||
|
||||
public WindowManager(LocationsConfig locations) {
|
||||
this.windows = new ArrayList<>();
|
||||
loadWindows(locations);
|
||||
}
|
||||
|
||||
private void loadWindows(LocationsConfig locations) {
|
||||
for (Location loc : locations.getWindows()) {
|
||||
windows.add(new Window(loc));
|
||||
}
|
||||
}
|
||||
|
||||
public void addWindow(Window window) {
|
||||
windows.add(window);
|
||||
}
|
||||
|
||||
public void removeWindow(Window window) {
|
||||
windows.remove(window);
|
||||
}
|
||||
|
||||
public List<Window> getAllWindows() {
|
||||
return new ArrayList<>(windows);
|
||||
}
|
||||
|
||||
public Window findNearestWindow(Location location, double maxRadius) {
|
||||
Window nearest = null;
|
||||
double minDistance = maxRadius;
|
||||
|
||||
for (Window window : windows) {
|
||||
double dist = window.getDistance(location);
|
||||
if (dist < minDistance) {
|
||||
minDistance = dist;
|
||||
nearest = window;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public Window findVaultableWindow(Window.PlayerContext ctx, double maxRadius) {
|
||||
Window nearest = null;
|
||||
double minDistance = maxRadius;
|
||||
|
||||
for (Window window : windows) {
|
||||
if (!window.isVaultable(ctx)) continue;
|
||||
|
||||
double dist = window.getDistance(ctx.getLocation());
|
||||
if (dist < minDistance) {
|
||||
minDistance = dist;
|
||||
nearest = window;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public void resetAll() {
|
||||
for (Window window : windows) {
|
||||
window.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return windows.size();
|
||||
}
|
||||
|
||||
public int getValidCount() {
|
||||
return (int) windows.stream().filter(Window::isValid).count();
|
||||
}
|
||||
|
||||
public void clearInvalid() {
|
||||
windows.removeIf(w -> !w.isValid());
|
||||
}
|
||||
}
|
||||
112
src/main/java/dev/loki/window/WindowVault.java
Normal file
112
src/main/java/dev/loki/window/WindowVault.java
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package dev.loki.window;
|
||||
|
||||
import dev.loki.player.BasePlayer;
|
||||
import dev.loki.player.Killer;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WindowVault {
|
||||
private final WindowManager windowManager;
|
||||
private final Set<UUID> vaultingPlayers;
|
||||
|
||||
public WindowVault(WindowManager windowManager) {
|
||||
this.windowManager = windowManager;
|
||||
this.vaultingPlayers = new HashSet<>();
|
||||
}
|
||||
|
||||
public boolean tryVault(BasePlayer player, org.bukkit.plugin.Plugin plugin) {
|
||||
if (isVaulting(player)) return false;
|
||||
|
||||
Player bukkitPlayer = player.getPlayer();
|
||||
if (bukkitPlayer == null) return false;
|
||||
|
||||
Window.PlayerContext ctx = new Window.PlayerContext(
|
||||
player.isKiller(),
|
||||
bukkitPlayer.getLocation()
|
||||
);
|
||||
|
||||
Window window = windowManager.findVaultableWindow(ctx, 3.0);
|
||||
if (window == null) {
|
||||
bukkitPlayer.sendMessage("§cНет окна поблизости!");
|
||||
return false;
|
||||
}
|
||||
|
||||
double vaultTime = player.isKiller() ? 1.5 : getSurvivorVaultTime(player, window);
|
||||
performVault(player, window, vaultTime, plugin);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isVaulting(BasePlayer player) {
|
||||
return vaultingPlayers.contains(player.getUuid());
|
||||
}
|
||||
|
||||
private double getSurvivorVaultTime(BasePlayer player, Window window) {
|
||||
if (isFastVaultAvailable(player, window)) {
|
||||
return 1.0;
|
||||
}
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
private boolean isFastVaultAvailable(BasePlayer player, Window window) {
|
||||
return !hasRecentVault(player.getUuid());
|
||||
}
|
||||
|
||||
private boolean hasRecentVault(UUID playerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void performVault(BasePlayer player, Window window, double seconds, org.bukkit.plugin.Plugin plugin) {
|
||||
Player bukkitPlayer = player.getPlayer();
|
||||
if (bukkitPlayer == null) return;
|
||||
|
||||
vaultingPlayers.add(player.getUuid());
|
||||
|
||||
int ticks = (int) (seconds * 20);
|
||||
Location target = window.getVaultTarget(player.isKiller());
|
||||
|
||||
bukkitPlayer.addPotionEffect(new PotionEffect(
|
||||
PotionEffectType.SLOW, ticks, 10, false, false));
|
||||
bukkitPlayer.addPotionEffect(new PotionEffect(
|
||||
PotionEffectType.JUMP, ticks, -10, false, false));
|
||||
|
||||
bukkitPlayer.getWorld().playSound(bukkitPlayer.getLocation(),
|
||||
Sound.BLOCK_LADDER_STEP, 0.5f, 1.0f);
|
||||
|
||||
new VaultTask(player, target, plugin).runTaskLater(plugin, ticks);
|
||||
}
|
||||
|
||||
private void completeVault(BasePlayer player, Location target) {
|
||||
vaultingPlayers.remove(player.getUuid());
|
||||
|
||||
Player bukkitPlayer = player.getPlayer();
|
||||
if (bukkitPlayer == null) return;
|
||||
|
||||
bukkitPlayer.teleport(target);
|
||||
bukkitPlayer.getWorld().playSound(target, Sound.BLOCK_LADDER_STEP, 0.5f, 0.8f);
|
||||
bukkitPlayer.removePotionEffect(PotionEffectType.SLOW);
|
||||
bukkitPlayer.removePotionEffect(PotionEffectType.JUMP);
|
||||
}
|
||||
|
||||
private class VaultTask extends BukkitRunnable {
|
||||
private final BasePlayer player;
|
||||
private final Location target;
|
||||
|
||||
VaultTask(BasePlayer player, Location target, org.bukkit.plugin.Plugin plugin) {
|
||||
this.player = player;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
completeVault(player, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue