refactor: generator minigame system (gui + text), fix color codes, holograms, messages, & config
This commit is contained in:
parent
957ab4a5a2
commit
7c7562a8d4
48 changed files with 1628 additions and 733 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.loki.chest;
|
package dev.loki.chest;
|
||||||
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.util.TextUtil;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.ArmorStand;
|
import org.bukkit.entity.ArmorStand;
|
||||||
|
|
@ -77,7 +78,7 @@ public class ChestHologram {
|
||||||
if (useTextDisplay) {
|
if (useTextDisplay) {
|
||||||
TextDisplay display = loc.getWorld().spawn(holoLoc, TextDisplay.class, d -> {
|
TextDisplay display = loc.getWorld().spawn(holoLoc, TextDisplay.class, d -> {
|
||||||
d.setBillboard(Display.Billboard.VERTICAL);
|
d.setBillboard(Display.Billboard.VERTICAL);
|
||||||
d.setSeeThrough(true);
|
d.setSeeThrough(false);
|
||||||
d.setShadowed(true);
|
d.setShadowed(true);
|
||||||
d.setDefaultBackground(false);
|
d.setDefaultBackground(false);
|
||||||
d.setPersistent(false);
|
d.setPersistent(false);
|
||||||
|
|
@ -117,7 +118,14 @@ public class ChestHologram {
|
||||||
|
|
||||||
String status;
|
String status;
|
||||||
if (chest.isEmpty()) {
|
if (chest.isEmpty()) {
|
||||||
|
long secs = manager.getSecondsUntilNextReset();
|
||||||
|
if (secs > 60) {
|
||||||
|
status = "§7Пуст §8(" + (secs / 60) + "м)";
|
||||||
|
} else if (secs > 0) {
|
||||||
|
status = "§7Пуст §8(" + secs + "с)";
|
||||||
|
} else {
|
||||||
status = "§7Пуст";
|
status = "§7Пуст";
|
||||||
|
}
|
||||||
} else if (chest.isOpened()) {
|
} else if (chest.isOpened()) {
|
||||||
status = "§6Открывается...";
|
status = "§6Открывается...";
|
||||||
} else if (chest.hasLoot()) {
|
} else if (chest.hasLoot()) {
|
||||||
|
|
@ -130,7 +138,7 @@ public class ChestHologram {
|
||||||
String text = "§e📦 Сундук §f#" + visibleIndex + "/" + total + "\n§7" + status;
|
String text = "§e📦 Сундук §f#" + visibleIndex + "/" + total + "\n§7" + status;
|
||||||
|
|
||||||
if (display instanceof TextDisplay td) {
|
if (display instanceof TextDisplay td) {
|
||||||
td.text(net.kyori.adventure.text.Component.text(text));
|
td.text(TextUtil.color(text));
|
||||||
Location base = chest.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5);
|
Location base = chest.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5);
|
||||||
td.teleport(base);
|
td.teleport(base);
|
||||||
} else if (display instanceof ArmorStand as) {
|
} else if (display instanceof ArmorStand as) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class ChestManager {
|
||||||
private final GameConfig gameConfig;
|
private final GameConfig gameConfig;
|
||||||
private ChestLoot chestLoot;
|
private ChestLoot chestLoot;
|
||||||
private BukkitTask lootResetTask;
|
private BukkitTask lootResetTask;
|
||||||
|
private long lastResetTime = System.currentTimeMillis();
|
||||||
|
|
||||||
public ChestManager(JavaPlugin plugin, LocationsConfig locationsConfig, GameConfig gameConfig) {
|
public ChestManager(JavaPlugin plugin, LocationsConfig locationsConfig, GameConfig gameConfig) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
@ -73,6 +74,7 @@ public class ChestManager {
|
||||||
chest.setEmpty(false);
|
chest.setEmpty(false);
|
||||||
refilled++;
|
refilled++;
|
||||||
}
|
}
|
||||||
|
this.lastResetTime = System.currentTimeMillis();
|
||||||
plugin.getLogger().info("[Chest] Loot reset — " + refilled + " chests refilled");
|
plugin.getLogger().info("[Chest] Loot reset — " + refilled + " chests refilled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,6 +117,7 @@ public class ChestManager {
|
||||||
chest.setEmpty(false);
|
chest.setEmpty(false);
|
||||||
chest.setLoot(chestLoot.rollLoot());
|
chest.setLoot(chestLoot.rollLoot());
|
||||||
}
|
}
|
||||||
|
this.lastResetTime = System.currentTimeMillis();
|
||||||
plugin.getLogger().info("Reset all chests");
|
plugin.getLogger().info("Reset all chests");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +140,14 @@ public class ChestManager {
|
||||||
return (int) chests.values().stream().filter(Chest::isAvailable).count();
|
return (int) chests.values().stream().filter(Chest::isAvailable).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Seconds until the next automatic loot reset. */
|
||||||
|
public long getSecondsUntilNextReset() {
|
||||||
|
long elapsed = (System.currentTimeMillis() - lastResetTime) / 1000;
|
||||||
|
long total = (long) (gameConfig.getChestLootResetMinutes() * 60);
|
||||||
|
long remaining = total - elapsed;
|
||||||
|
return Math.max(0, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
public ChestLoot getChestLoot() {
|
public ChestLoot getChestLoot() {
|
||||||
return chestLoot;
|
return chestLoot;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package dev.loki.chest;
|
package dev.loki.chest;
|
||||||
|
|
||||||
|
import dev.loki.config.MessagesConfig;
|
||||||
import dev.loki.item.ChestLoot;
|
import dev.loki.item.ChestLoot;
|
||||||
|
import dev.loki.mdbd;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
@ -28,6 +31,10 @@ public class ChestOpening {
|
||||||
this.activeOpenings = new HashMap<>();
|
this.activeOpenings = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MessagesConfig messages() {
|
||||||
|
return ((mdbd) plugin).getConfigManager().getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
/** Передать конфиг для весов лута */
|
/** Передать конфиг для весов лута */
|
||||||
public void setChestLoot(ChestLoot loot) {
|
public void setChestLoot(ChestLoot loot) {
|
||||||
this.chestLoot = loot;
|
this.chestLoot = loot;
|
||||||
|
|
@ -44,7 +51,7 @@ public class ChestOpening {
|
||||||
if (!chest.hasLoot()) {
|
if (!chest.hasLoot()) {
|
||||||
ItemStack rolled = chestLoot.rollLoot();
|
ItemStack rolled = chestLoot.rollLoot();
|
||||||
if (rolled == null) {
|
if (rolled == null) {
|
||||||
player.sendMessage("§cThis chest is empty!");
|
player.sendMessage(messages().get("chest.empty"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
chest.setLoot(rolled);
|
chest.setLoot(rolled);
|
||||||
|
|
@ -52,13 +59,13 @@ public class ChestOpening {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chest.isAvailable()) {
|
if (!chest.isAvailable()) {
|
||||||
player.sendMessage("§cThis chest is empty!");
|
player.sendMessage(messages().get("chest.empty"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
chest.setOpened(true);
|
chest.setOpened(true);
|
||||||
chest.openVisual();
|
chest.openVisual();
|
||||||
player.sendMessage("§eOpening chest...");
|
player.sendMessage(messages().get("chest.opening"));
|
||||||
playOpeningSound(player, chest.getLocation());
|
playOpeningSound(player, chest.getLocation());
|
||||||
|
|
||||||
BukkitRunnable task = new BukkitRunnable() {
|
BukkitRunnable task = new BukkitRunnable() {
|
||||||
|
|
@ -72,12 +79,11 @@ public class ChestOpening {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!chest.isInRange(player.getLocation(), OPEN_RANGE)) {
|
if (!chest.isInRange(player.getLocation(), OPEN_RANGE)) {
|
||||||
player.sendMessage(net.kyori.adventure.text.Component.text(
|
player.sendMessage(messages().get("chest.too-far"));
|
||||||
"[DBD] Слишком далеко!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
|
||||||
chest.setOpened(false);
|
chest.setOpened(false);
|
||||||
chest.closeVisual();
|
chest.closeVisual();
|
||||||
activeOpenings.remove(playerId);
|
activeOpenings.remove(playerId);
|
||||||
player.sendActionBar(net.kyori.adventure.text.Component.empty());
|
player.sendActionBar(Component.empty());
|
||||||
cancel();
|
cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -85,10 +91,10 @@ public class ChestOpening {
|
||||||
int filled = (int)(8.0 * ticks / OPEN_TIME_TICKS);
|
int filled = (int)(8.0 * ticks / OPEN_TIME_TICKS);
|
||||||
int empty = 8 - filled;
|
int empty = 8 - filled;
|
||||||
int pct = (int)(100.0 * ticks / OPEN_TIME_TICKS);
|
int pct = (int)(100.0 * ticks / OPEN_TIME_TICKS);
|
||||||
player.sendActionBar(net.kyori.adventure.text.Component.text(
|
String bar = "§e" + "█".repeat(filled) + "§7" + "░".repeat(empty);
|
||||||
"§6Открытие: §e" + "█".repeat(filled) + "§7" + "░".repeat(empty) + " " + pct + "%"));
|
player.sendActionBar(messages().get("chest.actionbar", bar, pct));
|
||||||
if (ticks >= OPEN_TIME_TICKS) {
|
if (ticks >= OPEN_TIME_TICKS) {
|
||||||
player.sendActionBar(net.kyori.adventure.text.Component.empty());
|
player.sendActionBar(Component.empty());
|
||||||
completeOpening(player, chest);
|
completeOpening(player, chest);
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +113,7 @@ public class ChestOpening {
|
||||||
|
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.cancel();
|
task.cancel();
|
||||||
player.sendMessage("§cOpening cancelled!");
|
player.sendMessage(messages().get("chest.cancelled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,8 +139,7 @@ public class ChestOpening {
|
||||||
// Use pre-set loot from chest, or roll new if none
|
// Use pre-set loot from chest, or roll new if none
|
||||||
ItemStack loot = chest.hasLoot() ? chest.getLoot() : chestLoot.rollLoot();
|
ItemStack loot = chest.hasLoot() ? chest.getLoot() : chestLoot.rollLoot();
|
||||||
if (loot == null) {
|
if (loot == null) {
|
||||||
player.sendMessage(net.kyori.adventure.text.Component.text(
|
player.sendMessage(messages().get("chest.empty"));
|
||||||
"[DBD] Сундук пуст!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
|
||||||
chest.setOpened(false);
|
chest.setOpened(false);
|
||||||
chest.closeVisual();
|
chest.closeVisual();
|
||||||
return;
|
return;
|
||||||
|
|
@ -146,6 +151,7 @@ public class ChestOpening {
|
||||||
player.getWorld().dropItemNaturally(player.getLocation(), item);
|
player.getWorld().dropItemNaturally(player.getLocation(), item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
chest.setOpened(false);
|
||||||
chest.setEmpty(true);
|
chest.setEmpty(true);
|
||||||
chest.setLoot(null); // Clear loot after giving
|
chest.setLoot(null); // Clear loot after giving
|
||||||
chest.closeVisual();
|
chest.closeVisual();
|
||||||
|
|
@ -156,8 +162,7 @@ public class ChestOpening {
|
||||||
.serialize(loot.getItemMeta().displayName())
|
.serialize(loot.getItemMeta().displayName())
|
||||||
: loot.getType().name();
|
: loot.getType().name();
|
||||||
|
|
||||||
player.sendMessage(net.kyori.adventure.text.Component.text(
|
player.sendMessage(messages().get("chest.found", itemName));
|
||||||
"[DBD] Вы нашли: " + itemName, net.kyori.adventure.text.format.NamedTextColor.GREEN));
|
|
||||||
playSuccessSound(player, chest.getLocation());
|
playSuccessSound(player, chest.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
package dev.loki.command;
|
package dev.loki.command;
|
||||||
|
|
||||||
import dev.loki.config.LocationsConfig;
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
import dev.loki.util.TextUtil;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
@ -27,13 +23,15 @@ public class DBDAdminCommand implements TabExecutor {
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
@NotNull String label, @NotNull String[] args) {
|
@NotNull String label, @NotNull String[] args) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
|
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(messages.get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(messages.get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,14 +62,14 @@ public class DBDAdminCommand implements TabExecutor {
|
||||||
case "killer-spawn", "killerspawn" -> listType(player, "Спавн киллера", locations.getKillerSpawns());
|
case "killer-spawn", "killerspawn" -> listType(player, "Спавн киллера", locations.getKillerSpawns());
|
||||||
case "survivor-spawn", "survivorspawn" -> listType(player, "Спавн выживших", locations.getSurvivorSpawns());
|
case "survivor-spawn", "survivorspawn" -> listType(player, "Спавн выживших", locations.getSurvivorSpawns());
|
||||||
default -> {
|
default -> {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + args[1]));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin.unknown-type", args[1]));
|
||||||
yield true;
|
yield true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Показать всё
|
// Показать всё
|
||||||
player.sendMessage(TextUtil.color("<#FF6B00>=== Все установленные объекты ==="));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin-list.header"));
|
||||||
|
|
||||||
listType(player, "Генераторы", locations.getGenerators());
|
listType(player, "Генераторы", locations.getGenerators());
|
||||||
listType(player, "Крюки", locations.getHooks());
|
listType(player, "Крюки", locations.getHooks());
|
||||||
|
|
@ -83,24 +81,24 @@ public class DBDAdminCommand implements TabExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean listType(Player player, String label, List<Location> locations) {
|
private boolean listType(Player player, String label, List<Location> locations) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
if (locations.isEmpty()) {
|
if (locations.isEmpty()) {
|
||||||
player.sendMessage(TextUtil.color("<#808000>" + label + ": нет"));
|
player.sendMessage(messages.get("dbdadmin-list.none", label));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
player.sendMessage(TextUtil.color("<#FFD700>" + label + " (" + locations.size() + "):"));
|
player.sendMessage(messages.get("dbdadmin-list.count", label, locations.size()));
|
||||||
for (int i = 0; i < locations.size(); i++) {
|
for (int i = 0; i < locations.size(); i++) {
|
||||||
Location loc = locations.get(i);
|
Location loc = locations.get(i);
|
||||||
player.sendMessage(TextUtil.color(String.format(
|
player.sendMessage(messages.get("dbdadmin-list.entry",
|
||||||
"<#00CED1> #%d %s [%.1f, %.1f, %.1f]",
|
|
||||||
i + 1, loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ()
|
i + 1, loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ()
|
||||||
)));
|
));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleWorld(Player player, String[] args) {
|
private boolean handleWorld(Player player, String[] args) {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdadmin world <game|название мира>"));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin-list.world-usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,27 +108,28 @@ public class DBDAdminCommand implements TabExecutor {
|
||||||
if (worldArg.equals("game")) {
|
if (worldArg.equals("game")) {
|
||||||
target = plugin.getLobbyManager().getGameWorld();
|
target = plugin.getLobbyManager().getGameWorld();
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Мир game не загружен!"));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin-list.world-not-found"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = org.bukkit.Bukkit.getWorld(args[1]);
|
target = org.bukkit.Bukkit.getWorld(args[1]);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Мир не найден: " + args[1]));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin-list.world-not-exist", args[1]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
player.teleport(target.getSpawnLocation());
|
player.teleport(target.getSpawnLocation());
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Телепортирован в мир: <#FFD700>" + target.getName()));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin-list.world-teleport", target.getName()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendUsage(CommandSender sender) {
|
private void sendUsage(CommandSender sender) {
|
||||||
sender.sendMessage(TextUtil.color("<#FF6B00>=== DBD Admin ==="));
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin list — Список всех объектов"));
|
sender.sendMessage(messages.get("usage.admin-list-header"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin list <тип> — Список по типу"));
|
sender.sendMessage(messages.get("usage.admin-list-list"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdadmin world <game|мир> — Телепорт в мир"));
|
sender.sendMessage(messages.get("usage.admin-list-type"));
|
||||||
|
sender.sendMessage(messages.get("usage.admin-world"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,8 @@ import dev.loki.core.GameManager;
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.BasePlayer;
|
import dev.loki.player.BasePlayer;
|
||||||
import dev.loki.player.Killer;
|
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.util.TextUtil;
|
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -31,8 +27,10 @@ public class DBDAdminPlayerCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean handle(CommandSender sender, String[] args) {
|
public boolean handle(CommandSender sender, String[] args) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
|
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(messages.get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
|
|
@ -43,7 +41,7 @@ public class DBDAdminPlayerCommand {
|
||||||
String action = args[1].toLowerCase(Locale.ROOT);
|
String action = args[1].toLowerCase(Locale.ROOT);
|
||||||
GameSession session = resolveSession(sender, args);
|
GameSession session = resolveSession(sender, args);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
sender.sendMessage(Component.text("[DBD] Нет активной игровой сессии.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.no-session"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +61,7 @@ public class DBDAdminPlayerCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean heal(CommandSender sender, GameSession session, String[] args) {
|
private boolean heal(CommandSender sender, GameSession session, String[] args) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
Survivor target = resolveSurvivor(sender, session, args, 2);
|
Survivor target = resolveSurvivor(sender, session, args, 2);
|
||||||
if (target == null) return true;
|
if (target == null) return true;
|
||||||
target.setState(PlayerState.HEALTHY);
|
target.setState(PlayerState.HEALTHY);
|
||||||
|
|
@ -70,13 +69,14 @@ public class DBDAdminPlayerCommand {
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
p.setSwimming(false);
|
p.setSwimming(false);
|
||||||
p.setHealth(20.0);
|
p.setHealth(20.0);
|
||||||
p.sendMessage(Component.text("[DBD] Админ вылечил вас.", NamedTextColor.GREEN));
|
p.sendMessage(messages.get("dbdadmin.heal.target"));
|
||||||
}
|
}
|
||||||
sender.sendMessage(Component.text("[DBD] " + playerName(target) + " → HEALTHY", NamedTextColor.GREEN));
|
sender.sendMessage(messages.get("dbdadmin.heal.sender", playerName(target)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setState(CommandSender sender, GameSession session, String[] args, PlayerState state) {
|
private boolean setState(CommandSender sender, GameSession session, String[] args, PlayerState state) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
Survivor target = resolveSurvivor(sender, session, args, 2);
|
Survivor target = resolveSurvivor(sender, session, args, 2);
|
||||||
if (target == null) return true;
|
if (target == null) return true;
|
||||||
target.setState(state);
|
target.setState(state);
|
||||||
|
|
@ -86,28 +86,30 @@ public class DBDAdminPlayerCommand {
|
||||||
else p.setSwimming(false);
|
else p.setSwimming(false);
|
||||||
p.setHealth(20.0);
|
p.setHealth(20.0);
|
||||||
}
|
}
|
||||||
sender.sendMessage(Component.text("[DBD] " + playerName(target) + " → " + state.name(), NamedTextColor.YELLOW));
|
sender.sendMessage(messages.get("dbdadmin.state.sender", playerName(target), state.name()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setStateArg(CommandSender sender, GameSession session, String[] args) {
|
private boolean setStateArg(CommandSender sender, GameSession session, String[] args) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
if (args.length < 4) {
|
if (args.length < 4) {
|
||||||
sender.sendMessage(Component.text("[DBD] /dbd admin state <игрок> <HEALTHY|INJURED|DOWNED|HOOKED|...>", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.state.usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
PlayerState state;
|
PlayerState state;
|
||||||
try {
|
try {
|
||||||
state = PlayerState.valueOf(args[3].toUpperCase(Locale.ROOT));
|
state = PlayerState.valueOf(args[3].toUpperCase(Locale.ROOT));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
sender.sendMessage(Component.text("[DBD] Неизвестный статус.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.state.unknown"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return setState(sender, session, args, state);
|
return setState(sender, session, args, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setHooks(CommandSender sender, GameSession session, String[] args) {
|
private boolean setHooks(CommandSender sender, GameSession session, String[] args) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
if (args.length < 4) {
|
if (args.length < 4) {
|
||||||
sender.sendMessage(Component.text("[DBD] /dbd admin hooks <игрок> <0-3>", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.hooks.usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Survivor target = resolveSurvivor(sender, session, args, 2);
|
Survivor target = resolveSurvivor(sender, session, args, 2);
|
||||||
|
|
@ -116,33 +118,33 @@ public class DBDAdminPlayerCommand {
|
||||||
try {
|
try {
|
||||||
hooks = Integer.parseInt(args[3]);
|
hooks = Integer.parseInt(args[3]);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
sender.sendMessage(Component.text("[DBD] Число 0–3.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.hooks.invalid"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
hooks = Math.max(0, Math.min(3, hooks));
|
hooks = Math.max(0, Math.min(3, hooks));
|
||||||
target.setHooksTaken(hooks);
|
target.setHooksTaken(hooks);
|
||||||
sender.sendMessage(Component.text(
|
sender.sendMessage(messages.get("dbdadmin.hooks.sender", playerName(target), target.getHooksTaken()));
|
||||||
"[DBD] " + playerName(target) + " hooks=" + target.getHooksTaken(), NamedTextColor.GREEN));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Survivor resolveSurvivor(CommandSender sender, GameSession session, String[] args, int nameIndex) {
|
private Survivor resolveSurvivor(CommandSender sender, GameSession session, String[] args, int nameIndex) {
|
||||||
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
if (args.length <= nameIndex) {
|
if (args.length <= nameIndex) {
|
||||||
if (sender instanceof Player pl) {
|
if (sender instanceof Player pl) {
|
||||||
BasePlayer bp = session.getPlayer(pl.getUniqueId());
|
BasePlayer bp = session.getPlayer(pl.getUniqueId());
|
||||||
if (bp instanceof Survivor s) return s;
|
if (bp instanceof Survivor s) return s;
|
||||||
}
|
}
|
||||||
sender.sendMessage(Component.text("[DBD] Укажите игрока.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.resolve.no-player"));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Player target = Bukkit.getPlayer(args[nameIndex]);
|
Player target = Bukkit.getPlayer(args[nameIndex]);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
sender.sendMessage(Component.text("[DBD] Игрок не в сети.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.resolve.offline"));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BasePlayer bp = session.getPlayer(target.getUniqueId());
|
BasePlayer bp = session.getPlayer(target.getUniqueId());
|
||||||
if (!(bp instanceof Survivor s)) {
|
if (!(bp instanceof Survivor s)) {
|
||||||
sender.sendMessage(Component.text("[DBD] Игрок не в этой игре или не выживший.", NamedTextColor.RED));
|
sender.sendMessage(messages.get("dbdadmin.resolve.not-in-game"));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
@ -158,8 +160,8 @@ public class DBDAdminPlayerCommand {
|
||||||
newState = !dev.loki.listener.PlayerJumpListener.isJumpingAllowed();
|
newState = !dev.loki.listener.PlayerJumpListener.isJumpingAllowed();
|
||||||
}
|
}
|
||||||
dev.loki.listener.PlayerJumpListener.setJumpingAllowed(newState);
|
dev.loki.listener.PlayerJumpListener.setJumpingAllowed(newState);
|
||||||
sender.sendMessage(Component.text(
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbdadmin.jump.sender",
|
||||||
"[DBD] Прыжки " + (newState ? "§aВКЛЮЧЕНЫ" : "§cВЫКЛЮЧЕНЫ"), NamedTextColor.GOLD));
|
newState ? "ВКЛЮЧЕНЫ" : "ВЫКЛЮЧЕНЫ"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,14 +186,15 @@ public class DBDAdminPlayerCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendUsage(CommandSender sender) {
|
private void sendUsage(CommandSender sender) {
|
||||||
sender.sendMessage(TextUtil.color("<#FF6B00>=== /dbd admin ==="));
|
var messages = plugin.getConfigManager().getMessages();
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin heal [игрок]"));
|
sender.sendMessage(messages.get("usage.admin-header"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin down [игрок]"));
|
sender.sendMessage(messages.get("usage.admin-heal"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin unhook [игрок]"));
|
sender.sendMessage(messages.get("usage.admin-down"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin state <игрок> <статус>"));
|
sender.sendMessage(messages.get("usage.admin-unhook"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin hooks <игрок> <0-3>"));
|
sender.sendMessage(messages.get("usage.admin-state"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin kill [игрок]"));
|
sender.sendMessage(messages.get("usage.admin-hooks"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbd admin jump [on/off] — вкл/выкл прыжки"));
|
sender.sendMessage(messages.get("usage.admin-kill"));
|
||||||
|
sender.sendMessage(messages.get("usage.admin-jump"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static java.util.List<String> tabComplete(String[] args) {
|
public static java.util.List<String> tabComplete(String[] args) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package dev.loki.command;
|
package dev.loki.command;
|
||||||
|
|
||||||
import dev.loki.core.GameManager;
|
import dev.loki.core.GameManager;
|
||||||
|
import dev.loki.core.GameSession;
|
||||||
import dev.loki.core.GameState;
|
import dev.loki.core.GameState;
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
import net.kyori.adventure.text.Component;
|
import dev.loki.player.BasePlayer;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import dev.loki.world.LobbyRegistry;
|
||||||
|
import org.bukkit.attribute.Attribute;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.TabExecutor;
|
import org.bukkit.command.TabExecutor;
|
||||||
|
|
@ -13,16 +15,19 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DBDCommand implements TabExecutor {
|
public class DBDCommand implements TabExecutor {
|
||||||
private final mdbd plugin;
|
private final mdbd plugin;
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
|
private final LobbyRegistry lobbyRegistry;
|
||||||
private final DBDAdminPlayerCommand adminPlayerCommand;
|
private final DBDAdminPlayerCommand adminPlayerCommand;
|
||||||
|
|
||||||
public DBDCommand(mdbd plugin, GameManager gameManager) {
|
public DBDCommand(mdbd plugin, GameManager gameManager, LobbyRegistry lobbyRegistry) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
|
this.lobbyRegistry = lobbyRegistry;
|
||||||
this.adminPlayerCommand = new DBDAdminPlayerCommand(plugin, gameManager);
|
this.adminPlayerCommand = new DBDAdminPlayerCommand(plugin, gameManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,12 +39,14 @@ public class DBDCommand implements TabExecutor {
|
||||||
return switch (args[0].toLowerCase()) {
|
return switch (args[0].toLowerCase()) {
|
||||||
case "join" -> handleJoin(sender, args);
|
case "join" -> handleJoin(sender, args);
|
||||||
case "leave" -> handleLeave(sender);
|
case "leave" -> handleLeave(sender);
|
||||||
|
case "create" -> handleCreate(sender, args);
|
||||||
|
case "list" -> handleList(sender);
|
||||||
|
case "delete" -> handleDelete(sender, args);
|
||||||
case "forcestart" -> handleForceStart(sender);
|
case "forcestart" -> handleForceStart(sender);
|
||||||
case "stop" -> handleStop(sender);
|
case "stop" -> handleStop(sender);
|
||||||
case "reload" -> handleReload(sender);
|
case "reload" -> handleReload(sender);
|
||||||
case "save" -> handleMapSave(sender, args);
|
case "save" -> handleMapSave(sender, args);
|
||||||
case "load" -> handleMapLoad(sender, args);
|
case "load" -> handleMapLoad(sender, args);
|
||||||
case "maps" -> handleMapsList(sender);
|
|
||||||
case "lobby" -> handleLobby(sender);
|
case "lobby" -> handleLobby(sender);
|
||||||
case "setlobby" -> handleSetLobby(sender);
|
case "setlobby" -> handleSetLobby(sender);
|
||||||
case "admin" -> adminPlayerCommand.handle(sender, args);
|
case "admin" -> adminPlayerCommand.handle(sender, args);
|
||||||
|
|
@ -52,14 +59,25 @@ public class DBDCommand implements TabExecutor {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
private boolean handleJoin(CommandSender sender, String[] args) {
|
private boolean handleJoin(CommandSender sender, String[] args) {
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /dbd join <name> [password] — присоединиться к именованному лобби
|
||||||
|
if (args.length >= 2) {
|
||||||
|
String[] lobbyArgs = java.util.Arrays.copyOfRange(args, 1, args.length);
|
||||||
|
lobbyRegistry.joinLobby(player, lobbyArgs);
|
||||||
|
return true; // всегда true чтобы не показывать usage из plugin.yml
|
||||||
|
}
|
||||||
|
|
||||||
|
// /dbd join — присоединиться к дефолтной сессии
|
||||||
|
if (gameManager.getCurrentSession() == null) {
|
||||||
|
gameManager.createGame();
|
||||||
|
}
|
||||||
if (gameManager.joinGame(player)) {
|
if (gameManager.joinGame(player)) {
|
||||||
player.sendMessage(Component.text("[DBD] Вы присоединились к игре!", NamedTextColor.GREEN));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.join.success"));
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(Component.text("[DBD] Не удалось присоединиться. Возможно, игра уже идёт.", NamedTextColor.RED));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.join.fail"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -69,56 +87,164 @@ public class DBDCommand implements TabExecutor {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
private boolean handleLeave(CommandSender sender) {
|
private boolean handleLeave(CommandSender sender) {
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
GameSession session = gameManager.getPlayerSession(player.getUniqueId());
|
||||||
if (gameManager.leaveGame(player)) {
|
if (gameManager.leaveGame(player)) {
|
||||||
player.sendMessage(Component.text("[DBD] Вы покинули игру.", NamedTextColor.YELLOW));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.leave.success"));
|
||||||
|
// Если лобби опустело — удаляем
|
||||||
|
if (session != null && lobbyRegistry != null) {
|
||||||
|
lobbyRegistry.onPlayerLeftSession(session, player);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(Component.text("[DBD] Вы не в игре.", NamedTextColor.RED));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.join.not-in-game"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// /dbd create <name> [password] [private] — создать лобби
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private boolean handleCreate(CommandSender sender, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.create.usage"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String[] createArgs = java.util.Arrays.copyOfRange(args, 1, args.length);
|
||||||
|
lobbyRegistry.createLobby(player, createArgs);
|
||||||
|
return true; // всегда true чтобы не показывать usage из plugin.yml
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// /dbd list — список лобби
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private boolean handleList(CommandSender sender) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
lobbyRegistry.listLobbies(player);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// /dbd delete <name> — удалить лобби
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private boolean handleDelete(CommandSender sender, String[] args) {
|
||||||
|
if (!(sender instanceof Player player)) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.delete.usage"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
lobbyRegistry.deleteLobby(player, args[1]);
|
||||||
|
return true; // всегда true чтобы не показывать usage из plugin.yml
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Admin commands
|
// Admin commands
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
private boolean handleForceStart(CommandSender sender) {
|
private boolean handleForceStart(CommandSender sender) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var session = gameManager.getCurrentSession();
|
|
||||||
|
// Определяем сессию — сначала по игроку, потом currentSession
|
||||||
|
GameSession session = null;
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
session = gameManager.getPlayerSession(player.getUniqueId());
|
||||||
|
}
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
sender.sendMessage(Component.text("Нет активной игры!", NamedTextColor.RED));
|
session = gameManager.getCurrentSession();
|
||||||
|
}
|
||||||
|
if (session == null) {
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.forcestart.no-game"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (session.getState() == GameState.LOBBY) {
|
if (session.getState() == GameState.LOBBY) {
|
||||||
session.forceStart();
|
session.forceStart();
|
||||||
sender.sendMessage(Component.text("[DBD] Игра форсирована!", NamedTextColor.GREEN));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.forcestart.success"));
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text("Нельзя форсировать сейчас.", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.forcestart.fail"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleStop(CommandSender sender) {
|
private boolean handleStop(CommandSender sender) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Останавливаем текущую сессию игрока, если есть
|
||||||
|
GameSession session = null;
|
||||||
|
if (sender instanceof Player player) {
|
||||||
|
session = gameManager.getPlayerSession(player.getUniqueId());
|
||||||
|
}
|
||||||
|
if (session == null) {
|
||||||
|
// endGame — телепортируем всех участников в лобби
|
||||||
|
teleportAllToLobby(gameManager.getCurrentSession());
|
||||||
gameManager.endGame();
|
gameManager.endGame();
|
||||||
sender.sendMessage(Component.text("[DBD] Игра остановлена.", NamedTextColor.YELLOW));
|
} else {
|
||||||
|
teleportAllToLobby(session);
|
||||||
|
session.stop();
|
||||||
|
}
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.stop.success"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Телепорт всех игроков сессии в лобби со сбросом атрибутов. */
|
||||||
|
private void teleportAllToLobby(GameSession session) {
|
||||||
|
if (session == null) return;
|
||||||
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
Player p = bp.getPlayer();
|
||||||
|
if (p != null && p.isOnline()) {
|
||||||
|
resetPlayerAttributes(p);
|
||||||
|
plugin.getLobbyManager().teleportToLobby(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetPlayerAttributes(Player p) {
|
||||||
|
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
||||||
|
p.getInventory().clear();
|
||||||
|
p.setWalkSpeed(0.2f);
|
||||||
|
p.setFlySpeed(0.1f);
|
||||||
|
p.setSwimming(false);
|
||||||
|
p.setSprinting(false);
|
||||||
|
p.setSneaking(false);
|
||||||
|
p.setHealth(20.0);
|
||||||
|
p.setFoodLevel(20);
|
||||||
|
p.setSaturation(5);
|
||||||
|
try {
|
||||||
|
var scale = p.getAttribute(Attribute.SCALE);
|
||||||
|
if (scale != null) scale.setBaseValue(1.0);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
try {
|
||||||
|
var maxHp = p.getAttribute(Attribute.MAX_HEALTH);
|
||||||
|
if (maxHp != null) maxHp.setBaseValue(20.0);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
try {
|
||||||
|
var speed = p.getAttribute(Attribute.MOVEMENT_SPEED);
|
||||||
|
if (speed != null) speed.setBaseValue(0.1);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean handleReload(CommandSender sender) {
|
private boolean handleReload(CommandSender sender) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
plugin.reloadConfigs();
|
plugin.reloadConfigs();
|
||||||
sender.sendMessage(Component.text("[DBD] Конфиги перезагружены!", NamedTextColor.GREEN));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.reload.success"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,79 +253,64 @@ public class DBDCommand implements TabExecutor {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
private boolean handleMapSave(CommandSender sender, String[] args) {
|
private boolean handleMapSave(CommandSender sender, String[] args) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
sender.sendMessage(Component.text("[DBD] Использование: /dbd save <название>", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.save-usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String name = args[1].toLowerCase();
|
String name = args[1].toLowerCase();
|
||||||
if (plugin.getMapManager().saveMap(name)) {
|
if (plugin.getMapManager().saveMap(name)) {
|
||||||
sender.sendMessage(Component.text("[DBD] Карта «" + name + "» сохранена в plugins/Mdbd/maps/", NamedTextColor.GREEN));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.save-success", name));
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text("[DBD] Не удалось сохранить карту.", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.save-fail"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleMapLoad(CommandSender sender, String[] args) {
|
private boolean handleMapLoad(CommandSender sender, String[] args) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
sender.sendMessage(Component.text("[DBD] Использование: /dbd load <название>", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.load-usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String name = args[1].toLowerCase();
|
String name = args[1].toLowerCase();
|
||||||
if (plugin.getMapManager().loadMap(name)) {
|
if (plugin.getMapManager().loadMap(name)) {
|
||||||
sender.sendMessage(Component.text("[DBD] Карта «" + name + "» загружена!", NamedTextColor.GREEN));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.load-success", name));
|
||||||
sender.sendMessage(Component.text("[DBD] Перезагрузите карту или перезапустите сервер для применения.", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.load-warn"));
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage(Component.text("[DBD] Карта «" + name + "» не найдена.", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("dbd.map.load-not-found", name));
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean handleMapsList(CommandSender sender) {
|
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
List<String> maps = plugin.getMapManager().listMaps();
|
|
||||||
if (maps.isEmpty()) {
|
|
||||||
sender.sendMessage(Component.text("[DBD] Нет сохранённых карт.", NamedTextColor.YELLOW));
|
|
||||||
} else {
|
|
||||||
sender.sendMessage(Component.text("=== Сохранённые карты ===", NamedTextColor.GOLD));
|
|
||||||
for (String m : maps) sender.sendMessage(Component.text(" §f" + m, NamedTextColor.GRAY));
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleLobby(CommandSender sender) {
|
private boolean handleLobby(CommandSender sender) {
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
plugin.getLobbyManager().teleportToLobby(player);
|
plugin.getLobbyManager().teleportToLobby(player);
|
||||||
player.sendMessage(Component.text("[DBD] Телепорт в лобби!", NamedTextColor.GREEN));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.lobby.teleport"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleSetLobby(CommandSender sender) {
|
private boolean handleSetLobby(CommandSender sender) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
plugin.getConfigManager().getLocations().setLobby(player.getLocation());
|
plugin.getConfigManager().getLocations().setLobby(player.getLocation());
|
||||||
plugin.getConfigManager().saveConfig("locations.yml",
|
plugin.getConfigManager().saveConfig("locations.yml",
|
||||||
plugin.getConfigManager().getLocations().getConfig());
|
plugin.getConfigManager().getLocations().getConfig());
|
||||||
player.sendMessage(Component.text("[DBD] Точка лобби установлена!", NamedTextColor.GREEN));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("dbd.setlobby.success"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,18 +318,21 @@ public class DBDCommand implements TabExecutor {
|
||||||
// Usage
|
// Usage
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
private void sendUsage(CommandSender sender) {
|
private void sendUsage(CommandSender sender) {
|
||||||
sender.sendMessage(Component.text("=== DBD Команды ===", NamedTextColor.GOLD));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-header"));
|
||||||
sender.sendMessage(Component.text("/dbd join — присоединиться к игре", NamedTextColor.GRAY));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-join"));
|
||||||
sender.sendMessage(Component.text("/dbd leave — покинуть игру", NamedTextColor.GRAY));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-leave"));
|
||||||
sender.sendMessage(Component.text("/dbd lobby — телепорт в лобби", NamedTextColor.GRAY));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-lobby"));
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-create"));
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-list"));
|
||||||
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-delete"));
|
||||||
if (sender.hasPermission("mdbd.admin")) {
|
if (sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("/dbd admin — управление игроками в матче", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-admin"));
|
||||||
sender.sendMessage(Component.text("/dbd forcestart — форс старт", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-forcestart"));
|
||||||
sender.sendMessage(Component.text("/dbd stop — остановить игру", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-stop"));
|
||||||
sender.sendMessage(Component.text("/dbd reload — перезагрузить конфиги", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-reload"));
|
||||||
sender.sendMessage(Component.text("/dbd setlobby — установить точку лобби", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-setlobby"));
|
||||||
sender.sendMessage(Component.text("/dbd save — сохранить карту", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-save"));
|
||||||
sender.sendMessage(Component.text("/dbd load — загрузить карту", NamedTextColor.YELLOW));
|
sender.sendMessage(plugin.getConfigManager().getMessages().get("usage.dbd-load"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,15 +340,22 @@ public class DBDCommand implements TabExecutor {
|
||||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
@NotNull String label, @NotNull String[] args) {
|
@NotNull String label, @NotNull String[] args) {
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> subs = new ArrayList<>(List.of("join", "leave", "lobby"));
|
List<String> subs = new ArrayList<>(List.of("join", "leave", "lobby", "create", "list", "delete"));
|
||||||
if (sender.hasPermission("mdbd.admin")) {
|
if (sender.hasPermission("mdbd.admin")) {
|
||||||
subs.addAll(List.of("admin", "forcestart", "stop", "reload", "setlobby", "save", "load", "maps"));
|
subs.addAll(List.of("admin", "forcestart", "stop", "reload", "setlobby", "save", "load"));
|
||||||
}
|
}
|
||||||
return subs.stream().filter(s -> s.startsWith(args[0].toLowerCase())).toList();
|
return subs.stream().filter(s -> s.startsWith(args[0].toLowerCase())).toList();
|
||||||
}
|
}
|
||||||
if (args.length >= 2 && args[0].equalsIgnoreCase("admin") && sender.hasPermission("mdbd.admin")) {
|
if (args.length >= 2 && args[0].equalsIgnoreCase("admin") && sender.hasPermission("mdbd.admin")) {
|
||||||
return DBDAdminPlayerCommand.tabComplete(args);
|
return DBDAdminPlayerCommand.tabComplete(args);
|
||||||
}
|
}
|
||||||
|
// Предлагаем имена лобби для join / delete
|
||||||
|
if (args.length == 2 && (args[0].equalsIgnoreCase("join") || args[0].equalsIgnoreCase("delete"))) {
|
||||||
|
return lobbyRegistry.getAllLobbies().stream()
|
||||||
|
.map(dev.loki.world.LobbySession::getName)
|
||||||
|
.filter(s -> s.startsWith(args[1].toLowerCase()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package dev.loki.command;
|
package dev.loki.command;
|
||||||
|
|
||||||
import dev.loki.config.LocationsConfig;
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
import dev.loki.util.SpawnUtil;
|
import dev.loki.util.SpawnUtil;
|
||||||
import dev.loki.util.TextUtil;
|
import dev.loki.util.TextUtil;
|
||||||
import net.kyori.adventure.text.Component;
|
import dev.loki.config.MessagesConfig;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
|
|
@ -52,15 +50,19 @@ public class SetupCommand implements TabExecutor {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MessagesConfig messages() {
|
||||||
|
return plugin.getConfigManager().getMessages();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command,
|
||||||
@NotNull String label, @NotNull String[] args) {
|
@NotNull String label, @NotNull String[] args) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) {
|
if (!sender.hasPermission("mdbd.admin")) {
|
||||||
sender.sendMessage(Component.text("Нет прав!", NamedTextColor.RED));
|
sender.sendMessage(messages().get("cmd.no-permission"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(sender instanceof Player player)) {
|
if (!(sender instanceof Player player)) {
|
||||||
sender.sendMessage(Component.text("Только игроки!", NamedTextColor.RED));
|
sender.sendMessage(messages().get("cmd.players-only"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (args.length == 0) { sendUsage(sender); return true; }
|
if (args.length == 0) { sendUsage(sender); return true; }
|
||||||
|
|
@ -68,7 +70,6 @@ public class SetupCommand implements TabExecutor {
|
||||||
return switch (args[0].toLowerCase()) {
|
return switch (args[0].toLowerCase()) {
|
||||||
case "wand" -> giveWand(player);
|
case "wand" -> giveWand(player);
|
||||||
case "set" -> handleSet(player, args);
|
case "set" -> handleSet(player, args);
|
||||||
case "build" -> buildMap(player);
|
|
||||||
case "clear" -> clearMap(player);
|
case "clear" -> clearMap(player);
|
||||||
case "clear-spawns" -> clearSpawns(player);
|
case "clear-spawns" -> clearSpawns(player);
|
||||||
case "lobby" -> setLobby(player);
|
case "lobby" -> setLobby(player);
|
||||||
|
|
@ -92,7 +93,7 @@ public class SetupCommand implements TabExecutor {
|
||||||
org.bukkit.persistence.PersistentDataType.BYTE, (byte) 1);
|
org.bukkit.persistence.PersistentDataType.BYTE, (byte) 1);
|
||||||
});
|
});
|
||||||
player.getInventory().addItem(wand);
|
player.getInventory().addItem(wand);
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Получен DBD Setup Wand"));
|
player.sendMessage(messages().get("setup.wand-given"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,11 +101,9 @@ public class SetupCommand implements TabExecutor {
|
||||||
|
|
||||||
private boolean handleSet(Player player, String[] args) {
|
private boolean handleSet(Player player, String[] args) {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Использование: /dbdsetup set <тип> [номер]"));
|
player.sendMessage(messages().get("setup.set-usage"));
|
||||||
player.sendMessage(TextUtil.color(
|
player.sendMessage(messages().get("setup.set-types"));
|
||||||
"<#FFD700>Типы: hook, chest, generator, killer-spawn, survivor-spawn, gate"));
|
player.sendMessage(messages().get("setup.set-spawn-info"));
|
||||||
player.sendMessage(TextUtil.color(
|
|
||||||
"<#FFD700>Для спавнов можно указать номер: /dbdsetup set killer-spawn 0"));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,12 +112,12 @@ public class SetupCommand implements TabExecutor {
|
||||||
|
|
||||||
if (type.equals("gate")) {
|
if (type.equals("gate")) {
|
||||||
if (args.length < 3) {
|
if (args.length < 3) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Укажи: /dbdsetup set gate A (или B)"));
|
player.sendMessage(messages().get("setup.gate-usage"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String gateId = args[2].toUpperCase();
|
String gateId = args[2].toUpperCase();
|
||||||
if (!gateId.equals("A") && !gateId.equals("B")) {
|
if (!gateId.equals("A") && !gateId.equals("B")) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Ворота могут быть только A или B"));
|
player.sendMessage(messages().get("setup.gate-invalid"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return placeGate(player, gateId);
|
return placeGate(player, gateId);
|
||||||
|
|
@ -131,7 +130,7 @@ public class SetupCommand implements TabExecutor {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!List.of("hook", "chest", "generator", "killer-spawn", "survivor-spawn").contains(mode)) {
|
if (!List.of("hook", "chest", "generator", "killer-spawn", "survivor-spawn").contains(mode)) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Неизвестный тип: " + type));
|
player.sendMessage(messages().get("setup.unknown-type", type));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,13 +138,22 @@ public class SetupCommand implements TabExecutor {
|
||||||
int spawnIndex = -1;
|
int spawnIndex = -1;
|
||||||
if (args.length >= 3 && (mode.equals("killer-spawn") || mode.equals("survivor-spawn"))) {
|
if (args.length >= 3 && (mode.equals("killer-spawn") || mode.equals("survivor-spawn"))) {
|
||||||
try {
|
try {
|
||||||
spawnIndex = Integer.parseInt(args[2]);
|
int raw = Integer.parseInt(args[2]);
|
||||||
if (spawnIndex < 0) {
|
if (mode.equals("survivor-spawn")) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Номер спавна должен быть >= 0"));
|
if (raw < 1 || raw > 4) {
|
||||||
|
player.sendMessage(messages().get("setup.survivor-spawn-range"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
spawnIndex = raw; // 1-4, храним как есть
|
||||||
|
} else {
|
||||||
|
if (raw < 0) {
|
||||||
|
player.sendMessage(messages().get("setup.killer-spawn-range"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
spawnIndex = raw;
|
||||||
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Номер спавна должен быть числом: " + args[2]));
|
player.sendMessage(messages().get("setup.invalid-number", args[2]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,22 +162,16 @@ public class SetupCommand implements TabExecutor {
|
||||||
SetupData currentData = setupMode.get(uuid);
|
SetupData currentData = setupMode.get(uuid);
|
||||||
if (currentData != null && mode.equals(currentData.mode)) {
|
if (currentData != null && mode.equals(currentData.mode)) {
|
||||||
setupMode.remove(uuid);
|
setupMode.remove(uuid);
|
||||||
player.sendMessage(TextUtil.color("<#FFA500>Режим установки <#FFD700>" + mode + " <#FFA500>выключен"));
|
player.sendMessage(messages().get("setup.mode-off", mode));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupMode.put(uuid, new SetupData(mode, spawnIndex));
|
setupMode.put(uuid, new SetupData(mode, spawnIndex));
|
||||||
String indexMsg = spawnIndex >= 0 ? " #" + spawnIndex : "";
|
String indexMsg = spawnIndex >= 0 ? " #" + spawnIndex : "";
|
||||||
if (mode.contains("spawn")) {
|
if (mode.contains("spawn")) {
|
||||||
player.sendMessage(TextUtil.color(
|
player.sendMessage(messages().get("setup.mode-on-spawn", mode, indexMsg));
|
||||||
"<#00FF00>Режим установки: <#FFD700>" + mode + indexMsg +
|
|
||||||
"\n<#00FF00>Сломай блок чтобы установить спавн (блок восстановится)" +
|
|
||||||
"\n<#FFA500>Повторно введи команду для выключения"));
|
|
||||||
} else {
|
} else {
|
||||||
player.sendMessage(TextUtil.color(
|
player.sendMessage(messages().get("setup.mode-on", mode, indexMsg));
|
||||||
"<#00FF00>Режим установки: <#FFD700>" + mode + indexMsg +
|
|
||||||
"\n<#00FF00>Сломай блок чтобы установить (блок восстановится)" +
|
|
||||||
"\n<#FFA500>Введи команду снова чтобы выключить режим"));
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -179,13 +181,12 @@ public class SetupCommand implements TabExecutor {
|
||||||
Location p2 = pos2.get(player.getUniqueId());
|
Location p2 = pos2.get(player.getUniqueId());
|
||||||
|
|
||||||
if (p1 == null || p2 == null) {
|
if (p1 == null || p2 == null) {
|
||||||
player.sendMessage(TextUtil.color(
|
player.sendMessage(messages().get("setup.gate-need-points"));
|
||||||
"<#FF0000>Сначала выбери оба угла топориком! (ЛКМ=точка 1, ПКМ=точка 2)"));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!p1.getWorld().equals(p2.getWorld())) {
|
if (!p1.getWorld().equals(p2.getWorld())) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Точки должны быть в одном мире!"));
|
player.sendMessage(messages().get("setup.gate-same-world"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,8 +203,7 @@ public class SetupCommand implements TabExecutor {
|
||||||
locations.setGate(gateId, center);
|
locations.setGate(gateId, center);
|
||||||
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
|
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
|
||||||
|
|
||||||
player.sendMessage(TextUtil.color(
|
player.sendMessage(messages().get("setup.gate-placed", gateId, formatLoc(center)));
|
||||||
"<#00FF00>Ворота <#FFFFFF>" + gateId + " <#00FF00>установлены: " + formatLoc(center)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,39 +229,42 @@ public class SetupCommand implements TabExecutor {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "hook" -> {
|
case "hook" -> {
|
||||||
locations.addLocation("hooks", blockLoc.clone().add(0.5, 0, 0.5));
|
locations.addLocation("hooks", blockLoc.clone().add(0.5, 0, 0.5));
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Крюк установлен: " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.hook-placed", formatLoc(loc)));
|
||||||
}
|
}
|
||||||
case "chest" -> {
|
case "chest" -> {
|
||||||
locations.addLocation("chests", blockLoc.clone().add(0.5, 0, 0.5));
|
locations.addLocation("chests", blockLoc.clone().add(0.5, 0, 0.5));
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Сундук добавлен: " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.chest-placed", formatLoc(loc)));
|
||||||
}
|
}
|
||||||
case "generator" -> {
|
case "generator" -> {
|
||||||
locations.addLocation("generators", blockLoc.clone().add(0.5, 0, 0.5));
|
locations.addLocation("generators", blockLoc.clone().add(0.5, 0, 0.5));
|
||||||
plugin.getGeneratorData().addGenerator(blockLoc.clone().add(0.5, 0, 0.5));
|
plugin.getGeneratorData().addGenerator(blockLoc.clone().add(0.5, 0, 0.5));
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Генератор добавлен: " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.generator-placed", formatLoc(loc)));
|
||||||
}
|
}
|
||||||
case "killer-spawn" -> {
|
case "killer-spawn" -> {
|
||||||
|
if (locations.getKillerSpawn() != null) {
|
||||||
|
player.sendMessage(messages().get("setup.killer-spawn-exists"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
locations.setSingleLocation("killer-spawn", loc);
|
locations.setSingleLocation("killer-spawn", loc);
|
||||||
player.sendMessage(TextUtil.color("<#FF4444>Спавн киллера установлен: " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.killer-spawn-placed", formatLoc(loc)));
|
||||||
data.spawnIndex = -1;
|
data.spawnIndex = -1;
|
||||||
}
|
}
|
||||||
case "survivor-spawn" -> {
|
case "survivor-spawn" -> {
|
||||||
int currentCount = locations.getSurvivorSpawns().size();
|
int slot = data.spawnIndex >= 1 ? data.spawnIndex : locations.firstFreeSurvivorSlot();
|
||||||
if (currentCount >= 4) {
|
if (slot == -1) {
|
||||||
player.sendMessage(TextUtil.color("<#FF0000>Достигнут лимит спавнов выживших (4)! Используй /dbdsetup clear-spawns для сброса."));
|
player.sendMessage(messages().get("setup.survivor-spawn-limit"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
saveSpawn(locations, "survivor-spawns", loc, data.spawnIndex);
|
locations.setSurvivorSpawn(slot, loc);
|
||||||
int newCount = locations.getSurvivorSpawns().size();
|
int newCount = locations.getSurvivorSpawns().size();
|
||||||
String label = switch (newCount) {
|
String label = switch (slot) {
|
||||||
case 1 -> "первого";
|
case 1 -> "первого";
|
||||||
case 2 -> "второго";
|
case 2 -> "второго";
|
||||||
case 3 -> "третьего";
|
case 3 -> "третьего";
|
||||||
case 4 -> "четвёртого";
|
case 4 -> "четвёртого";
|
||||||
default -> "#" + newCount;
|
default -> "#" + slot;
|
||||||
};
|
};
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Спавн для <#FFD700>" + label + " <#00FF00>выжившего установлен! (всего: " + newCount + "/4) " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.survivor-spawn-placed", label, newCount, formatLoc(loc)));
|
||||||
// Сбрасываем индекс после использования, чтобы следующие блоки добавлялись
|
|
||||||
data.spawnIndex = -1;
|
data.spawnIndex = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -279,23 +282,12 @@ public class SetupCommand implements TabExecutor {
|
||||||
// НЕ выходим из режима — чтобы можно было установить несколько подряд
|
// НЕ выходим из режима — чтобы можно было установить несколько подряд
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves a spawn location at a specific index, or appends if index is -1.
|
|
||||||
*/
|
|
||||||
private void saveSpawn(LocationsConfig locations, String path, Location loc, int index) {
|
|
||||||
if (index >= 0) {
|
|
||||||
locations.setLocationAtIndex(path, index, loc);
|
|
||||||
} else {
|
|
||||||
locations.addLocation(path, loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Wand position callbacks ────────────────────────────────────────────────
|
// ── Wand position callbacks ────────────────────────────────────────────────
|
||||||
|
|
||||||
public void setWandPos(Player player, int num, Location loc) {
|
public void setWandPos(Player player, int num, Location loc) {
|
||||||
if (num == 1) pos1.put(player.getUniqueId(), loc);
|
if (num == 1) pos1.put(player.getUniqueId(), loc);
|
||||||
else pos2.put(player.getUniqueId(), loc);
|
else pos2.put(player.getUniqueId(), loc);
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>[Wand] Точка " + num + ": " + formatLoc(loc)));
|
player.sendMessage(messages().get("setup.wand-point", num, formatLoc(loc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInSetupMode(UUID uuid) {
|
public boolean isInSetupMode(UUID uuid) {
|
||||||
|
|
@ -315,26 +307,20 @@ public class SetupCommand implements TabExecutor {
|
||||||
return data != null ? data.spawnIndex : -1;
|
return data != null ? data.spawnIndex : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Build / clear / lobby ─────────────────────────────────────────────────
|
// ── Clear / lobby ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private boolean buildMap(Player player) {
|
|
||||||
plugin.getMapBuilder().buildMap(plugin.getConfigManager().getLocations());
|
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Карта построена!"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean clearMap(Player player) {
|
private boolean clearMap(Player player) {
|
||||||
plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations());
|
plugin.getMapBuilder().clearMap(plugin.getConfigManager().getLocations());
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Карта очищена!"));
|
player.sendMessage(messages().get("setup.map-cleared"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean clearSpawns(Player player) {
|
private boolean clearSpawns(Player player) {
|
||||||
var locations = plugin.getConfigManager().getLocations();
|
var locations = plugin.getConfigManager().getLocations();
|
||||||
locations.clearLocationList("killer-spawn");
|
locations.clearLocationList("killer-spawn");
|
||||||
locations.clearLocationList("survivor-spawns");
|
locations.clearSurvivorSpawns();
|
||||||
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
|
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Все спавны (киллер + выжившие) очищены!"));
|
player.sendMessage(messages().get("setup.spawns-cleared"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,23 +328,23 @@ public class SetupCommand implements TabExecutor {
|
||||||
plugin.getConfigManager().getLocations().setLobby(player.getLocation());
|
plugin.getConfigManager().getLocations().setLobby(player.getLocation());
|
||||||
plugin.getConfigManager().saveConfig("locations.yml",
|
plugin.getConfigManager().saveConfig("locations.yml",
|
||||||
plugin.getConfigManager().getLocations().getConfig());
|
plugin.getConfigManager().getLocations().getConfig());
|
||||||
player.sendMessage(TextUtil.color("<#00FF00>Лобби установлено: " + formatLoc(player.getLocation())));
|
player.sendMessage(messages().get("setup.lobby-set", formatLoc(player.getLocation())));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Usage / helpers ───────────────────────────────────────────────────────
|
// ── Usage / helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void sendUsage(CommandSender sender) {
|
private void sendUsage(CommandSender sender) {
|
||||||
sender.sendMessage(TextUtil.color("<#FF6B00>=== DBD Setup ==="));
|
sender.sendMessage(messages().get("usage.setup-header"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup wand — Получить ванд (ЛКМ/ПКМ для ворот)"));
|
sender.sendMessage(messages().get("usage.setup-wand"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set hook — Установить крюк (ломай блок)"));
|
sender.sendMessage(messages().get("usage.setup-hook"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set chest — Установить сундук"));
|
sender.sendMessage(messages().get("usage.setup-chest"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set generator — Установить генератор"));
|
sender.sendMessage(messages().get("usage.setup-generator"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set killer-spawn — Спавн киллера (только 1)"));
|
sender.sendMessage(messages().get("usage.setup-killer-spawn"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set survivor-spawn — Спавн выжившего (до 4)"));
|
sender.sendMessage(messages().get("usage.setup-survivor-spawn"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup set gate A|B — Установить ворота (из точек ванда)"));
|
sender.sendMessage(messages().get("usage.setup-gate"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup clear-spawns — Очистить все спавны"));
|
sender.sendMessage(messages().get("usage.setup-clear-spawns"));
|
||||||
sender.sendMessage(TextUtil.color("<#FFD700>/dbdsetup lobby — Установить точку лобби"));
|
sender.sendMessage(messages().get("usage.setup-lobby"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatLoc(Location loc) {
|
private String formatLoc(Location loc) {
|
||||||
|
|
@ -371,7 +357,7 @@ public class SetupCommand implements TabExecutor {
|
||||||
@NotNull String label, @NotNull String[] args) {
|
@NotNull String label, @NotNull String[] args) {
|
||||||
if (!sender.hasPermission("mdbd.admin")) return List.of();
|
if (!sender.hasPermission("mdbd.admin")) return List.of();
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
return List.of("wand", "set", "build", "clear", "clear-spawns", "lobby").stream()
|
return List.of("wand", "set", "clear", "clear-spawns", "lobby").stream()
|
||||||
.filter(s -> s.startsWith(args[0].toLowerCase())).toList();
|
.filter(s -> s.startsWith(args[0].toLowerCase())).toList();
|
||||||
}
|
}
|
||||||
if (args.length == 2 && args[0].equalsIgnoreCase("set")) {
|
if (args.length == 2 && args[0].equalsIgnoreCase("set")) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public class GameConfig {
|
||||||
setDefault("generators.kick-cooldown-seconds", 30);
|
setDefault("generators.kick-cooldown-seconds", 30);
|
||||||
setDefault("generators.kick-charge-seconds", 3);
|
setDefault("generators.kick-charge-seconds", 3);
|
||||||
setDefault("generators.interaction-range", 3.0);
|
setDefault("generators.interaction-range", 3.0);
|
||||||
|
setDefault("generators.minigame", "gui");
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
setDefault("hooks.stage1-seconds", 60);
|
setDefault("hooks.stage1-seconds", 60);
|
||||||
|
|
@ -97,6 +98,9 @@ public class GameConfig {
|
||||||
setDefault("scoreboard.color-healing", "GREEN");
|
setDefault("scoreboard.color-healing", "GREEN");
|
||||||
setDefault("scoreboard.color-hooked", "DARK_RED");
|
setDefault("scoreboard.color-hooked", "DARK_RED");
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
setDefault("debug-mode", false);
|
||||||
|
|
||||||
// Colors for UI
|
// Colors for UI
|
||||||
setDefault("colors.progress-bar-complete", "�FF00");
|
setDefault("colors.progress-bar-complete", "�FF00");
|
||||||
setDefault("colors.progress-bar-incomplete", "&#FF0000");
|
setDefault("colors.progress-bar-incomplete", "&#FF0000");
|
||||||
|
|
@ -116,9 +120,9 @@ public class GameConfig {
|
||||||
setDefault("holograms.generator.enabled", true);
|
setDefault("holograms.generator.enabled", true);
|
||||||
setDefault("holograms.generator.height-offset", -1.0);
|
setDefault("holograms.generator.height-offset", -1.0);
|
||||||
setDefault("holograms.hook.enabled", true);
|
setDefault("holograms.hook.enabled", true);
|
||||||
setDefault("holograms.hook.height-offset", 0.0);
|
setDefault("holograms.hook.height-offset", -1.0);
|
||||||
setDefault("holograms.chest.enabled", true);
|
setDefault("holograms.chest.enabled", true);
|
||||||
setDefault("holograms.chest.height-offset", 0.0);
|
setDefault("holograms.chest.height-offset", -1.0);
|
||||||
|
|
||||||
// Skill check
|
// Skill check
|
||||||
setDefault("skillcheck.chance", 0.005);
|
setDefault("skillcheck.chance", 0.005);
|
||||||
|
|
@ -171,6 +175,7 @@ public class GameConfig {
|
||||||
public int getKickCooldownSeconds() { return config.getInt("generators.kick-cooldown-seconds", 30); }
|
public int getKickCooldownSeconds() { return config.getInt("generators.kick-cooldown-seconds", 30); }
|
||||||
public int getKickChargeSeconds() { return config.getInt("generators.kick-charge-seconds", 3); }
|
public int getKickChargeSeconds() { return config.getInt("generators.kick-charge-seconds", 3); }
|
||||||
public double getGeneratorInteractionRange() { return config.getDouble("generators.interaction-range", 3.0); }
|
public double getGeneratorInteractionRange() { return config.getDouble("generators.interaction-range", 3.0); }
|
||||||
|
public String getGeneratorMinigame() { return config.getString("generators.minigame", "gui"); }
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
@ -286,5 +291,7 @@ public class GameConfig {
|
||||||
public String getSkillCheckFailBlock() { return config.getString("skillcheck.fail-block", "RED_STAINED_GLASS_PANE"); }
|
public String getSkillCheckFailBlock() { return config.getString("skillcheck.fail-block", "RED_STAINED_GLASS_PANE"); }
|
||||||
public String getSkillCheckNeutralBlock() { return config.getString("skillcheck.neutral-block", "GRAY_STAINED_GLASS_PANE"); }
|
public String getSkillCheckNeutralBlock() { return config.getString("skillcheck.neutral-block", "GRAY_STAINED_GLASS_PANE"); }
|
||||||
|
|
||||||
|
public boolean isDebugMode() { return config.getBoolean("debug-mode", false); }
|
||||||
|
|
||||||
public FileConfiguration getConfig() { return config; }
|
public FileConfiguration getConfig() { return config; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class LocationsConfig {
|
public class LocationsConfig {
|
||||||
private final FileConfiguration config;
|
private final FileConfiguration config;
|
||||||
|
|
@ -45,9 +46,7 @@ public class LocationsConfig {
|
||||||
if (!config.contains("killer-spawn") || config.getList("killer-spawn") == null) {
|
if (!config.contains("killer-spawn") || config.getList("killer-spawn") == null) {
|
||||||
config.set("killer-spawn", new ArrayList<>());
|
config.set("killer-spawn", new ArrayList<>());
|
||||||
}
|
}
|
||||||
if (!config.contains("survivor-spawns") || config.getList("survivor-spawns") == null) {
|
// Survivor spawns are stored as individual keys survivor-spawn-1..4 (no list)
|
||||||
config.set("survivor-spawns", new ArrayList<>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Location getLobby() {
|
public Location getLobby() {
|
||||||
|
|
@ -103,8 +102,39 @@ public class LocationsConfig {
|
||||||
return spawns.isEmpty() ? null : spawns.get(0);
|
return spawns.isEmpty() ? null : spawns.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Read survivor spawns from individual keys survivor-spawn-1..4 (no list, avoids YAML bugs). */
|
||||||
public List<Location> getSurvivorSpawns() {
|
public List<Location> getSurvivorSpawns() {
|
||||||
return loadLocationList("survivor-spawns");
|
List<Location> result = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= 4; i++) {
|
||||||
|
String str = config.getString("survivor-spawn-" + i);
|
||||||
|
if (str != null && !str.isEmpty()) {
|
||||||
|
Location loc = parseLocation(str);
|
||||||
|
if (loc != null) result.add(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set a specific survivor spawn slot (index 1-4). */
|
||||||
|
public void setSurvivorSpawn(int index, Location loc) {
|
||||||
|
if (index < 1 || index > 4) throw new IllegalArgumentException("Index must be 1-4");
|
||||||
|
config.set("survivor-spawn-" + index, locationToString(loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find first empty survivor spawn slot (1-4), or -1 if all full. */
|
||||||
|
public int firstFreeSurvivorSlot() {
|
||||||
|
for (int i = 1; i <= 4; i++) {
|
||||||
|
String str = config.getString("survivor-spawn-" + i);
|
||||||
|
if (str == null || str.isEmpty()) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear all survivor spawn slots. */
|
||||||
|
public void clearSurvivorSpawns() {
|
||||||
|
for (int i = 1; i <= 4; i++) {
|
||||||
|
config.set("survivor-spawn-" + i, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Location> loadLocationList(String path) {
|
private List<Location> loadLocationList(String path) {
|
||||||
|
|
@ -217,7 +247,7 @@ public class LocationsConfig {
|
||||||
|
|
||||||
private String locationToString(Location loc) {
|
private String locationToString(Location loc) {
|
||||||
// Use | as separator instead of , to avoid issues with negative coordinates in YAML
|
// Use | as separator instead of , to avoid issues with negative coordinates in YAML
|
||||||
return String.format("%s|%.2f|%.2f|%.2f|%.1f|%.1f",
|
return String.format(Locale.US, "%s|%.2f|%.2f|%.2f|%.1f|%.1f",
|
||||||
loc.getWorld().getName(),
|
loc.getWorld().getName(),
|
||||||
loc.getX(), loc.getY(), loc.getZ(),
|
loc.getX(), loc.getY(), loc.getZ(),
|
||||||
loc.getYaw(), loc.getPitch());
|
loc.getYaw(), loc.getPitch());
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,69 @@
|
||||||
package dev.loki.config;
|
package dev.loki.config;
|
||||||
|
|
||||||
|
import dev.loki.util.TextUtil;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class MessagesConfig {
|
public class MessagesConfig {
|
||||||
private final FileConfiguration config;
|
private final org.bukkit.configuration.file.FileConfiguration config;
|
||||||
private final MiniMessage miniMessage;
|
private final MiniMessage miniMessage;
|
||||||
|
|
||||||
public MessagesConfig(FileConfiguration config) {
|
public MessagesConfig(org.bukkit.configuration.file.FileConfiguration config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.miniMessage = MiniMessage.miniMessage();
|
this.miniMessage = MiniMessage.miniMessage();
|
||||||
setDefaults();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDefaults() {
|
|
||||||
// Prefix (orange)
|
|
||||||
setDefault("prefix", "&#FF6B00&l[DBD]&r ");
|
|
||||||
|
|
||||||
// Game messages
|
|
||||||
setDefault("game.start", "�FF00Игра началась! Бегите от Киллера!");
|
|
||||||
setDefault("game.end.victory-escape", "&#FFD700Выжившие сбежали!");
|
|
||||||
setDefault("game.end.victory-killer", "&#DC143CКиллер победил!");
|
|
||||||
setDefault("game.waiting", "&#FFFF00Ожидание игроков... (%s/%s)");
|
|
||||||
setDefault("game.countdown", "&#FFA500До начала: %s сек");
|
|
||||||
|
|
||||||
// Generator messages
|
|
||||||
setDefault("generator.repair.start", "�CED1Вы начали чинить генератор...");
|
|
||||||
setDefault("generator.repair.stop", "WCEEBВы остановились.");
|
|
||||||
setDefault("generator.repair.complete", "�FF00✓ Генератор активирован!");
|
|
||||||
setDefault("generator.skillcheck.success", "�FF00Отлично!");
|
|
||||||
setDefault("generator.skillcheck.fail", "&#FF0000✗ Провал!");
|
|
||||||
setDefault("generator.explosion", "&#FF4500⚠ Взрыв генератора!");
|
|
||||||
setDefault("generator.regression", "&#FF6347Генератор откатывается...");
|
|
||||||
setDefault("generator.progress", "&#FFD700Генераторы: %s/%s");
|
|
||||||
|
|
||||||
// Killer messages
|
|
||||||
setDefault("killer.terror-near", "B0000Сердце колотится...");
|
|
||||||
setDefault("killer.chase.start", "&#FF0000⚠ Киллер преследует вас!");
|
|
||||||
setDefault("killer.chase.stop", "�FF00Вы ушли от погони.");
|
|
||||||
setDefault("killer.hit", "&#DC143CВы ранены!");
|
|
||||||
setDefault("killer.down", "B0000Вы в нокдауне!");
|
|
||||||
setDefault("killer.pickup", "󃕐Киллер поднял вас...");
|
|
||||||
setDefault("killer.hook", "B0082Вас повесили на крюк!");
|
|
||||||
|
|
||||||
// Survivor messages
|
|
||||||
setDefault("survivor.heal.start", "�FA9AВы лечите...");
|
|
||||||
setDefault("survivor.heal.complete", "�FF00Вылечено!");
|
|
||||||
setDefault("survivor.heal.interrupt", "&#FFD700Лечение прервано!");
|
|
||||||
setDefault("survivor.sprint.exhausted", "&#FF8C00Вы устали...");
|
|
||||||
|
|
||||||
// Hook messages
|
|
||||||
setDefault("hook.stage1", "&#FFFF00Крюк: Фаза 1");
|
|
||||||
setDefault("hook.stage2", "&#FFA500Крюк: Фаза 2 — БОРИТЕСЬ!");
|
|
||||||
setDefault("hook.struggle.success", "�FF00Успех!");
|
|
||||||
setDefault("hook.struggle.fail", "&#FF0000Провал!");
|
|
||||||
setDefault("hook.rescue.start", "�CED1Спасение...");
|
|
||||||
setDefault("hook.rescue.complete", "�FF00Спасены!");
|
|
||||||
setDefault("hook.sacrifice", "B0000☠ Вы жертвованы...");
|
|
||||||
|
|
||||||
// Item messages
|
|
||||||
setDefault("item.found", "&#FFD700Найдено: %s");
|
|
||||||
setDefault("item.medkit.use", "�FA9AИспользуете аптечку...");
|
|
||||||
|
|
||||||
// Endgame
|
|
||||||
setDefault("endgame.gates.powered", "�FF00Ворота запитаны!");
|
|
||||||
setDefault("endgame.gates.opening", "&#FFD700Открываете ворота...");
|
|
||||||
setDefault("endgame.hatch.open", "B0082Люк открыт!");
|
|
||||||
setDefault("endgame.escape", "�FF00✓ ПОБЕГ!");
|
|
||||||
|
|
||||||
// Error messages
|
|
||||||
setDefault("error.no-permission", "&#FF0000Нет прав!");
|
|
||||||
setDefault("error.not-in-game", "&#FF6347Вы не в игре!");
|
|
||||||
setDefault("error.already-in-game", "&#FF6347Вы уже в игре!");
|
|
||||||
setDefault("error.game-full", "&#FF6347Игра заполнена!");
|
|
||||||
setDefault("error.no-game", "&#FF6347Нет активной игры!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDefault(String path, String value) {
|
|
||||||
if (!config.contains(path)) {
|
|
||||||
config.set(path, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a message by path. Replaces %prefix%, %admin-prefix%, %setup-prefix%
|
||||||
|
* with configured values.
|
||||||
|
*/
|
||||||
public Component get(String path, Object... args) {
|
public Component get(String path, Object... args) {
|
||||||
String raw = config.getString(path, "&cMissing: " + path);
|
String raw = config.getString(path, "&cMissing: " + path);
|
||||||
String withPrefix = config.getString("prefix", "") + raw;
|
return deserialize(raw, args);
|
||||||
|
|
||||||
// Replace &#RRGGBB with MiniMessage <#RRGGBB>
|
|
||||||
String mini = withPrefix.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>");
|
|
||||||
|
|
||||||
// Replace %s with arguments
|
|
||||||
if (args.length > 0) {
|
|
||||||
for (Object arg : args) {
|
|
||||||
mini = mini.replaceFirst("%s", String.valueOf(arg));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return miniMessage.deserialize(mini);
|
/**
|
||||||
}
|
* Get a message as a plain string by path (no MiniMessage processing).
|
||||||
|
*/
|
||||||
public Component getRaw(String path, Object... args) {
|
public String getString(String path, Object... args) {
|
||||||
String raw = config.getString(path, "&cMissing: " + path);
|
String raw = config.getString(path, "&cMissing: " + path);
|
||||||
String mini = raw.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>");
|
raw = replacePrefixes(raw);
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
for (Object arg : args) {
|
raw = String.format(Locale.US, raw, args);
|
||||||
mini = mini.replaceFirst("%s", String.valueOf(arg));
|
|
||||||
}
|
}
|
||||||
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Component deserialize(String raw, Object... args) {
|
||||||
|
raw = replacePrefixes(raw);
|
||||||
|
String mini = raw;
|
||||||
|
if (args.length > 0) {
|
||||||
|
mini = String.format(Locale.US, raw, args);
|
||||||
|
}
|
||||||
|
mini = TextUtil.toMiniMessage(mini);
|
||||||
return miniMessage.deserialize(mini);
|
return miniMessage.deserialize(mini);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileConfiguration getConfig() {
|
private String replacePrefixes(String raw) {
|
||||||
|
String prefix = config.getString("prefix", "");
|
||||||
|
String adminPrefix = config.getString("admin-prefix", prefix);
|
||||||
|
String setupPrefix = config.getString("setup-prefix", prefix);
|
||||||
|
// Replace admin-prefix first (it may contain %prefix%)
|
||||||
|
if (raw.contains("%admin-prefix%")) {
|
||||||
|
String resolvedAdmin = adminPrefix.replace("%prefix%", prefix);
|
||||||
|
raw = raw.replace("%admin-prefix%", resolvedAdmin);
|
||||||
|
}
|
||||||
|
if (raw.contains("%setup-prefix%")) {
|
||||||
|
String resolvedSetup = setupPrefix.replace("%prefix%", prefix);
|
||||||
|
raw = raw.replace("%setup-prefix%", resolvedSetup);
|
||||||
|
}
|
||||||
|
raw = raw.replace("%prefix%", prefix);
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.bukkit.configuration.file.FileConfiguration getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,16 +56,17 @@ public class GameSession {
|
||||||
tickTask.cancel();
|
tickTask.cancel();
|
||||||
tickTask = null;
|
tickTask = null;
|
||||||
}
|
}
|
||||||
if (currentPhase != null) {
|
Phase phase = currentPhase;
|
||||||
currentPhase.exit(this);
|
currentPhase = null; // сбрасываем ДО exit() чтобы избежать рекурсии
|
||||||
currentPhase = null;
|
if (phase != null) {
|
||||||
|
phase.exit(this);
|
||||||
}
|
}
|
||||||
players.clear();
|
players.clear();
|
||||||
eventBus.clear();
|
eventBus.clear();
|
||||||
plugin.getLogger().info("Game session stopped");
|
plugin.getLogger().info("Game session stopped");
|
||||||
if (onStopped != null) {
|
if (onStopped != null) {
|
||||||
Runnable cb = onStopped;
|
Runnable cb = onStopped;
|
||||||
onStopped = null; // предотвращаем повторный вызов
|
onStopped = null;
|
||||||
Bukkit.getScheduler().runTask(plugin, cb);
|
Bukkit.getScheduler().runTask(plugin, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,18 @@ public class ExitGate {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Place a HOPPER block at this gate's location for player interaction. */
|
||||||
|
public void spawnGateBlock() {
|
||||||
|
if (location.getWorld() == null) return;
|
||||||
|
location.getBlock().setType(org.bukkit.Material.HOPPER, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove the interaction block (e.g. when gate opens or on cleanup). */
|
||||||
|
public void removeGateBlock() {
|
||||||
|
if (location.getWorld() == null) return;
|
||||||
|
location.getBlock().setType(org.bukkit.Material.AIR, false);
|
||||||
|
}
|
||||||
|
|
||||||
public GateState getState() {
|
public GateState getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +102,7 @@ public class ExitGate {
|
||||||
if (progress >= 1.0) {
|
if (progress >= 1.0) {
|
||||||
progress = 1.0;
|
progress = 1.0;
|
||||||
state = GateState.OPEN;
|
state = GateState.OPEN;
|
||||||
|
removeGateBlock();
|
||||||
playOpenedSound();
|
playOpenedSound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +116,7 @@ public class ExitGate {
|
||||||
public void restoreProgress() { progress = savedProgress; }
|
public void restoreProgress() { progress = savedProgress; }
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
removeGateBlock();
|
||||||
this.state = GateState.CLOSED;
|
this.state = GateState.CLOSED;
|
||||||
this.progress = 0.0;
|
this.progress = 0.0;
|
||||||
this.savedProgress = 0.0;
|
this.savedProgress = 0.0;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package dev.loki.gate;
|
package dev.loki.gate;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.config.MessagesConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
|
|
@ -19,11 +21,13 @@ public class GateOpening {
|
||||||
private final GateManager gateManager;
|
private final GateManager gateManager;
|
||||||
private final int openTicks;
|
private final int openTicks;
|
||||||
private final Map<UUID, OpeningProgress> openingPlayers;
|
private final Map<UUID, OpeningProgress> openingPlayers;
|
||||||
|
private final MessagesConfig messages;
|
||||||
|
|
||||||
public GateOpening(GateManager gateManager, GameConfig config) {
|
public GateOpening(GateManager gateManager, GameConfig config) {
|
||||||
this.gateManager = gateManager;
|
this.gateManager = gateManager;
|
||||||
this.openTicks = config.getConfig().getInt("endgame.gate-open-seconds", 20) * 20;
|
this.openTicks = config.getConfig().getInt("endgame.gate-open-seconds", 20) * 20;
|
||||||
this.openingPlayers = new HashMap<>();
|
this.openingPlayers = new HashMap<>();
|
||||||
|
this.messages = mdbd.getInstance().getConfigManager().getMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean tryStartOpening(Survivor survivor, org.bukkit.plugin.Plugin plugin) {
|
public boolean tryStartOpening(Survivor survivor, org.bukkit.plugin.Plugin plugin) {
|
||||||
|
|
@ -34,17 +38,17 @@ public class GateOpening {
|
||||||
|
|
||||||
ExitGate gate = gateManager.getGateAt(player.getLocation(), INTERACTION_RADIUS);
|
ExitGate gate = gateManager.getGateAt(player.getLocation(), INTERACTION_RADIUS);
|
||||||
if (gate == null) {
|
if (gate == null) {
|
||||||
player.sendMessage("§cНет ворот поблизости!");
|
player.sendMessage(messages.get("chest.door-nearby"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gate.isPowered()) {
|
if (!gate.isPowered()) {
|
||||||
player.sendMessage("§cВорота обесточены!");
|
player.sendMessage(messages.get("chest.door-no-power"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gate.isClosed() && !gate.isOpening()) {
|
if (!gate.isClosed() && !gate.isOpening()) {
|
||||||
player.sendMessage("§aВорота уже открыты!");
|
player.sendMessage(messages.get("chest.door-already"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +61,7 @@ public class GateOpening {
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
Player player = survivor.getPlayer();
|
Player player = survivor.getPlayer();
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.sendMessage("§7Открытие прервано!");
|
player.sendMessage(messages.get("chest.door-interrupted"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +78,7 @@ public class GateOpening {
|
||||||
gate.restoreProgress();
|
gate.restoreProgress();
|
||||||
|
|
||||||
openingPlayers.put(survivor.getUuid(), new OpeningProgress(gate, 0));
|
openingPlayers.put(survivor.getUuid(), new OpeningProgress(gate, 0));
|
||||||
player.sendMessage("§aОткрываем ворота... (зажмите ПКМ)");
|
player.sendMessage(messages.get("chest.door-open"));
|
||||||
|
|
||||||
new OpeningTask(survivor, gate, plugin).runTaskTimer(plugin, 0L, 1L);
|
new OpeningTask(survivor, gate, plugin).runTaskTimer(plugin, 0L, 1L);
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +89,7 @@ public class GateOpening {
|
||||||
World world = loc.getWorld();
|
World world = loc.getWorld();
|
||||||
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.sendMessage("§aВорота открыты!");
|
player.sendMessage(messages.get("chest.door-opened"));
|
||||||
if (world != null) {
|
if (world != null) {
|
||||||
world.playSound(loc, Sound.BLOCK_IRON_DOOR_OPEN, 1.0f, 1.0f);
|
world.playSound(loc, Sound.BLOCK_IRON_DOOR_OPEN, 1.0f, 1.0f);
|
||||||
// Big particle burst when opened
|
// Big particle burst when opened
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package dev.loki.hatch;
|
package dev.loki.hatch;
|
||||||
|
|
||||||
|
import dev.loki.config.MessagesConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
|
|
@ -11,9 +13,11 @@ public class HatchEscape {
|
||||||
private static final double ESCAPE_RADIUS = 1.5;
|
private static final double ESCAPE_RADIUS = 1.5;
|
||||||
|
|
||||||
private final Hatch hatch;
|
private final Hatch hatch;
|
||||||
|
private final MessagesConfig messages;
|
||||||
|
|
||||||
public HatchEscape(Hatch hatch) {
|
public HatchEscape(Hatch hatch) {
|
||||||
this.hatch = hatch;
|
this.hatch = hatch;
|
||||||
|
this.messages = mdbd.getInstance().getConfigManager().getMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean tryEscape(Survivor survivor) {
|
public boolean tryEscape(Survivor survivor) {
|
||||||
|
|
@ -25,7 +29,7 @@ public class HatchEscape {
|
||||||
if (player == null) return false;
|
if (player == null) return false;
|
||||||
|
|
||||||
if (!isAtHatch(player.getLocation())) {
|
if (!isAtHatch(player.getLocation())) {
|
||||||
player.sendMessage("§cПодойдите ближе к люку!");
|
player.sendMessage(messages.get("hatch.too-far"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,12 +51,12 @@ public class HatchEscape {
|
||||||
if (player == null) return false;
|
if (player == null) return false;
|
||||||
|
|
||||||
if (hatch.isHidden()) {
|
if (hatch.isHidden()) {
|
||||||
player.sendMessage("§7Люк скрыт...");
|
player.sendMessage(messages.get("hatch.hidden"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hatch.isClosed()) {
|
if (hatch.isClosed()) {
|
||||||
player.sendMessage("§cЛюк закрыт!");
|
player.sendMessage(messages.get("hatch.closed"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +76,10 @@ public class HatchEscape {
|
||||||
|
|
||||||
player.getWorld().playSound(player.getLocation(),
|
player.getWorld().playSound(player.getLocation(),
|
||||||
Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f);
|
Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f);
|
||||||
player.sendTitle("§a§lПОБЕГ!", "§7Вы сбежали через люк!", 10, 60, 10);
|
player.sendTitle(
|
||||||
|
messages.getString("hatch.escape-title"),
|
||||||
|
messages.getString("hatch.escape-subtitle"),
|
||||||
|
10, 60, 10);
|
||||||
|
|
||||||
survivor.setState(PlayerState.ESCAPED);
|
survivor.setState(PlayerState.ESCAPED);
|
||||||
|
|
||||||
|
|
@ -83,7 +90,7 @@ public class HatchEscape {
|
||||||
Player player = survivor.getPlayer();
|
Player player = survivor.getPlayer();
|
||||||
if (player == null) return;
|
if (player == null) return;
|
||||||
|
|
||||||
player.sendMessage("§a+5000 Очков — Побег через люк!");
|
player.sendMessage(messages.get("hatch.reward"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forceOpenForEscape() {
|
public void forceOpenForEscape() {
|
||||||
|
|
@ -102,7 +109,7 @@ public class HatchEscape {
|
||||||
|
|
||||||
for (Player player : world.getPlayers()) {
|
for (Player player : world.getPlayers()) {
|
||||||
if (player.getLocation().distance(hatchLoc) < 30) {
|
if (player.getLocation().distance(hatchLoc) < 30) {
|
||||||
player.sendMessage("§e§lЛюк открыт!");
|
player.sendMessage(messages.get("hatch.open-notify"));
|
||||||
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f);
|
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 2.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.loki.hook;
|
package dev.loki.hook;
|
||||||
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.util.TextUtil;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.ArmorStand;
|
import org.bukkit.entity.ArmorStand;
|
||||||
import org.bukkit.entity.Display;
|
import org.bukkit.entity.Display;
|
||||||
|
|
@ -71,12 +72,12 @@ public class HookHologram {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location holoLoc = loc.clone().add(0.5, 2.5 + heightOffset, 0.5);
|
Location holoLoc = loc.clone().add(0.5, 2.0 + heightOffset, 0.5);
|
||||||
try {
|
try {
|
||||||
if (useTextDisplay) {
|
if (useTextDisplay) {
|
||||||
TextDisplay display = loc.getWorld().spawn(holoLoc, TextDisplay.class, d -> {
|
TextDisplay display = loc.getWorld().spawn(holoLoc, TextDisplay.class, d -> {
|
||||||
d.setBillboard(Display.Billboard.VERTICAL);
|
d.setBillboard(Display.Billboard.VERTICAL);
|
||||||
d.setSeeThrough(true);
|
d.setSeeThrough(false);
|
||||||
d.setShadowed(true);
|
d.setShadowed(true);
|
||||||
d.setDefaultBackground(false);
|
d.setDefaultBackground(false);
|
||||||
d.setPersistent(false);
|
d.setPersistent(false);
|
||||||
|
|
@ -127,10 +128,10 @@ public class HookHologram {
|
||||||
String text = "§c⚓ Крюк §f#" + visibleIndex + "/" + total + "\n§7" + status;
|
String text = "§c⚓ Крюк §f#" + visibleIndex + "/" + total + "\n§7" + status;
|
||||||
|
|
||||||
double heightOffset = plugin.getConfigManager().getGame().getHookHologramHeightOffset();
|
double heightOffset = plugin.getConfigManager().getGame().getHookHologramHeightOffset();
|
||||||
Location base = hook.getLocation().clone().add(0.5, 2.5 + heightOffset, 0.5);
|
Location base = hook.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5);
|
||||||
|
|
||||||
if (display instanceof TextDisplay td) {
|
if (display instanceof TextDisplay td) {
|
||||||
td.text(net.kyori.adventure.text.Component.text(text));
|
td.text(TextUtil.color(text));
|
||||||
td.teleport(base);
|
td.teleport(base);
|
||||||
} else if (display instanceof ArmorStand as) {
|
} else if (display instanceof ArmorStand as) {
|
||||||
as.setCustomName(text);
|
as.setCustomName(text);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package dev.loki.hook;
|
package dev.loki.hook;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.scheduler.BukkitRunnable;
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
@ -71,7 +71,8 @@ public class HookRecovery {
|
||||||
|
|
||||||
elapsed++;
|
elapsed++;
|
||||||
double pct = (double) elapsed / durationTicks * 100;
|
double pct = (double) elapsed / durationTicks * 100;
|
||||||
p.sendActionBar(Component.text("§6Попытка вырваться: " + (int) pct + "%"));
|
p.sendActionBar(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
|
.get("hook.struggle.actionbar", (int) pct));
|
||||||
|
|
||||||
if (elapsed >= durationTicks) {
|
if (elapsed >= durationTicks) {
|
||||||
resolveAttempt(survivor, hook);
|
resolveAttempt(survivor, hook);
|
||||||
|
|
@ -83,8 +84,8 @@ public class HookRecovery {
|
||||||
activeTasks.put(uuid, task);
|
activeTasks.put(uuid, task);
|
||||||
Player p = survivor.getPlayer();
|
Player p = survivor.getPlayer();
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
p.sendMessage(Component.text(
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
"[DBD] Держите Shift чтобы вырваться с крюка...", NamedTextColor.YELLOW));
|
.get("hook.struggle.hold-shift"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +135,8 @@ public class HookRecovery {
|
||||||
failedAttempts.remove(uuid);
|
failedAttempts.remove(uuid);
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.sendActionBar(Component.empty());
|
p.sendActionBar(Component.empty());
|
||||||
p.sendMessage(Component.text("[DBD] Вы вырвались с крюка!", NamedTextColor.GREEN));
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
|
.get("hook.struggle.success"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Failure
|
// Failure
|
||||||
|
|
@ -142,9 +144,8 @@ public class HookRecovery {
|
||||||
failedAttempts.put(uuid, newFailures);
|
failedAttempts.put(uuid, newFailures);
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.sendActionBar(Component.empty());
|
p.sendActionBar(Component.empty());
|
||||||
p.sendMessage(Component.text(
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
"[DBD] Попытка не удалась! (" + newFailures + "/" + MAX_ATTEMPTS + ")",
|
.get("hook.struggle.fail", newFailures, MAX_ATTEMPTS));
|
||||||
NamedTextColor.RED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newFailures >= MAX_ATTEMPTS) {
|
if (newFailures >= MAX_ATTEMPTS) {
|
||||||
|
|
@ -152,8 +153,8 @@ public class HookRecovery {
|
||||||
hook.advanceToSacrifice();
|
hook.advanceToSacrifice();
|
||||||
failedAttempts.remove(uuid);
|
failedAttempts.remove(uuid);
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.sendMessage(Component.text(
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
"[DBD] Сущность поглотила вас!", NamedTextColor.DARK_RED));
|
.get("hook.struggle.sacrifice"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.ui.ActionBar;
|
import dev.loki.ui.ActionBar;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
|
|
@ -57,7 +56,7 @@ public class HookRescue {
|
||||||
session.setTask(task);
|
session.setTask(task);
|
||||||
|
|
||||||
if (player.isOnline()) {
|
if (player.isOnline()) {
|
||||||
player.sendMessage(Component.text("[DBD] Спасаем... держите Shift!", NamedTextColor.YELLOW));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("hook.rescue.start"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +77,7 @@ public class HookRescue {
|
||||||
double rescueRange = config.getHookInteractionRange();
|
double rescueRange = config.getHookInteractionRange();
|
||||||
if (player.getLocation().distance(hook.getLocation()) > rescueRange) {
|
if (player.getLocation().distance(hook.getLocation()) > rescueRange) {
|
||||||
cancelRescue(uuid);
|
cancelRescue(uuid);
|
||||||
player.sendMessage(Component.text("[DBD] Слишком далеко! Спасение прервано.", NamedTextColor.RED));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("hook.rescue.too-far"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,14 +110,14 @@ public class HookRescue {
|
||||||
victimPlayer.addPotionEffect(
|
victimPlayer.addPotionEffect(
|
||||||
new PotionEffect(PotionEffectType.SPEED, 5 * 20, 0, false, true));
|
new PotionEffect(PotionEffectType.SPEED, 5 * 20, 0, false, true));
|
||||||
|
|
||||||
victimPlayer.sendMessage(Component.text("[DBD] Вас спасли! Бегите!", NamedTextColor.GREEN));
|
victimPlayer.sendMessage(plugin.getConfigManager().getMessages().get("hook.rescue.victim-saved"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Player rescuerPlayer = rescuer.getPlayer();
|
Player rescuerPlayer = rescuer.getPlayer();
|
||||||
if (rescuerPlayer != null && rescuerPlayer.isOnline()) {
|
if (rescuerPlayer != null && rescuerPlayer.isOnline()) {
|
||||||
rescuerPlayer.sendActionBar(Component.text("[DBD] Союзник спасён!", NamedTextColor.GREEN));
|
rescuerPlayer.sendActionBar(plugin.getConfigManager().getMessages().get("hook.rescue.rescuer-actionbar"));
|
||||||
rescuerPlayer.sendMessage(Component.text("[DBD] Спасение завершено!", NamedTextColor.GREEN));
|
rescuerPlayer.sendMessage(plugin.getConfigManager().getMessages().get("hook.rescue.rescuer-done"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onRescueComplete != null && victim != null) {
|
if (onRescueComplete != null && victim != null) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package dev.loki.hook;
|
package dev.loki.hook;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -30,6 +30,8 @@ public class HookSabotage {
|
||||||
private final Map<UUID, BukkitTask> sabotageCharging = new HashMap<>();
|
private final Map<UUID, BukkitTask> sabotageCharging = new HashMap<>();
|
||||||
/** hookUUID → recovery task */
|
/** hookUUID → recovery task */
|
||||||
private final Map<UUID, BukkitTask> recoveryTasks = new HashMap<>();
|
private final Map<UUID, BukkitTask> recoveryTasks = new HashMap<>();
|
||||||
|
/** Called when sabotage completes successfully */
|
||||||
|
private java.util.function.Consumer<UUID> onSabotageComplete;
|
||||||
|
|
||||||
public HookSabotage(Plugin plugin, GameConfig config, HookManager hookManager) {
|
public HookSabotage(Plugin plugin, GameConfig config, HookManager hookManager) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
@ -75,8 +77,8 @@ public class HookSabotage {
|
||||||
}
|
}
|
||||||
elapsed++;
|
elapsed++;
|
||||||
double pct = (double) elapsed / chargeTicks * 100;
|
double pct = (double) elapsed / chargeTicks * 100;
|
||||||
player.sendActionBar(Component.text(
|
player.sendActionBar(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
"§e⚙ Саботаж крюка: " + (int) pct + "%"));
|
.get("hook.sabotage.actionbar", (int) pct));
|
||||||
|
|
||||||
if (elapsed >= chargeTicks) {
|
if (elapsed >= chargeTicks) {
|
||||||
executeSabotage(survivor, hook);
|
executeSabotage(survivor, hook);
|
||||||
|
|
@ -87,8 +89,8 @@ public class HookSabotage {
|
||||||
|
|
||||||
sabotageCharging.put(uuid, task);
|
sabotageCharging.put(uuid, task);
|
||||||
if (p.isOnline()) {
|
if (p.isOnline()) {
|
||||||
p.sendMessage(Component.text("[DBD] Держите Shift для саботажа крюка...",
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
NamedTextColor.YELLOW));
|
.get("hook.sabotage.start"));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +109,10 @@ public class HookSabotage {
|
||||||
return sabotageCharging.containsKey(survivorUuid);
|
return sabotageCharging.containsKey(survivorUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnSabotageComplete(java.util.function.Consumer<UUID> callback) {
|
||||||
|
this.onSabotageComplete = callback;
|
||||||
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
sabotageCharging.values().forEach(BukkitTask::cancel);
|
sabotageCharging.values().forEach(BukkitTask::cancel);
|
||||||
sabotageCharging.clear();
|
sabotageCharging.clear();
|
||||||
|
|
@ -126,12 +132,17 @@ public class HookSabotage {
|
||||||
Player p = survivor.getPlayer();
|
Player p = survivor.getPlayer();
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.sendActionBar(Component.empty());
|
p.sendActionBar(Component.empty());
|
||||||
p.sendMessage(Component.text("[DBD] Крюк сломан!", NamedTextColor.GREEN));
|
p.sendMessage(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
|
.get("hook.sabotage.done"));
|
||||||
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f);
|
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.broadcast(Component.text("[DBD] ⚙ Крюк был сломан выжившим!",
|
Bukkit.broadcast(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
NamedTextColor.YELLOW));
|
.get("hook.sabotage.broadcast"));
|
||||||
|
|
||||||
|
if (onSabotageComplete != null) {
|
||||||
|
onSabotageComplete.accept(survivor.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
// Запускаем восстановление
|
// Запускаем восстановление
|
||||||
int recoveryTicks = config.getSabotageRecoverySeconds() * 20;
|
int recoveryTicks = config.getSabotageRecoverySeconds() * 20;
|
||||||
|
|
@ -140,8 +151,8 @@ public class HookSabotage {
|
||||||
public void run() {
|
public void run() {
|
||||||
hook.setDisabled(false);
|
hook.setDisabled(false);
|
||||||
recoveryTasks.remove(hook.getId());
|
recoveryTasks.remove(hook.getId());
|
||||||
Bukkit.broadcast(Component.text("[DBD] 🔧 Крюк восстановлен.",
|
Bukkit.broadcast(((mdbd) plugin).getConfigManager().getMessages()
|
||||||
NamedTextColor.GRAY));
|
.get("hook.sabotage.recovery"));
|
||||||
}
|
}
|
||||||
}.runTaskLater(plugin, recoveryTicks);
|
}.runTaskLater(plugin, recoveryTicks);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.ui.TitleUI;
|
import dev.loki.ui.TitleUI;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.title.Title;
|
import net.kyori.adventure.title.Title;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -33,7 +32,7 @@ public class HookSacrifice {
|
||||||
// Convert to SPECTATOR mode
|
// Convert to SPECTATOR mode
|
||||||
if (player != null && player.isOnline()) {
|
if (player != null && player.isOnline()) {
|
||||||
player.setGameMode(org.bukkit.GameMode.SPECTATOR);
|
player.setGameMode(org.bukkit.GameMode.SPECTATOR);
|
||||||
player.sendMessage(Component.text("[DBD] Вы были принесены в жертву. Наблюдайте за матчем.", NamedTextColor.DARK_RED));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("hook.sacrifice.message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
announceSacrifice(survivor);
|
announceSacrifice(survivor);
|
||||||
|
|
@ -47,8 +46,8 @@ public class HookSacrifice {
|
||||||
);
|
);
|
||||||
|
|
||||||
Title title = Title.title(
|
Title title = Title.title(
|
||||||
Component.text("☠").color(NamedTextColor.DARK_RED),
|
plugin.getConfigManager().getMessages().get("hook.sacrifice.title"),
|
||||||
Component.text("You have been sacrificed").color(NamedTextColor.RED),
|
plugin.getConfigManager().getMessages().get("hook.sacrifice.subtitle"),
|
||||||
times
|
times
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -64,10 +63,8 @@ public class HookSacrifice {
|
||||||
name = player.getName();
|
name = player.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String finalName = name;
|
|
||||||
plugin.getServer().broadcast(
|
plugin.getServer().broadcast(
|
||||||
Component.text(finalName + " has been sacrificed to the Entity")
|
plugin.getConfigManager().getMessages().get("hook.sacrifice.broadcast", name)
|
||||||
.color(NamedTextColor.DARK_RED)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,14 @@ public class HookStruggle {
|
||||||
private final int struggleIntervalTicks;
|
private final int struggleIntervalTicks;
|
||||||
private static final int STRUGGLE_WINDOW_TICKS = 20;
|
private static final int STRUGGLE_WINDOW_TICKS = 20;
|
||||||
|
|
||||||
public HookStruggle(mdbd plugin, TitleUI titleUI) {
|
|
||||||
this(plugin, titleUI, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HookStruggle(mdbd plugin, TitleUI titleUI, GameConfig config) {
|
public HookStruggle(mdbd plugin, TitleUI titleUI, GameConfig config) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.titleUI = titleUI;
|
this.titleUI = titleUI;
|
||||||
this.struggleTasks = new HashMap<>();
|
this.struggleTasks = new HashMap<>();
|
||||||
this.struggleProgress = new HashMap<>();
|
this.struggleProgress = new HashMap<>();
|
||||||
// Читаем из конфига: struggle-interval в секундах → тики
|
|
||||||
this.struggleIntervalTicks = (config != null)
|
this.struggleIntervalTicks = (config != null)
|
||||||
? config.getStruggleInterval() * 20
|
? config.getStruggleInterval() * 20
|
||||||
: 40; // дефолт 2 сек
|
: 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startStruggle(Survivor survivor, Hook hook) {
|
public void startStruggle(Survivor survivor, Hook hook) {
|
||||||
|
|
@ -86,7 +81,7 @@ public class HookStruggle {
|
||||||
|
|
||||||
private void failStruggle(Survivor survivor) {
|
private void failStruggle(Survivor survivor) {
|
||||||
UUID uuid = survivor.getUuid();
|
UUID uuid = survivor.getUuid();
|
||||||
struggleProgress.put(uuid, struggleProgress.getOrDefault(uuid, 0) - 2);
|
struggleProgress.put(uuid, Math.max(0, struggleProgress.getOrDefault(uuid, 0) - 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sacrifice(Survivor survivor) {
|
public void sacrifice(Survivor survivor) {
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,21 @@ public final class DBDItems {
|
||||||
|
|
||||||
/** Медкит — лечит одно состояние здоровья. */
|
/** Медкит — лечит одно состояние здоровья. */
|
||||||
public static ItemStack medkit(int charges) {
|
public static ItemStack medkit(int charges) {
|
||||||
return build(
|
var item = new ItemStack(Material.PAPER);
|
||||||
Material.PAPER, CMD_MEDKIT,
|
item.editMeta(meta -> {
|
||||||
"<#00FF7F>Медкит",
|
meta.setCustomModelData(CMD_MEDKIT);
|
||||||
charges, ItemType.MEDKIT.getDefaultCharges(),
|
meta.displayName(TextUtil.color("<#00FF7F>Медкит"));
|
||||||
List.of(
|
meta.lore(java.util.List.of(
|
||||||
"<gray>Лечит одно состояние здоровья.",
|
TextUtil.color("<gray>Лечит одно состояние здоровья."),
|
||||||
"<yellow>ПКМ <gray>— начать самолечение"
|
TextUtil.color("<yellow>ПКМ <gray>— начать самолечение"),
|
||||||
),
|
Component.empty(),
|
||||||
"MEDKIT"
|
TextUtil.color("<#FFD700>Заряды: <#00FF00>████████ <white>1/1")
|
||||||
);
|
));
|
||||||
|
var pdc = meta.getPersistentDataContainer();
|
||||||
|
pdc.set(KEY_TYPE, PersistentDataType.STRING, "MEDKIT");
|
||||||
|
pdc.set(KEY_CHARGES, PersistentDataType.INTEGER, charges);
|
||||||
|
});
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemStack medkit() { return medkit(ItemType.MEDKIT.getDefaultCharges()); }
|
public static ItemStack medkit() { return medkit(ItemType.MEDKIT.getDefaultCharges()); }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.loki.item;
|
package dev.loki.item;
|
||||||
|
|
||||||
|
import dev.loki.mdbd;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -66,7 +67,7 @@ public class ItemMedkit implements BaseItem {
|
||||||
@Override
|
@Override
|
||||||
public void onDeplete(Player player) {
|
public void onDeplete(Player player) {
|
||||||
player.getInventory().setItemInMainHand(null);
|
player.getInventory().setItemInMainHand(null);
|
||||||
player.sendMessage("§cYour medkit has been depleted!");
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.depleted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package dev.loki.item;
|
package dev.loki.item;
|
||||||
|
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.BasePlayer;
|
import dev.loki.player.BasePlayer;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -72,8 +72,7 @@ public class ItemUseListener implements Listener {
|
||||||
if ("MEDKIT".equals(type)) {
|
if ("MEDKIT".equals(type)) {
|
||||||
// Медкит: нужен Shift (sneaking) + стоять на месте
|
// Медкит: нужен Shift (sneaking) + стоять на месте
|
||||||
if (!player.isSneaking()) {
|
if (!player.isSneaking()) {
|
||||||
player.sendMessage(Component.text(
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.need-shift"));
|
||||||
"[DBD] Зажмите Shift и стойте на месте для лечения.", NamedTextColor.YELLOW));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
useMedkit(player, (Survivor) bp, hand);
|
useMedkit(player, (Survivor) bp, hand);
|
||||||
|
|
@ -92,18 +91,18 @@ public class ItemUseListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (survivor.getState() != PlayerState.INJURED) {
|
if (survivor.getState() != PlayerState.INJURED) {
|
||||||
player.sendMessage(Component.text("[DBD] Вы не ранены!", NamedTextColor.RED));
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.not-injured"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int charges = DBDItems.getCharges(item);
|
int charges = DBDItems.getCharges(item);
|
||||||
if (charges <= 0) {
|
if (charges <= 0) {
|
||||||
player.sendMessage(Component.text("[DBD] Медкит пуст!", NamedTextColor.RED));
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.empty"));
|
||||||
player.getInventory().setItemInMainHand(null);
|
player.getInventory().setItemInMainHand(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.sendMessage(Component.text("[DBD] Самолечение... держите Shift и не двигайтесь.", NamedTextColor.GREEN));
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.start"));
|
||||||
|
|
||||||
// Запоминаем позицию для проверки движения
|
// Запоминаем позицию для проверки движения
|
||||||
final Location startLoc = player.getLocation().clone();
|
final Location startLoc = player.getLocation().clone();
|
||||||
|
|
@ -136,9 +135,8 @@ public class ItemUseListener implements Listener {
|
||||||
int filled = (int) (8.0 * ticks / healTicks);
|
int filled = (int) (8.0 * ticks / healTicks);
|
||||||
int empty = 8 - filled;
|
int empty = 8 - filled;
|
||||||
int pct = (int) (100.0 * ticks / healTicks);
|
int pct = (int) (100.0 * ticks / healTicks);
|
||||||
player.sendActionBar(Component.text(
|
String barStr = "█".repeat(filled) + "░".repeat(empty);
|
||||||
"⚕ " + "█".repeat(filled) + "░".repeat(empty) + " " + pct + "%",
|
player.sendActionBar(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.actionbar", barStr, pct));
|
||||||
NamedTextColor.GREEN));
|
|
||||||
|
|
||||||
if (ticks % 20 == 0) {
|
if (ticks % 20 == 0) {
|
||||||
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_HURT, 0.15f, 1.8f);
|
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_HURT, 0.15f, 1.8f);
|
||||||
|
|
@ -146,7 +144,7 @@ public class ItemUseListener implements Listener {
|
||||||
|
|
||||||
if (ticks >= healTicks) {
|
if (ticks >= healTicks) {
|
||||||
survivor.setState(PlayerState.HEALTHY);
|
survivor.setState(PlayerState.HEALTHY);
|
||||||
player.sendMessage(Component.text("[DBD] ✔ Вы вылечились!", NamedTextColor.GREEN));
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.done"));
|
||||||
player.sendActionBar(Component.empty());
|
player.sendActionBar(Component.empty());
|
||||||
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.6f, 1.5f);
|
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.6f, 1.5f);
|
||||||
// Полностью убираем медкит (один раз использован)
|
// Полностью убираем медкит (один раз использован)
|
||||||
|
|
@ -165,7 +163,7 @@ public class ItemUseListener implements Listener {
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.cancel();
|
task.cancel();
|
||||||
player.sendActionBar(Component.empty());
|
player.sendActionBar(Component.empty());
|
||||||
player.sendMessage(Component.text("Лечение прервано.", NamedTextColor.YELLOW));
|
player.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.medkit.cancelled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package dev.loki.killer;
|
package dev.loki.killer;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.config.MessagesConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.objective.Generator;
|
import dev.loki.objective.Generator;
|
||||||
import dev.loki.objective.GeneratorManager;
|
import dev.loki.objective.GeneratorManager;
|
||||||
import dev.loki.player.Killer;
|
import dev.loki.player.Killer;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
@ -29,6 +30,7 @@ public class GeneratorKick {
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
private final GameConfig config;
|
private final GameConfig config;
|
||||||
private final GeneratorManager generatorManager;
|
private final GeneratorManager generatorManager;
|
||||||
|
private final MessagesConfig messages;
|
||||||
|
|
||||||
/** UUID → ongoing charging task */
|
/** UUID → ongoing charging task */
|
||||||
private final Map<UUID, BukkitTask> chargingTasks = new HashMap<>();
|
private final Map<UUID, BukkitTask> chargingTasks = new HashMap<>();
|
||||||
|
|
@ -41,6 +43,7 @@ public class GeneratorKick {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.generatorManager = generatorManager;
|
this.generatorManager = generatorManager;
|
||||||
|
this.messages = mdbd.getInstance().getConfigManager().getMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
@ -55,7 +58,7 @@ public class GeneratorKick {
|
||||||
Player p = killer.getPlayer();
|
Player p = killer.getPlayer();
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
int secsLeft = (int) Math.ceil(globalCooldownTicks / 20.0);
|
int secsLeft = (int) Math.ceil(globalCooldownTicks / 20.0);
|
||||||
p.sendMessage(Component.text("[DBD] Кулдаун удара: " + secsLeft + " сек", NamedTextColor.RED));
|
p.sendMessage(messages.get("generator.kicked.cooldown", secsLeft));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +69,12 @@ public class GeneratorKick {
|
||||||
Generator gen = generatorManager.getNearestGenerator(p.getLocation(),
|
Generator gen = generatorManager.getNearestGenerator(p.getLocation(),
|
||||||
config.getGeneratorInteractionRange());
|
config.getGeneratorInteractionRange());
|
||||||
if (gen == null || !gen.canBeRepaired()) return false;
|
if (gen == null || !gen.canBeRepaired()) return false;
|
||||||
|
if (gen.getProgress() <= 0) {
|
||||||
|
if (p.isOnline()) {
|
||||||
|
p.sendMessage(messages.get("generator.kicked.already-broken"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int chargeTicks = config.getKickChargeSeconds() * 20;
|
int chargeTicks = config.getKickChargeSeconds() * 20;
|
||||||
|
|
||||||
|
|
@ -92,7 +101,7 @@ public class GeneratorKick {
|
||||||
|
|
||||||
elapsed++;
|
elapsed++;
|
||||||
double pct = (double) elapsed / chargeTicks * 100;
|
double pct = (double) elapsed / chargeTicks * 100;
|
||||||
player.sendActionBar(Component.text("§c⚡ Разрушение: " + (int) pct + "%"));
|
player.sendActionBar(messages.get("generator.kicked.actionbar", (int) pct));
|
||||||
|
|
||||||
if (elapsed >= chargeTicks) {
|
if (elapsed >= chargeTicks) {
|
||||||
executeKick(killer, gen);
|
executeKick(killer, gen);
|
||||||
|
|
@ -102,7 +111,7 @@ public class GeneratorKick {
|
||||||
}.runTaskTimer(plugin, 1L, 1L);
|
}.runTaskTimer(plugin, 1L, 1L);
|
||||||
|
|
||||||
chargingTasks.put(uuid, task);
|
chargingTasks.put(uuid, task);
|
||||||
if (p.isOnline()) p.sendMessage(Component.text("[DBD] Держите Shift для разрушения генератора...", NamedTextColor.RED));
|
if (p.isOnline()) p.sendMessage(messages.get("generator.kicked.begin"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,12 +155,12 @@ public class GeneratorKick {
|
||||||
Player p = killer.getPlayer();
|
Player p = killer.getPlayer();
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.sendActionBar(Component.empty());
|
p.sendActionBar(Component.empty());
|
||||||
p.sendMessage(Component.text("[DBD] Генератор поврежден! -" + (int) regPct + "% прогресса", NamedTextColor.RED));
|
p.sendMessage(messages.get("generator.kicked.success", (int) regPct));
|
||||||
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.6f, 1.3f);
|
p.getWorld().playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.6f, 1.3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify all players
|
// Notify all players
|
||||||
Bukkit.broadcast(Component.text("[DBD] ⚡ Маньяк разрушил генератор! -" + (int)regPct + "%", NamedTextColor.DARK_RED));
|
Bukkit.broadcast(messages.get("generator.kicked.broadcast", (int) regPct));
|
||||||
|
|
||||||
// Unblock generator after kick-duration (separate from global cooldown)
|
// Unblock generator after kick-duration (separate from global cooldown)
|
||||||
int unblockTicks = config.getKickDuration() * 20;
|
int unblockTicks = config.getKickDuration() * 20;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,9 @@ public class PlayerInteractListener implements Listener {
|
||||||
public void setOnHealSneak(BiConsumer<Player, Player> h) { this.onHealSneak = h; }
|
public void setOnHealSneak(BiConsumer<Player, Player> h) { this.onHealSneak = h; }
|
||||||
private BiConsumer<Player, Boolean> onCarriedShift;
|
private BiConsumer<Player, Boolean> onCarriedShift;
|
||||||
public void setOnCarriedShift(BiConsumer<Player, Boolean> h) { this.onCarriedShift = h; }
|
public void setOnCarriedShift(BiConsumer<Player, Boolean> h) { this.onCarriedShift = h; }
|
||||||
|
/** Called when a survivor sneaks while a text skill check is in progress */
|
||||||
|
private BiConsumer<Player, Boolean> onGeneratorSkillCheckSneak;
|
||||||
|
public void setOnGeneratorSkillCheckSneak(BiConsumer<Player, Boolean> h) { this.onGeneratorSkillCheckSneak = h; }
|
||||||
|
|
||||||
public void setGeneratorManager(GeneratorManager manager) {
|
public void setGeneratorManager(GeneratorManager manager) {
|
||||||
this.generatorManager = manager;
|
this.generatorManager = manager;
|
||||||
|
|
@ -71,22 +74,28 @@ public class PlayerInteractListener implements Listener {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
if (!gameManager.isInGame(player.getUniqueId())) return;
|
if (!gameManager.isInGame(player.getUniqueId())) return;
|
||||||
|
|
||||||
// For survivors: trigger generator repair when sneaking
|
|
||||||
if (event.isSneaking()) {
|
|
||||||
var session = gameManager.getPlayerSession(player.getUniqueId());
|
var session = gameManager.getPlayerSession(player.getUniqueId());
|
||||||
if (session != null) {
|
if (session == null) return;
|
||||||
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
||||||
if (bp != null && !bp.isKiller()) {
|
if (bp == null) return;
|
||||||
// #11: Carried survivor shift-spam to escape
|
|
||||||
if (bp.getState() == PlayerState.CARRIED && onCarriedShift != null) {
|
if (!bp.isKiller()) {
|
||||||
onCarriedShift.accept(player, true);
|
// Carried survivor shift-spam
|
||||||
|
if (bp.getState() == PlayerState.CARRIED) {
|
||||||
|
if (onCarriedShift != null) onCarriedShift.accept(player, event.isSneaking());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text skill check: complete on sneak release
|
||||||
|
if (!event.isSneaking() && onGeneratorSkillCheckSneak != null) {
|
||||||
|
onGeneratorSkillCheckSneak.accept(player, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Only HEALTHY or INJURED survivors can repair/heal
|
// Only HEALTHY or INJURED survivors can repair/heal
|
||||||
PlayerState st = bp.getState();
|
PlayerState st = bp.getState();
|
||||||
if (st != PlayerState.HEALTHY && st != PlayerState.INJURED) return;
|
if (st != PlayerState.HEALTHY && st != PlayerState.INJURED) return;
|
||||||
|
|
||||||
|
if (event.isSneaking()) {
|
||||||
// Generator repair takes priority over healing
|
// Generator repair takes priority over healing
|
||||||
if (tryGeneratorInteract(player)) return;
|
if (tryGeneratorInteract(player)) return;
|
||||||
|
|
||||||
|
|
@ -99,19 +108,6 @@ public class PlayerInteractListener implements Listener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// When un-sneaking
|
|
||||||
var session = gameManager.getPlayerSession(player.getUniqueId());
|
|
||||||
if (session != null) {
|
|
||||||
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
|
||||||
if (bp != null && !bp.isKiller()) {
|
|
||||||
if (bp.getState() == PlayerState.CARRIED && onCarriedShift != null) {
|
|
||||||
onCarriedShift.accept(player, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onKillerSneak != null) {
|
if (onKillerSneak != null) {
|
||||||
onKillerSneak.accept(player, event.isSneaking());
|
onKillerSneak.accept(player, event.isSneaking());
|
||||||
|
|
@ -246,14 +242,21 @@ public class PlayerInteractListener implements Listener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gate opening via proximity for survivors — works on air clicks too
|
||||||
|
if (!bp.isKiller() && onHatchInteract != null) {
|
||||||
|
onHatchInteract.accept(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.getClickedBlock() == null) return;
|
if (event.getClickedBlock() == null) return;
|
||||||
|
|
||||||
String type = event.getClickedBlock().getType().toString();
|
String type = event.getClickedBlock().getType().toString();
|
||||||
|
|
||||||
// Chest interaction - cancel vanilla GUI and use plugin chest system
|
// Chest interaction - cancel vanilla GUI and use plugin chest system
|
||||||
if (type.contains("CHEST") && onChestInteract != null && !bp.isKiller()) {
|
if (type.contains("CHEST")) {
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
if (onChestInteract != null && !bp.isKiller()) {
|
||||||
onChestInteract.accept(player, true);
|
onChestInteract.accept(player, true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Hook rescue: if survivor clicks on a hook block, attempt rescue
|
// Hook rescue: if survivor clicks on a hook block, attempt rescue
|
||||||
|
|
@ -261,7 +264,6 @@ public class PlayerInteractListener implements Listener {
|
||||||
onRescueAttempt.accept(player, true);
|
onRescueAttempt.accept(player, true);
|
||||||
if (type.contains("IRON_BARS") || type.contains("IRON")) event.setCancelled(true);
|
if (type.contains("IRON_BARS") || type.contains("IRON")) event.setCancelled(true);
|
||||||
}
|
}
|
||||||
if ((type.contains("HOPPER") || type.contains("PORTAL")) && onHatchInteract != null && !bp.isKiller()) onHatchInteract.accept(player, true);
|
|
||||||
// Cancel interaction with hook blocks to prevent block manipulation
|
// Cancel interaction with hook blocks to prevent block manipulation
|
||||||
if (type.contains("IRON_BARS") || type.contains("IRON")) event.setCancelled(true);
|
if (type.contains("IRON_BARS") || type.contains("IRON")) event.setCancelled(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ package dev.loki.listener;
|
||||||
|
|
||||||
import dev.loki.core.GameManager;
|
import dev.loki.core.GameManager;
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.BasePlayer;
|
import dev.loki.player.BasePlayer;
|
||||||
import dev.loki.player.Killer;
|
import dev.loki.player.Killer;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
|
import dev.loki.world.LobbyRegistry;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
|
|
@ -20,10 +21,12 @@ import java.util.UUID;
|
||||||
public class PlayerQuitListener implements Listener {
|
public class PlayerQuitListener implements Listener {
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
private final GameManager gameManager;
|
private final GameManager gameManager;
|
||||||
|
private final LobbyRegistry lobbyRegistry;
|
||||||
|
|
||||||
public PlayerQuitListener(Plugin plugin, GameManager gameManager) {
|
public PlayerQuitListener(Plugin plugin, GameManager gameManager, LobbyRegistry lobbyRegistry) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.gameManager = gameManager;
|
this.gameManager = gameManager;
|
||||||
|
this.lobbyRegistry = lobbyRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
|
@ -49,6 +52,11 @@ public class PlayerQuitListener implements Listener {
|
||||||
session.removePlayer(uuid);
|
session.removePlayer(uuid);
|
||||||
gameManager.leaveGame(player);
|
gameManager.leaveGame(player);
|
||||||
|
|
||||||
|
// Если лобби опустело — удаляем его
|
||||||
|
if (lobbyRegistry != null) {
|
||||||
|
lobbyRegistry.onPlayerLeftSession(session, player);
|
||||||
|
}
|
||||||
|
|
||||||
// Гарантируем чистый инвентарь и scale при следующем входе
|
// Гарантируем чистый инвентарь и scale при следующем входе
|
||||||
player.getInventory().clear();
|
player.getInventory().clear();
|
||||||
player.setSwimming(false);
|
player.setSwimming(false);
|
||||||
|
|
@ -59,9 +67,7 @@ public class PlayerQuitListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleKillerDisconnect(GameSession session, Killer killer) {
|
private void handleKillerDisconnect(GameSession session, Killer killer) {
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(mdbd.getInstance().getConfigManager().getMessages().get("quit.killer-disconnect"));
|
||||||
"[DBD] Киллер покинул игру. Игра завершена.",
|
|
||||||
NamedTextColor.RED));
|
|
||||||
session.stop();
|
session.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,8 +99,7 @@ public class PlayerQuitListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Только участникам сессии
|
// Только участникам сессии
|
||||||
Component msg = Component.text(
|
Component msg = mdbd.getInstance().getConfigManager().getMessages().get("quit.survivor-left", alive);
|
||||||
"[DBD] Выживший покинул игру. Осталось живых: " + alive, NamedTextColor.YELLOW);
|
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null && p.isOnline()) p.sendMessage(msg);
|
if (p != null && p.isOnline()) p.sendMessage(msg);
|
||||||
|
|
@ -113,9 +118,9 @@ public class PlayerQuitListener implements Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (livingSurvivors == 0) {
|
if (livingSurvivors == 0) {
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(mdbd.getInstance().getConfigManager().getMessages().get("game.all-dead"));
|
||||||
"[DBD] Все выжившие мертвы. Киллер победил!",
|
// Останавливаем сессию сразу при выходе последнего выжившего
|
||||||
NamedTextColor.RED));
|
session.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package dev.loki.listener;
|
package dev.loki.listener;
|
||||||
|
|
||||||
import dev.loki.objective.SkillCheckInventory;
|
import dev.loki.minigames.generator.GuiRouletteMinigame;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
|
|
@ -11,37 +11,29 @@ import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
|
||||||
* Listens for clicks on skill check inventory GUI.
|
|
||||||
* Blocks all item manipulation — only clicking is allowed.
|
|
||||||
*/
|
|
||||||
public class SkillCheckListener implements Listener {
|
public class SkillCheckListener implements Listener {
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
private final SkillCheckInventory skillCheckGUI;
|
private final GuiRouletteMinigame minigame;
|
||||||
|
|
||||||
public SkillCheckListener(Plugin plugin, SkillCheckInventory skillCheckGUI) {
|
public SkillCheckListener(Plugin plugin, GuiRouletteMinigame minigame) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.skillCheckGUI = skillCheckGUI;
|
this.minigame = minigame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClick(InventoryClickEvent event) {
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
// Check if this is a skill check inventory (title contains "Skill Check")
|
|
||||||
String title = event.getView().getTitle();
|
String title = event.getView().getTitle();
|
||||||
if (!title.contains("Skill Check")) return;
|
if (!title.contains("Skill Check")) return;
|
||||||
|
|
||||||
// Block ALL item manipulation: shift-click, number keys, double-click, etc.
|
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
||||||
if (!(event.getWhoClicked() instanceof Player player)) return;
|
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||||
|
|
||||||
UUID playerId = player.getUniqueId();
|
minigame.handleClick(player.getUniqueId(), event.getRawSlot());
|
||||||
skillCheckGUI.handleClick(event, playerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryDrag(InventoryDragEvent event) {
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
// Block dragging items out of the skill check inventory
|
|
||||||
String title = event.getView().getTitle();
|
String title = event.getView().getTitle();
|
||||||
if (!title.contains("Skill Check")) return;
|
if (!title.contains("Skill Check")) return;
|
||||||
event.setCancelled(true);
|
event.setCancelled(true);
|
||||||
|
|
@ -49,11 +41,10 @@ public class SkillCheckListener implements Listener {
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onInventoryClose(InventoryCloseEvent event) {
|
public void onInventoryClose(InventoryCloseEvent event) {
|
||||||
// Check if this is a skill check inventory (title contains "Skill Check")
|
|
||||||
String title = event.getView().getTitle();
|
String title = event.getView().getTitle();
|
||||||
if (!title.contains("Skill Check")) return;
|
if (!title.contains("Skill Check")) return;
|
||||||
|
|
||||||
skillCheckGUI.onInventoryClose(event);
|
minigame.handleClose(event.getPlayer().getUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void register() {
|
public void register() {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import dev.loki.map.MapBuilder;
|
||||||
import dev.loki.map.MapManager;
|
import dev.loki.map.MapManager;
|
||||||
import dev.loki.map.TemplateManager;
|
import dev.loki.map.TemplateManager;
|
||||||
import dev.loki.world.LobbyManager;
|
import dev.loki.world.LobbyManager;
|
||||||
|
import dev.loki.world.LobbyRegistry;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public final class mdbd extends JavaPlugin {
|
public final class mdbd extends JavaPlugin {
|
||||||
|
|
@ -24,6 +25,7 @@ public final class mdbd extends JavaPlugin {
|
||||||
private MapManager mapManager;
|
private MapManager mapManager;
|
||||||
private MapBuilder mapBuilder;
|
private MapBuilder mapBuilder;
|
||||||
private GeneratorDataManager generatorData;
|
private GeneratorDataManager generatorData;
|
||||||
|
private LobbyRegistry lobbyRegistry;
|
||||||
private DBDCommand dbdCommand;
|
private DBDCommand dbdCommand;
|
||||||
private DBDAdminCommand dbdAdminCommand;
|
private DBDAdminCommand dbdAdminCommand;
|
||||||
private SetupCommand setupCommand;
|
private SetupCommand setupCommand;
|
||||||
|
|
@ -57,7 +59,10 @@ public final class mdbd extends JavaPlugin {
|
||||||
|
|
||||||
this.gameManager = new GameManager(this);
|
this.gameManager = new GameManager(this);
|
||||||
|
|
||||||
this.dbdCommand = new DBDCommand(this, gameManager);
|
this.lobbyRegistry = new LobbyRegistry(this, gameManager);
|
||||||
|
this.lobbyRegistry.load();
|
||||||
|
|
||||||
|
this.dbdCommand = new DBDCommand(this, gameManager, lobbyRegistry);
|
||||||
getCommand("dbd").setExecutor(dbdCommand);
|
getCommand("dbd").setExecutor(dbdCommand);
|
||||||
getCommand("dbd").setTabCompleter(dbdCommand);
|
getCommand("dbd").setTabCompleter(dbdCommand);
|
||||||
|
|
||||||
|
|
@ -71,7 +76,7 @@ public final class mdbd extends JavaPlugin {
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
new dev.loki.listener.SetupBlockBreakListener(setupCommand, this), this);
|
new dev.loki.listener.SetupBlockBreakListener(setupCommand, this), this);
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
new PlayerQuitListener(this, gameManager), this);
|
new PlayerQuitListener(this, gameManager, lobbyRegistry), this);
|
||||||
getServer().getPluginManager().registerEvents(
|
getServer().getPluginManager().registerEvents(
|
||||||
new PlayerJoinListener(lobbyManager), this);
|
new PlayerJoinListener(lobbyManager), this);
|
||||||
this.interactListener = new PlayerInteractListener(this, gameManager, null);
|
this.interactListener = new PlayerInteractListener(this, gameManager, null);
|
||||||
|
|
@ -98,6 +103,9 @@ public final class mdbd extends JavaPlugin {
|
||||||
if (generatorData != null) {
|
if (generatorData != null) {
|
||||||
generatorData.save();
|
generatorData.save();
|
||||||
}
|
}
|
||||||
|
if (lobbyRegistry != null) {
|
||||||
|
lobbyRegistry.save();
|
||||||
|
}
|
||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,6 +141,10 @@ public final class mdbd extends JavaPlugin {
|
||||||
return dbdAdminCommand;
|
return dbdAdminCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LobbyRegistry getLobbyRegistry() {
|
||||||
|
return lobbyRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
public LobbyManager getLobbyManager() {
|
public LobbyManager getLobbyManager() {
|
||||||
return lobbyManager;
|
return lobbyManager;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/main/java/dev/loki/minigames/GeneratorMinigame.java
Normal file
14
src/main/java/dev/loki/minigames/GeneratorMinigame.java
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.loki.minigames;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public interface GeneratorMinigame {
|
||||||
|
void start(Player player, BiConsumer<Player, Boolean> callback);
|
||||||
|
boolean isActive(UUID playerId);
|
||||||
|
boolean handleRightClick(Player player);
|
||||||
|
boolean handleSneak(Player player);
|
||||||
|
void cleanup();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
package dev.loki.minigames.generator;
|
||||||
|
|
||||||
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.minigames.GeneratorMinigame;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class GuiRouletteMinigame implements GeneratorMinigame {
|
||||||
|
private final mdbd plugin;
|
||||||
|
private final Map<UUID, RouletteSession> activeSessions = new HashMap<>();
|
||||||
|
private static final int TIMEOUT_TICKS = 60;
|
||||||
|
private static final int MIN_SWITCHES = 5;
|
||||||
|
private static final int MAX_SWITCHES = 8;
|
||||||
|
|
||||||
|
private final Material successMaterial;
|
||||||
|
private final Material failMaterial;
|
||||||
|
private final Material neutralMaterial;
|
||||||
|
|
||||||
|
public GuiRouletteMinigame(mdbd plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
GameConfig cfg = plugin.getConfigManager().getGame();
|
||||||
|
this.successMaterial = getMaterial(cfg.getSkillCheckSuccessBlock(), Material.GREEN_STAINED_GLASS_PANE);
|
||||||
|
this.failMaterial = getMaterial(cfg.getSkillCheckFailBlock(), Material.RED_STAINED_GLASS_PANE);
|
||||||
|
this.neutralMaterial = getMaterial(cfg.getSkillCheckNeutralBlock(), Material.GRAY_STAINED_GLASS_PANE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Material getMaterial(String name, Material fallback) {
|
||||||
|
Material m = Material.getMaterial(name);
|
||||||
|
return m != null ? m : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive(UUID playerId) {
|
||||||
|
return activeSessions.containsKey(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleRightClick(Player player) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleSneak(Player player) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Player player, BiConsumer<Player, Boolean> callback) {
|
||||||
|
UUID playerId = player.getUniqueId();
|
||||||
|
RouletteSession session = new RouletteSession(player, callback);
|
||||||
|
activeSessions.put(playerId, session);
|
||||||
|
|
||||||
|
String titleStr = plugin.getConfigManager().getMessages().getString("minigames.generator-roulette.title", "⚡ Skill Check!");
|
||||||
|
Inventory inv = Bukkit.createInventory(null, 9, Component.text(titleStr).color(NamedTextColor.YELLOW));
|
||||||
|
fillInventory(inv, session.successSlot);
|
||||||
|
player.openInventory(inv);
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
|
||||||
|
if (activeSessions.containsKey(playerId)) {
|
||||||
|
timeout(playerId);
|
||||||
|
}
|
||||||
|
}, TIMEOUT_TICKS);
|
||||||
|
|
||||||
|
runRoulette(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runRoulette(UUID playerId) {
|
||||||
|
RouletteSession session = activeSessions.get(playerId);
|
||||||
|
if (session == null || !session.player.isOnline()) {
|
||||||
|
activeSessions.remove(playerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.totalTicks >= TIMEOUT_TICKS) {
|
||||||
|
timeout(playerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.lastSuccessSlot = session.successSlot;
|
||||||
|
session.successSlot = (session.successSlot + 1) % 9;
|
||||||
|
session.switchesDone++;
|
||||||
|
|
||||||
|
Inventory inv = session.player.getOpenInventory().getTopInventory();
|
||||||
|
if (inv != null && inv.getSize() == 9) {
|
||||||
|
inv.setItem(session.lastSuccessSlot, createNeutralGlass());
|
||||||
|
inv.setItem(session.successSlot, createSuccessGlass());
|
||||||
|
session.player.playSound(session.player.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 0.5f, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextDelay;
|
||||||
|
int done = session.switchesDone;
|
||||||
|
int total = session.totalSwitches;
|
||||||
|
|
||||||
|
if (done < total * 0.4) {
|
||||||
|
nextDelay = 2 + (int)(Math.random() * 2);
|
||||||
|
} else if (done < total * 0.8) {
|
||||||
|
nextDelay = 4 + (int)(Math.random() * 3);
|
||||||
|
} else {
|
||||||
|
nextDelay = 8 + (int)(Math.random() * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.totalTicks += nextDelay;
|
||||||
|
|
||||||
|
if (done >= total) return;
|
||||||
|
|
||||||
|
plugin.getServer().getScheduler().runTaskLater(plugin, () -> runRoulette(playerId), nextDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleClick(UUID playerId, int clickedSlot) {
|
||||||
|
RouletteSession session = activeSessions.remove(playerId);
|
||||||
|
if (session == null) return;
|
||||||
|
|
||||||
|
Player player = session.player;
|
||||||
|
if (player == null || !player.isOnline()) return;
|
||||||
|
|
||||||
|
player.closeInventory();
|
||||||
|
|
||||||
|
boolean success = (clickedSlot == session.successSlot);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.5f);
|
||||||
|
} else {
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.callback.accept(player, success);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleClose(UUID playerId) {
|
||||||
|
if (activeSessions.containsKey(playerId)) {
|
||||||
|
timeout(playerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timeout(UUID playerId) {
|
||||||
|
RouletteSession session = activeSessions.remove(playerId);
|
||||||
|
if (session == null) return;
|
||||||
|
|
||||||
|
Player player = session.player;
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
player.closeInventory();
|
||||||
|
player.playSound(player.getLocation(), Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.callback.accept(player, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillInventory(Inventory inv, int greenSlot) {
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
inv.setItem(i, i == greenSlot ? createSuccessGlass() : createNeutralGlass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack createSuccessGlass() {
|
||||||
|
ItemStack glass = new ItemStack(successMaterial);
|
||||||
|
ItemMeta meta = glass.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
String name = plugin.getConfigManager().getMessages().getString("minigames.generator-roulette.success-label", "✓ Нажми!");
|
||||||
|
meta.displayName(Component.text(name).color(NamedTextColor.GREEN));
|
||||||
|
glass.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return glass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack createNeutralGlass() {
|
||||||
|
ItemStack glass = new ItemStack(neutralMaterial);
|
||||||
|
ItemMeta meta = glass.getItemMeta();
|
||||||
|
if (meta != null) {
|
||||||
|
meta.displayName(Component.text("-").color(NamedTextColor.GRAY));
|
||||||
|
glass.setItemMeta(meta);
|
||||||
|
}
|
||||||
|
return glass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
for (UUID uuid : new java.util.HashSet<>(activeSessions.keySet())) {
|
||||||
|
RouletteSession session = activeSessions.remove(uuid);
|
||||||
|
if (session.player != null && session.player.isOnline()) {
|
||||||
|
session.player.closeInventory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeSessions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RouletteSession {
|
||||||
|
final Player player;
|
||||||
|
final BiConsumer<Player, Boolean> callback;
|
||||||
|
int successSlot = 0;
|
||||||
|
int lastSuccessSlot = -1;
|
||||||
|
int switchesDone = 0;
|
||||||
|
final int totalSwitches = MIN_SWITCHES + new Random().nextInt(MAX_SWITCHES - MIN_SWITCHES + 1);
|
||||||
|
int totalTicks = 0;
|
||||||
|
|
||||||
|
RouletteSession(Player player, BiConsumer<Player, Boolean> callback) {
|
||||||
|
this.player = player;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package dev.loki.minigames.generator;
|
||||||
|
|
||||||
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.minigames.GeneratorMinigame;
|
||||||
|
import dev.loki.util.TextUtil;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.title.Title;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class TextSkillCheckMinigame implements GeneratorMinigame {
|
||||||
|
private final mdbd plugin;
|
||||||
|
private final Map<UUID, SkillCheckSession> active = new HashMap<>();
|
||||||
|
|
||||||
|
private static final double ZONE_SIZE = 0.15;
|
||||||
|
private static final double ROTATION_SPEED = 3.0;
|
||||||
|
private static final int MAX_TICKS = 100;
|
||||||
|
|
||||||
|
public TextSkillCheckMinigame(mdbd plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Player player, BiConsumer<Player, Boolean> callback) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
|
||||||
|
double zoneStart = Math.random() * 0.7;
|
||||||
|
double zoneEnd = zoneStart + ZONE_SIZE;
|
||||||
|
|
||||||
|
SkillCheckSession session = new SkillCheckSession(player, callback, zoneStart, zoneEnd);
|
||||||
|
active.put(uuid, session);
|
||||||
|
|
||||||
|
BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, () -> {
|
||||||
|
SkillCheckSession s = active.get(uuid);
|
||||||
|
if (s == null) { return; }
|
||||||
|
|
||||||
|
s.pointer += ROTATION_SPEED * 0.01;
|
||||||
|
if (s.pointer >= 1.0) s.pointer = 0.0;
|
||||||
|
s.ticks++;
|
||||||
|
|
||||||
|
if (s.ticks > MAX_TICKS) {
|
||||||
|
expire(uuid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(player, s);
|
||||||
|
}, 1L, 1L);
|
||||||
|
|
||||||
|
session.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void render(Player player, SkillCheckSession s) {
|
||||||
|
int width = 15;
|
||||||
|
int zStart = (int) (s.zoneStart * width);
|
||||||
|
int zEnd = (int) (s.zoneEnd * width);
|
||||||
|
int ptr = (int) (s.pointer * width);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("<gray>[");
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
if (i == ptr) {
|
||||||
|
sb.append("<white>◆");
|
||||||
|
} else if (i >= zStart && i <= zEnd) {
|
||||||
|
sb.append("<green>█");
|
||||||
|
} else {
|
||||||
|
sb.append("<dark_gray>▒");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("<gray>]");
|
||||||
|
|
||||||
|
Component title = TextUtil.color(sb.toString());
|
||||||
|
String subStr = plugin.getConfigManager().getMessages()
|
||||||
|
.getString("minigames.text-skillcheck.subtitle", "&eНажми ПКМ когда будешь в зоне!");
|
||||||
|
Component subtitle = TextUtil.color(subStr);
|
||||||
|
|
||||||
|
Title.Times times = Title.Times.times(Duration.ZERO, Duration.ofMillis(150), Duration.ZERO);
|
||||||
|
player.showTitle(Title.title(title, subtitle, times));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive(UUID playerId) {
|
||||||
|
return active.containsKey(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleRightClick(Player player) {
|
||||||
|
return complete(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleSneak(Player player) {
|
||||||
|
return complete(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean complete(Player player) {
|
||||||
|
UUID uuid = player.getUniqueId();
|
||||||
|
SkillCheckSession s = active.remove(uuid);
|
||||||
|
if (s == null) return false;
|
||||||
|
|
||||||
|
if (s.task != null) s.task.cancel();
|
||||||
|
player.clearTitle();
|
||||||
|
|
||||||
|
boolean inZone = s.pointer >= s.zoneStart && s.pointer <= s.zoneEnd;
|
||||||
|
s.callback.accept(player, inZone);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expire(UUID uuid) {
|
||||||
|
SkillCheckSession s = active.remove(uuid);
|
||||||
|
if (s == null) return;
|
||||||
|
|
||||||
|
if (s.task != null) s.task.cancel();
|
||||||
|
Player player = s.player;
|
||||||
|
if (player != null && player.isOnline()) {
|
||||||
|
player.clearTitle();
|
||||||
|
}
|
||||||
|
s.callback.accept(player, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
for (UUID uuid : new java.util.HashSet<>(active.keySet())) {
|
||||||
|
expire(uuid);
|
||||||
|
}
|
||||||
|
active.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SkillCheckSession {
|
||||||
|
final Player player;
|
||||||
|
final BiConsumer<Player, Boolean> callback;
|
||||||
|
final double zoneStart;
|
||||||
|
final double zoneEnd;
|
||||||
|
double pointer = 0.0;
|
||||||
|
int ticks = 0;
|
||||||
|
BukkitTask task;
|
||||||
|
|
||||||
|
SkillCheckSession(Player player, BiConsumer<Player, Boolean> callback,
|
||||||
|
double zoneStart, double zoneEnd) {
|
||||||
|
this.player = player;
|
||||||
|
this.callback = callback;
|
||||||
|
this.zoneStart = zoneStart;
|
||||||
|
this.zoneEnd = zoneEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.loki.objective;
|
package dev.loki.objective;
|
||||||
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.util.TextUtil;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.entity.ArmorStand;
|
import org.bukkit.entity.ArmorStand;
|
||||||
import org.bukkit.entity.Display;
|
import org.bukkit.entity.Display;
|
||||||
|
|
@ -120,7 +121,7 @@ public class GeneratorHologram {
|
||||||
Location base = gen.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5);
|
Location base = gen.getLocation().clone().add(0.5, 2.0 + heightOffset, 0.5);
|
||||||
|
|
||||||
if (display instanceof TextDisplay td) {
|
if (display instanceof TextDisplay td) {
|
||||||
td.text(net.kyori.adventure.text.Component.text(text));
|
td.text(TextUtil.color(text));
|
||||||
td.teleport(base);
|
td.teleport(base);
|
||||||
} else if (display instanceof ArmorStand as) {
|
} else if (display instanceof ArmorStand as) {
|
||||||
as.setCustomName(text);
|
as.setCustomName(text);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package dev.loki.objective;
|
package dev.loki.objective;
|
||||||
|
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.minigames.GeneratorMinigame;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
|
@ -19,18 +18,16 @@ public class GeneratorRepair {
|
||||||
private final mdbd plugin;
|
private final mdbd plugin;
|
||||||
private final GeneratorManager manager;
|
private final GeneratorManager manager;
|
||||||
private final Map<UUID, RepairSession> sessions;
|
private final Map<UUID, RepairSession> sessions;
|
||||||
private final SkillCheck skillCheck;
|
private final GeneratorMinigame minigame;
|
||||||
private final SkillCheckInventory skillCheckGUI; // GUI-based skill check
|
|
||||||
|
|
||||||
private static final double REPAIR_PER_TICK = 100.0 / 1600.0;
|
private static final double REPAIR_PER_TICK = 100.0 / 1600.0;
|
||||||
private static final double REPAIR_RANGE = 3.0;
|
private static final double REPAIR_RANGE = 3.0;
|
||||||
|
|
||||||
public GeneratorRepair(mdbd plugin, GeneratorManager manager) {
|
public GeneratorRepair(mdbd plugin, GeneratorManager manager, GeneratorMinigame minigame) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
this.minigame = minigame;
|
||||||
this.sessions = new HashMap<>();
|
this.sessions = new HashMap<>();
|
||||||
this.skillCheck = new SkillCheck(plugin, this);
|
|
||||||
this.skillCheckGUI = new SkillCheckInventory(plugin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startRepair(Player player, Generator generator) {
|
public void startRepair(Player player, Generator generator) {
|
||||||
|
|
@ -49,12 +46,12 @@ public class GeneratorRepair {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!generator.canBeRepaired()) {
|
if (!generator.canBeRepaired()) {
|
||||||
player.sendMessage(Component.text("Генератор не может быть починен!"));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("generator.repair.cannot"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generator.isBeingRepaired() && !generator.getCurrentRepairer().equals(uuid)) {
|
if (generator.isBeingRepaired() && !generator.getCurrentRepairer().equals(uuid)) {
|
||||||
player.sendMessage(Component.text("Кто-то уже чинит этот генератор!"));
|
player.sendMessage(plugin.getConfigManager().getMessages().get("generator.repair.busy"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,8 +80,6 @@ public class GeneratorRepair {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
skillCheck.tick();
|
|
||||||
|
|
||||||
for (RepairSession session : new HashMap<>(sessions).values()) {
|
for (RepairSession session : new HashMap<>(sessions).values()) {
|
||||||
if (!updateRepair(session)) {
|
if (!updateRepair(session)) {
|
||||||
stopRepair(session.player);
|
stopRepair(session.player);
|
||||||
|
|
@ -101,17 +96,26 @@ public class GeneratorRepair {
|
||||||
if (generator.getDistance(player.getLocation()) > REPAIR_RANGE) return false;
|
if (generator.getDistance(player.getLocation()) > REPAIR_RANGE) return false;
|
||||||
if (generator.isCompleted()) return false;
|
if (generator.isCompleted()) return false;
|
||||||
|
|
||||||
if (skillCheck.isActive(player.getUniqueId())) {
|
if (minigame.isActive(player.getUniqueId())) return true;
|
||||||
showSkillCheckUI(player, session);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.skillCheckCooldown > 0) {
|
if (session.skillCheckCooldown > 0) {
|
||||||
session.skillCheckCooldown--;
|
session.skillCheckCooldown--;
|
||||||
} else if (shouldTriggerSkillCheck()) {
|
} else if (shouldTriggerSkillCheck()) {
|
||||||
// Start GUI-based skill check
|
minigame.start(player, (p, success) -> {
|
||||||
Player p = session.player;
|
Generator g = session.generator;
|
||||||
skillCheckGUI.startSkillCheck(p, player.getUniqueId(), skillCheck);
|
if (success) {
|
||||||
|
double winPercent = plugin.getConfigManager().getGame().getSkillCheckWinPercent();
|
||||||
|
g.addProgress(winPercent);
|
||||||
|
p.sendActionBar(plugin.getConfigManager().getMessages().get("generator.repair.skillcheck-success", (int)winPercent));
|
||||||
|
p.playSound(p.getLocation(), org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 0.8f, 1.2f);
|
||||||
|
} else {
|
||||||
|
double lossPercent = plugin.getConfigManager().getGame().getSkillCheckLossPercent();
|
||||||
|
g.removeProgress(lossPercent);
|
||||||
|
p.sendActionBar(plugin.getConfigManager().getMessages().get("generator.repair.skillcheck-fail", (int)lossPercent));
|
||||||
|
p.playSound(p.getLocation(), org.bukkit.Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
||||||
|
notifyKillerWithCompass(p, g);
|
||||||
|
}
|
||||||
|
});
|
||||||
session.skillCheckCooldown = 100 + (int)(Math.random() * 200);
|
session.skillCheckCooldown = 100 + (int)(Math.random() * 200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -127,22 +131,10 @@ public class GeneratorRepair {
|
||||||
return true;
|
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) {
|
private void showProgressBar(Player player, RepairSession session) {
|
||||||
double progress = session.generator.getProgress();
|
double progress = session.generator.getProgress();
|
||||||
String bar = renderProgressBar(progress);
|
String bar = renderProgressBar(progress);
|
||||||
player.sendActionBar(MiniMessage.miniMessage().deserialize(
|
player.sendActionBar(plugin.getConfigManager().getMessages().get("generator.repair.actionbar", bar));
|
||||||
"<gray>Починка: " + bar
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String renderProgressBar(double progress) {
|
private String renderProgressBar(double progress) {
|
||||||
|
|
@ -158,9 +150,8 @@ public class GeneratorRepair {
|
||||||
|
|
||||||
private void onGeneratorComplete(Generator generator, Player player) {
|
private void onGeneratorComplete(Generator generator, Player player) {
|
||||||
manager.onGeneratorCompleted(generator);
|
manager.onGeneratorCompleted(generator);
|
||||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
Bukkit.broadcast(plugin.getConfigManager().getMessages().get(
|
||||||
"<green>Генератор починен! <gray>(" + manager.getCompletedCount() + "/" + manager.getRequiredCount() + ")"
|
"generator.repair.broadcast", manager.getCompletedCount(), manager.getRequiredCount()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldTriggerSkillCheck() {
|
private boolean shouldTriggerSkillCheck() {
|
||||||
|
|
@ -171,16 +162,15 @@ public class GeneratorRepair {
|
||||||
public void onSkillCheckResult(UUID playerId, boolean success, Generator generator) {
|
public void onSkillCheckResult(UUID playerId, boolean success, Generator generator) {
|
||||||
Player player = Bukkit.getPlayer(playerId);
|
Player player = Bukkit.getPlayer(playerId);
|
||||||
if (player == null) return;
|
if (player == null) return;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
double winPercent = plugin.getConfigManager().getGame().getSkillCheckWinPercent();
|
double w = plugin.getConfigManager().getGame().getSkillCheckWinPercent();
|
||||||
generator.addProgress(winPercent);
|
generator.addProgress(w);
|
||||||
player.sendActionBar(MiniMessage.miniMessage().deserialize("<green>✓ Отлично! +" + (int)winPercent + "%"));
|
player.sendActionBar(plugin.getConfigManager().getMessages().get("generator.repair.skillcheck-success", (int)w));
|
||||||
player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 0.8f, 1.2f);
|
player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 0.8f, 1.2f);
|
||||||
} else {
|
} else {
|
||||||
double lossPercent = plugin.getConfigManager().getGame().getSkillCheckLossPercent();
|
double l = plugin.getConfigManager().getGame().getSkillCheckLossPercent();
|
||||||
generator.removeProgress(lossPercent);
|
generator.removeProgress(l);
|
||||||
player.sendActionBar(MiniMessage.miniMessage().deserialize("<red>✗ Ошибка! -" + (int)lossPercent + "%. Продолжай чинить."));
|
player.sendActionBar(plugin.getConfigManager().getMessages().get("generator.repair.skillcheck-fail", (int)l));
|
||||||
player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
||||||
notifyKillerWithCompass(player, generator);
|
notifyKillerWithCompass(player, generator);
|
||||||
}
|
}
|
||||||
|
|
@ -194,8 +184,7 @@ public class GeneratorRepair {
|
||||||
for (Player online : Bukkit.getOnlinePlayers()) {
|
for (Player online : Bukkit.getOnlinePlayers()) {
|
||||||
if (online.getUniqueId().equals(failedPlayer.getUniqueId())) continue;
|
if (online.getUniqueId().equals(failedPlayer.getUniqueId())) continue;
|
||||||
|
|
||||||
online.sendMessage(MiniMessage.miniMessage().deserialize(
|
online.sendMessage(plugin.getConfigManager().getMessages().get("generator.kicked.notification"));
|
||||||
"<dark_red>[DBD] ⚡ Выживший допустил ошибку! Компас укажет на генератор!"));
|
|
||||||
|
|
||||||
ItemStack compass = new ItemStack(Material.COMPASS);
|
ItemStack compass = new ItemStack(Material.COMPASS);
|
||||||
CompassMeta meta = (CompassMeta) compass.getItemMeta();
|
CompassMeta meta = (CompassMeta) compass.getItemMeta();
|
||||||
|
|
@ -225,12 +214,24 @@ public class GeneratorRepair {
|
||||||
return sessions.get(uuid);
|
return sessions.get(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SkillCheckInventory getSkillCheckGUI() {
|
public GeneratorMinigame getMinigame() {
|
||||||
return skillCheckGUI;
|
return minigame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMinigameActive(UUID playerId) {
|
||||||
|
return minigame.isActive(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleMinigameClick(Player player) {
|
||||||
|
minigame.handleRightClick(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleMinigameSneak(Player player) {
|
||||||
|
minigame.handleSneak(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
skillCheckGUI.clear();
|
minigame.cleanup();
|
||||||
sessions.clear();
|
sessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package dev.loki.objective;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.objective.Generator;
|
||||||
|
import dev.loki.objective.GeneratorRepair;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
@ -48,9 +50,8 @@ public class SkillCheckInventory {
|
||||||
return m != null ? m : fallback;
|
return m != null ? m : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startSkillCheck(Player player, UUID playerId, SkillCheck skillCheck) {
|
public void startSkillCheck(Player player, UUID playerId, SkillCheck skillCheck, Generator generator, GeneratorRepair generatorRepair) {
|
||||||
// Start from slot 0, roulette animation
|
SkillCheckSession session = new SkillCheckSession(player, playerId, skillCheck, generator, generatorRepair);
|
||||||
SkillCheckSession session = new SkillCheckSession(player, playerId, skillCheck);
|
|
||||||
activeSessions.put(playerId, session);
|
activeSessions.put(playerId, session);
|
||||||
|
|
||||||
Inventory inv = Bukkit.createInventory(null, 9, Component.text("⚡ Skill Check!").color(NamedTextColor.YELLOW));
|
Inventory inv = Bukkit.createInventory(null, 9, Component.text("⚡ Skill Check!").color(NamedTextColor.YELLOW));
|
||||||
|
|
@ -158,7 +159,7 @@ public class SkillCheckInventory {
|
||||||
player.playSound(player.getLocation(), Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
player.playSound(player.getLocation(), Sound.ENTITY_BLAZE_HURT, 1.0f, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.skillCheck.tryComplete(playerId, success);
|
session.generatorRepair.onSkillCheckResult(playerId, success, session.generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTimeout(UUID playerId) {
|
private void onTimeout(UUID playerId) {
|
||||||
|
|
@ -172,7 +173,7 @@ public class SkillCheckInventory {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout = failure
|
// Timeout = failure
|
||||||
session.skillCheck.tryComplete(playerId, false);
|
session.generatorRepair.onSkillCheckResult(playerId, false, session.generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillInventory(Inventory inv, int greenSlot) {
|
private void fillInventory(Inventory inv, int greenSlot) {
|
||||||
|
|
@ -225,16 +226,20 @@ public class SkillCheckInventory {
|
||||||
final Player player;
|
final Player player;
|
||||||
final UUID playerId;
|
final UUID playerId;
|
||||||
final SkillCheck skillCheck;
|
final SkillCheck skillCheck;
|
||||||
|
final Generator generator;
|
||||||
|
final GeneratorRepair generatorRepair;
|
||||||
int successSlot = 0;
|
int successSlot = 0;
|
||||||
int lastSuccessSlot = -1;
|
int lastSuccessSlot = -1;
|
||||||
int switchesDone = 0;
|
int switchesDone = 0;
|
||||||
final int totalSwitches = MIN_SWITCHES + new Random().nextInt(MAX_SWITCHES - MIN_SWITCHES + 1);
|
final int totalSwitches = MIN_SWITCHES + new Random().nextInt(MAX_SWITCHES - MIN_SWITCHES + 1);
|
||||||
int totalTicks = 0;
|
int totalTicks = 0;
|
||||||
|
|
||||||
SkillCheckSession(Player player, UUID playerId, SkillCheck skillCheck) {
|
SkillCheckSession(Player player, UUID playerId, SkillCheck skillCheck, Generator generator, GeneratorRepair generatorRepair) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
this.skillCheck = skillCheck;
|
this.skillCheck = skillCheck;
|
||||||
|
this.generator = generator;
|
||||||
|
this.generatorRepair = generatorRepair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@ public class EndgamePhase implements Phase {
|
||||||
// показываем результаты сразу, без геймплея эндгейма
|
// показываем результаты сразу, без геймплея эндгейма
|
||||||
int aliveCount = 0;
|
int aliveCount = 0;
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
if (!bp.isKiller() && bp.getState() != PlayerState.DEAD && bp.getState() != PlayerState.ESCAPED) {
|
if (!bp.isKiller() && bp.getState() != PlayerState.DEAD && bp.getState() != PlayerState.ESCAPED
|
||||||
|
&& bp.getState() != PlayerState.DOWNED && bp.getState() != PlayerState.HOOKED) {
|
||||||
aliveCount++;
|
aliveCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,20 +123,12 @@ public class EndgamePhase implements Phase {
|
||||||
hatch.reset();
|
hatch.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Для killer_win: результаты уже показаны и телепорт выполнен в enter(),
|
// exit() НЕ вызывает session.stop() — это делает transitionToNextPhase()
|
||||||
// здесь просто останавливаем сессию
|
// Телепорт и результаты уже обработаны в enter() или будут в teleportAllToLobby
|
||||||
if (killerWin) {
|
|
||||||
session.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Нормальный эндгейм: показываем результаты и телепортируем всех в лобби
|
|
||||||
announceEndgameEnd(session);
|
|
||||||
teleportAllToLobby(session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Телепортирует всех игроков в лобби и останавливает сессию.
|
* Телепортирует всех игроков в лобби, сбрасывает атрибуты и останавливает сессию.
|
||||||
*/
|
*/
|
||||||
private void teleportAllToLobby(GameSession session) {
|
private void teleportAllToLobby(GameSession session) {
|
||||||
List<BasePlayer> playersToTeleport = new ArrayList<>(session.getPlayers().values());
|
List<BasePlayer> playersToTeleport = new ArrayList<>(session.getPlayers().values());
|
||||||
|
|
@ -146,12 +139,7 @@ public class EndgamePhase implements Phase {
|
||||||
for (BasePlayer bp : playersToTeleport) {
|
for (BasePlayer bp : playersToTeleport) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
resetPlayerAttributes(p);
|
||||||
p.getInventory().clear();
|
|
||||||
try {
|
|
||||||
var attr = p.getAttribute(org.bukkit.attribute.Attribute.SCALE);
|
|
||||||
if (attr != null) attr.setBaseValue(1.0);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
session.getPlugin().getLobbyManager().teleportToLobby(p);
|
session.getPlugin().getLobbyManager().teleportToLobby(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +148,34 @@ public class EndgamePhase implements Phase {
|
||||||
}.runTaskLater(session.getPlugin(), 60L);
|
}.runTaskLater(session.getPlugin(), 60L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сбрасывает все атрибуты игрока на значения по умолчанию.
|
||||||
|
*/
|
||||||
|
private void resetPlayerAttributes(Player p) {
|
||||||
|
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
||||||
|
p.getInventory().clear();
|
||||||
|
p.setWalkSpeed(0.2f);
|
||||||
|
p.setFlySpeed(0.1f);
|
||||||
|
p.setSwimming(false);
|
||||||
|
p.setSprinting(false);
|
||||||
|
p.setSneaking(false);
|
||||||
|
p.setHealth(20.0);
|
||||||
|
p.setFoodLevel(20);
|
||||||
|
p.setSaturation(5);
|
||||||
|
try {
|
||||||
|
var scale = p.getAttribute(org.bukkit.attribute.Attribute.SCALE);
|
||||||
|
if (scale != null) scale.setBaseValue(1.0);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
try {
|
||||||
|
var maxHp = p.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH);
|
||||||
|
if (maxHp != null) maxHp.setBaseValue(20.0);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
try {
|
||||||
|
var moveSpeed = p.getAttribute(org.bukkit.attribute.Attribute.MOVEMENT_SPEED);
|
||||||
|
if (moveSpeed != null) moveSpeed.setBaseValue(0.1);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isComplete() {
|
public boolean isComplete() {
|
||||||
return complete;
|
return complete;
|
||||||
|
|
@ -233,7 +249,8 @@ public class EndgamePhase implements Phase {
|
||||||
for (BasePlayer player : session.getPlayers().values()) {
|
for (BasePlayer player : session.getPlayers().values()) {
|
||||||
if (player.isKiller()) continue;
|
if (player.isKiller()) continue;
|
||||||
PlayerState st = player.getState();
|
PlayerState st = player.getState();
|
||||||
if (st != PlayerState.DEAD && st != PlayerState.ESCAPED) {
|
if (st != PlayerState.DEAD && st != PlayerState.ESCAPED
|
||||||
|
&& st != PlayerState.DOWNED && st != PlayerState.HOOKED) {
|
||||||
survivorsAlive++;
|
survivorsAlive++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ import dev.loki.player.Killer;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.survivor.SurvivorController;
|
import dev.loki.survivor.SurvivorController;
|
||||||
|
import dev.loki.item.DBDItems;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -168,8 +170,8 @@ public class GameplayInteraction {
|
||||||
player, PlayerState.DOWNED, 3.0);
|
player, PlayerState.DOWNED, 3.0);
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
boolean picked = damageSystem.getKillerController().tryPickup(killer, target);
|
boolean picked = damageSystem.getKillerController().tryPickup(killer, target);
|
||||||
if (!picked) player.sendMessage(Component.text(
|
if (!picked) player.sendMessage(net.kyori.adventure.text.Component.text(
|
||||||
"[DBD] Не удалось подобрать выжившего.", NamedTextColor.RED));
|
"[DBD] Не удалось подобрать выжившего.", net.kyori.adventure.text.format.NamedTextColor.RED));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -217,21 +219,62 @@ public class GameplayInteraction {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Survivor surv = (Survivor) bp;
|
Survivor surv = (Survivor) bp;
|
||||||
if (surv.getState() == PlayerState.HOOKED) {
|
PlayerState st = surv.getState();
|
||||||
|
if (st == PlayerState.HOOKED) {
|
||||||
// Hook recovery is handled elsewhere
|
// Hook recovery is handled elsewhere
|
||||||
|
} else if (st == PlayerState.HEALTHY || st == PlayerState.INJURED) {
|
||||||
|
if (isSneaking) {
|
||||||
|
double range = session.getConfig().getHookInteractionRange();
|
||||||
|
// Try rescue first (if someone is on a hook nearby)
|
||||||
|
Hook occupied = hookManager.findNearestOccupiedHook(player.getLocation(), range);
|
||||||
|
if (occupied != null) {
|
||||||
|
hookRescue.startRescue(surv, occupied);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Then try sabotage (only if holding hook cutter AND near a hook)
|
||||||
|
Hook free = hookManager.findNearestUnoccupiedHook(player.getLocation(), range);
|
||||||
|
if (free != null) {
|
||||||
|
ItemStack hand = player.getInventory().getItemInMainHand();
|
||||||
|
if (!DBDItems.isHookCutter(hand)) {
|
||||||
|
player.sendMessage(session.getPlugin().getConfigManager().getMessages().get("hook.sabotage.need-cutter"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hookSabotage.startSabotage(surv);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isSneaking) hookSabotage.startSabotage(surv);
|
hookSabotage.stopSabotage(surv.getUuid());
|
||||||
else hookSabotage.stopSabotage(surv.getUuid());
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generator repair
|
// Consume Hook Cutter after successful sabotage
|
||||||
|
hookSabotage.setOnSabotageComplete(uuid -> {
|
||||||
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
|
if (p != null && p.isOnline()) {
|
||||||
|
p.getInventory().setItemInMainHand(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Text skill check: complete on sneak release
|
||||||
|
interactListener.setOnGeneratorSkillCheckSneak((player, ignored) -> {
|
||||||
|
if (generatorRepair == null) return;
|
||||||
|
if (generatorRepair.isMinigameActive(player.getUniqueId())) {
|
||||||
|
generatorRepair.handleMinigameSneak(player);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generator repair + right‑click for text skill check
|
||||||
interactListener.setOnGeneratorInteract((player, action) -> {
|
interactListener.setOnGeneratorInteract((player, action) -> {
|
||||||
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
BasePlayer bp = session.getPlayer(player.getUniqueId());
|
||||||
if (bp == null || bp.isKiller()) return;
|
if (bp == null || bp.isKiller()) return;
|
||||||
if (generatorRepair == null) return;
|
if (generatorRepair == null) return;
|
||||||
|
|
||||||
|
if (generatorRepair.isMinigameActive(player.getUniqueId())) {
|
||||||
|
generatorRepair.handleMinigameClick(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var genManager = session.getGeneratorManager();
|
var genManager = session.getGeneratorManager();
|
||||||
if (genManager == null) return;
|
if (genManager == null) return;
|
||||||
double range = session.getConfig().getGeneratorInteractionRange();
|
double range = session.getConfig().getGeneratorInteractionRange();
|
||||||
|
|
@ -301,6 +344,7 @@ public class GameplayInteraction {
|
||||||
interactListener.setOnKillerSneak(null);
|
interactListener.setOnKillerSneak(null);
|
||||||
interactListener.setOnHealSneak(null);
|
interactListener.setOnHealSneak(null);
|
||||||
interactListener.setOnCarriedShift(null);
|
interactListener.setOnCarriedShift(null);
|
||||||
|
interactListener.setOnGeneratorSkillCheckSneak(null);
|
||||||
interactListener.setOnGeneratorInteract(null);
|
interactListener.setOnGeneratorInteract(null);
|
||||||
interactListener.setOnHatchInteract(null);
|
interactListener.setOnHatchInteract(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import dev.loki.chest.ChestManager;
|
||||||
import dev.loki.chest.ChestOpening;
|
import dev.loki.chest.ChestOpening;
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
|
import dev.loki.gate.ExitGate;
|
||||||
import dev.loki.gate.GateManager;
|
import dev.loki.gate.GateManager;
|
||||||
import dev.loki.gate.GateOpening;
|
import dev.loki.gate.GateOpening;
|
||||||
import dev.loki.hook.HookManager;
|
import dev.loki.hook.HookManager;
|
||||||
|
|
@ -15,6 +16,8 @@ import dev.loki.hook.HookStruggle;
|
||||||
import dev.loki.item.ChestLoot;
|
import dev.loki.item.ChestLoot;
|
||||||
import dev.loki.killer.GeneratorKick;
|
import dev.loki.killer.GeneratorKick;
|
||||||
import dev.loki.mdbd;
|
import dev.loki.mdbd;
|
||||||
|
import dev.loki.minigames.GeneratorMinigame;
|
||||||
|
import dev.loki.minigames.generator.GuiRouletteMinigame;
|
||||||
import dev.loki.objective.GeneratorManager;
|
import dev.loki.objective.GeneratorManager;
|
||||||
import dev.loki.player.BasePlayer;
|
import dev.loki.player.BasePlayer;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
|
|
@ -22,7 +25,6 @@ import dev.loki.player.Survivor;
|
||||||
import dev.loki.ui.ActionBar;
|
import dev.loki.ui.ActionBar;
|
||||||
import dev.loki.ui.TitleUI;
|
import dev.loki.ui.TitleUI;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
|
@ -91,8 +93,7 @@ public class GameplayPhase implements Phase {
|
||||||
world.setStorm(false);
|
world.setStorm(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.start"));
|
||||||
"[DBD] Игра началась! Выжившие, чините генераторы!", NamedTextColor.GREEN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -133,7 +134,7 @@ public class GameplayPhase implements Phase {
|
||||||
damageSystem.clear();
|
damageSystem.clear();
|
||||||
damageSystem.clearManagers();
|
damageSystem.clearManagers();
|
||||||
|
|
||||||
Bukkit.broadcast(Component.text("[DBD] Игровая фаза завершена!", NamedTextColor.GOLD));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.end"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -164,6 +165,18 @@ public class GameplayPhase implements Phase {
|
||||||
ActionBar ab = new ActionBar();
|
ActionBar ab = new ActionBar();
|
||||||
HookRecovery hookRecovery = new HookRecovery(plugin, cfg);
|
HookRecovery hookRecovery = new HookRecovery(plugin, cfg);
|
||||||
titleUI = new TitleUI(plugin);
|
titleUI = new TitleUI(plugin);
|
||||||
|
|
||||||
|
String genMinigameType = cfg.getGeneratorMinigame();
|
||||||
|
GeneratorMinigame genMinigame;
|
||||||
|
if ("gui".equalsIgnoreCase(genMinigameType)) {
|
||||||
|
genMinigame = new GuiRouletteMinigame(plugin);
|
||||||
|
} else if ("text".equalsIgnoreCase(genMinigameType)) {
|
||||||
|
genMinigame = new dev.loki.minigames.generator.TextSkillCheckMinigame(plugin);
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("Unknown generator minigame: " + genMinigameType + ", using gui");
|
||||||
|
genMinigame = new GuiRouletteMinigame(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
hookStruggle = new HookStruggle(plugin, titleUI, cfg);
|
hookStruggle = new HookStruggle(plugin, titleUI, cfg);
|
||||||
HookSacrifice hookSacrifice = new HookSacrifice(plugin, null);
|
HookSacrifice hookSacrifice = new HookSacrifice(plugin, null);
|
||||||
hookSabotage = new HookSabotage(plugin, cfg, hookManager);
|
hookSabotage = new HookSabotage(plugin, cfg, hookManager);
|
||||||
|
|
@ -176,11 +189,19 @@ public class GameplayPhase implements Phase {
|
||||||
chestManager.startLootResetTimer();
|
chestManager.startLootResetTimer();
|
||||||
chestOpening = new ChestOpening(plugin);
|
chestOpening = new ChestOpening(plugin);
|
||||||
chestOpening.setChestLoot(new ChestLoot(cfg));
|
chestOpening.setChestLoot(new ChestLoot(cfg));
|
||||||
|
|
||||||
|
var gameWorld = plugin.getLobbyManager().getGameWorld();
|
||||||
|
if (gameWorld != null) {
|
||||||
|
for (var h : hookManager.getAllHooks()) h.getLocation().setWorld(gameWorld);
|
||||||
|
for (var c : chestManager.getAllChests()) c.getLocation().setWorld(gameWorld);
|
||||||
|
for (var g : generatorManager.getAllGenerators()) g.getLocation().setWorld(gameWorld);
|
||||||
|
}
|
||||||
|
|
||||||
spawnGameBlocks(plugin, cfg);
|
spawnGameBlocks(plugin, cfg);
|
||||||
|
|
||||||
generatorKick = new GeneratorKick(plugin, cfg, generatorManager);
|
generatorKick = new GeneratorKick(plugin, cfg, generatorManager);
|
||||||
|
|
||||||
generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager);
|
generatorRepair = new dev.loki.objective.GeneratorRepair(plugin, generatorManager, genMinigame);
|
||||||
// Голограммы ВСЕГДА включены
|
// Голограммы ВСЕГДА включены
|
||||||
generatorHologram = new dev.loki.objective.GeneratorHologram(plugin, generatorManager);
|
generatorHologram = new dev.loki.objective.GeneratorHologram(plugin, generatorManager);
|
||||||
generatorHologram.start();
|
generatorHologram.start();
|
||||||
|
|
@ -222,11 +243,12 @@ public class GameplayPhase implements Phase {
|
||||||
if (p != null && p.isOnline()) p.getInventory().clear();
|
if (p != null && p.isOnline()) p.getInventory().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register skill check GUI listener
|
GeneratorMinigame gm = generatorRepair.getMinigame();
|
||||||
dev.loki.listener.SkillCheckListener skillCheckListener =
|
if (gm instanceof GuiRouletteMinigame gui) {
|
||||||
new dev.loki.listener.SkillCheckListener(plugin,
|
dev.loki.listener.SkillCheckListener listener =
|
||||||
generatorRepair.getSkillCheckGUI());
|
new dev.loki.listener.SkillCheckListener(plugin, gui);
|
||||||
skillCheckListener.register();
|
listener.register();
|
||||||
|
}
|
||||||
|
|
||||||
interaction.wire(session);
|
interaction.wire(session);
|
||||||
}
|
}
|
||||||
|
|
@ -241,8 +263,7 @@ public class GameplayPhase implements Phase {
|
||||||
allGensDone = true;
|
allGensDone = true;
|
||||||
interaction.setAllGensDone(true);
|
interaction.setAllGensDone(true);
|
||||||
gateManager.activateEndgame();
|
gateManager.activateEndgame();
|
||||||
Bukkit.broadcast(Component.text(
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("generator.repair.all-done"));
|
||||||
"[DBD] Все генераторы починены! Открывайте ворота!", NamedTextColor.GREEN));
|
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0f, 1.0f);
|
if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 1.0f, 1.0f);
|
||||||
|
|
@ -289,6 +310,12 @@ public class GameplayPhase implements Phase {
|
||||||
if (b.getType() != hookMat) b.setType(hookMat, false);
|
if (b.getType() != hookMat) b.setType(hookMat, false);
|
||||||
}
|
}
|
||||||
plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + hookManager.getAllHooks().size() + " hooks");
|
plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + hookManager.getAllHooks().size() + " hooks");
|
||||||
|
|
||||||
|
// Спавн блоков на местах ворот (Hopper — для открытия через ПКМ)
|
||||||
|
for (ExitGate gate : gateManager.getAllGates()) {
|
||||||
|
gate.spawnGateBlock();
|
||||||
|
}
|
||||||
|
plugin.getLogger().info("[GameplayPhase] Spawned blocks for " + gateManager.getAllGates().size() + " gates");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -342,6 +369,13 @@ public class GameplayPhase implements Phase {
|
||||||
if (hookStruggle != null) hookStruggle.clear();
|
if (hookStruggle != null) hookStruggle.clear();
|
||||||
if (chestManager != null) chestManager.stopLootResetTimer();
|
if (chestManager != null) chestManager.stopLootResetTimer();
|
||||||
if (chestHologram != null) chestHologram.stop();
|
if (chestHologram != null) chestHologram.stop();
|
||||||
|
// Clean up gate blocks
|
||||||
|
if (gateManager != null) {
|
||||||
|
for (ExitGate gate : gateManager.getAllGates()) {
|
||||||
|
gate.removeGateBlock();
|
||||||
|
}
|
||||||
|
gateManager.resetAll();
|
||||||
|
}
|
||||||
session.getPlugin().getInteractListener().setGeneratorManager(null);
|
session.getPlugin().getInteractListener().setGeneratorManager(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ public class GameplayUI {
|
||||||
matchTimerBar.color(color);
|
matchTimerBar.color(color);
|
||||||
|
|
||||||
if (matchTimerTicks <= 0) {
|
if (matchTimerTicks <= 0) {
|
||||||
Bukkit.broadcast(Component.text("[DBD] Время вышло! Маньяк победил!", NamedTextColor.DARK_RED));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.time-up"));
|
||||||
if (winConditions != null) winConditions.announceKillerWin(session);
|
if (winConditions != null) winConditions.announceKillerWin(session);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import dev.loki.player.Killer;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import dev.loki.ui.TitleUI;
|
import dev.loki.ui.TitleUI;
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.GameMode;
|
import org.bukkit.GameMode;
|
||||||
import org.bukkit.Sound;
|
import org.bukkit.Sound;
|
||||||
|
|
@ -35,22 +33,34 @@ public class GameplayWinConditions {
|
||||||
* Check all win conditions each second (20 ticks).
|
* Check all win conditions each second (20 ticks).
|
||||||
*/
|
*/
|
||||||
public void checkWinConditions(GameSession session) {
|
public void checkWinConditions(GameSession session) {
|
||||||
int aliveSurvivors = countAliveSurvivors(session);
|
|
||||||
Killer killer = getKiller(session);
|
Killer killer = getKiller(session);
|
||||||
|
|
||||||
// Killer disconnect: if killer player is null/offline, survivors win
|
// Killer disconnect: if killer player is null/offline, survivors win
|
||||||
if (killer == null || killer.getPlayer() == null || !killer.getPlayer().isOnline()) {
|
if (killer == null || killer.getPlayer() == null || !killer.getPlayer().isOnline()) {
|
||||||
|
if (!shouldEnd) {
|
||||||
winReason = "survivors_win";
|
winReason = "survivors_win";
|
||||||
Bukkit.broadcast(Component.text("[DBD] Маньяк покинул игру! Выжившие победили!", NamedTextColor.GREEN));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.killer-disconnect"));
|
||||||
announceSurvivorsWin(session);
|
announceSurvivorsWin(session);
|
||||||
shouldEnd = true;
|
shouldEnd = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No survivors at all — automatic killer win
|
||||||
|
long survivorCount = session.getPlayers().values().stream()
|
||||||
|
.filter(p -> !p.isKiller()).count();
|
||||||
|
if (survivorCount == 0) {
|
||||||
|
winReason = "killer_win";
|
||||||
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.no-survivors"));
|
||||||
|
announceKillerWin(session);
|
||||||
|
shouldEnd = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Killer wins: all survivors are downed/hooked/carried/dead
|
// Killer wins: all survivors are downed/hooked/carried/dead
|
||||||
if (allSurvivorsIncapacitated(session)) {
|
if (allSurvivorsIncapacitated(session)) {
|
||||||
winReason = "killer_win";
|
winReason = "killer_win";
|
||||||
Bukkit.broadcast(Component.text("[DBD] Все выжившие обезврежены!", NamedTextColor.DARK_RED));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.all-incapacitated"));
|
||||||
announceKillerWin(session);
|
announceKillerWin(session);
|
||||||
shouldEnd = true;
|
shouldEnd = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -64,7 +74,7 @@ public class GameplayWinConditions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, or DEAD). */
|
/** True if NO survivor can act (all are DOWNED, HOOKED, CARRIED, DEAD, or ESCAPED). */
|
||||||
private boolean allSurvivorsIncapacitated(GameSession session) {
|
private boolean allSurvivorsIncapacitated(GameSession session) {
|
||||||
boolean hasSurvivors = false;
|
boolean hasSurvivors = false;
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
|
|
@ -72,7 +82,8 @@ public class GameplayWinConditions {
|
||||||
hasSurvivors = true;
|
hasSurvivors = true;
|
||||||
PlayerState st = bp.getState();
|
PlayerState st = bp.getState();
|
||||||
if (st != PlayerState.DOWNED && st != PlayerState.HOOKED
|
if (st != PlayerState.DOWNED && st != PlayerState.HOOKED
|
||||||
&& st != PlayerState.CARRIED && st != PlayerState.DEAD) {
|
&& st != PlayerState.CARRIED && st != PlayerState.DEAD
|
||||||
|
&& st != PlayerState.ESCAPED) {
|
||||||
return false; // at least one survivor can still act
|
return false; // at least one survivor can still act
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +117,7 @@ public class GameplayWinConditions {
|
||||||
|
|
||||||
public void announceKillerWin(GameSession session) {
|
public void announceKillerWin(GameSession session) {
|
||||||
winReason = "killer_win";
|
winReason = "killer_win";
|
||||||
Bukkit.broadcast(Component.text("[DBD] Маньяк победил! Все выжившие мертвы!", NamedTextColor.DARK_RED));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.killer-win"));
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p == null || !p.isOnline()) continue;
|
if (p == null || !p.isOnline()) continue;
|
||||||
|
|
@ -122,7 +133,7 @@ public class GameplayWinConditions {
|
||||||
|
|
||||||
public void announceSurvivorsWin(GameSession session) {
|
public void announceSurvivorsWin(GameSession session) {
|
||||||
winReason = "survivors_win";
|
winReason = "survivors_win";
|
||||||
Bukkit.broadcast(Component.text("[DBD] Выжившие победили!", NamedTextColor.GREEN));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.survivors-win"));
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p == null || !p.isOnline()) continue;
|
if (p == null || !p.isOnline()) continue;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package dev.loki.phase;
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
import dev.loki.player.BasePlayer;
|
import dev.loki.player.BasePlayer;
|
||||||
|
import dev.loki.config.MessagesConfig;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
|
@ -38,7 +38,8 @@ public class LobbyPhase implements Phase {
|
||||||
if (p != null && p.isOnline()) {
|
if (p != null && p.isOnline()) {
|
||||||
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
p.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
||||||
lobbyManager.teleportToLobby(p);
|
lobbyManager.teleportToLobby(p);
|
||||||
p.sendMessage(Component.text("[DBD] Лобби открыто! Ожидание игроков...", NamedTextColor.GREEN));
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
|
p.sendMessage(messages.get("lobby.opened"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,9 +60,8 @@ public class LobbyPhase implements Phase {
|
||||||
secondsLeft = config.getCountdown4Players();
|
secondsLeft = config.getCountdown4Players();
|
||||||
tickSub = 0;
|
tickSub = 0;
|
||||||
lastPlayerCount = playerCount;
|
lastPlayerCount = playerCount;
|
||||||
sendActionBarToAll(session,
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers,
|
sendActionBarToAll(session, messages.get("lobby.waiting", playerCount, maxPlayers));
|
||||||
NamedTextColor.YELLOW));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,9 +74,8 @@ public class LobbyPhase implements Phase {
|
||||||
if (secondsLeft > fast) {
|
if (secondsLeft > fast) {
|
||||||
secondsLeft = fast;
|
secondsLeft = fast;
|
||||||
tickSub = 0;
|
tickSub = 0;
|
||||||
Bukkit.broadcast(Component.text(
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
"[DBD] Все игроки вошли! Старт через " + fast + " сек!",
|
Bukkit.broadcast(messages.get("lobby.all-ready", fast));
|
||||||
NamedTextColor.GREEN));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,17 +90,14 @@ public class LobbyPhase implements Phase {
|
||||||
if (secondsLeft == 30 || secondsLeft == 15 || secondsLeft == 10
|
if (secondsLeft == 30 || secondsLeft == 15 || secondsLeft == 10
|
||||||
|| secondsLeft == 5 || secondsLeft == 3
|
|| secondsLeft == 5 || secondsLeft == 3
|
||||||
|| secondsLeft == 2 || secondsLeft == 1) {
|
|| secondsLeft == 2 || secondsLeft == 1) {
|
||||||
Bukkit.broadcast(Component.text(
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
"[DBD] До начала: " + secondsLeft + " сек.",
|
Bukkit.broadcast(messages.get("lobby.countdown", secondsLeft));
|
||||||
NamedTextColor.YELLOW));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionBar всем (на русском, без прогресс-бара)
|
// ActionBar всем (на русском, без прогресс-бара)
|
||||||
NamedTextColor barColor = (playerCount >= maxPlayers)
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
? NamedTextColor.GREEN : NamedTextColor.YELLOW;
|
sendActionBarToAll(session, messages.get("lobby.actionbar-waiting", secondsLeft, playerCount, maxPlayers));
|
||||||
sendActionBarToAll(session,
|
|
||||||
Component.text(">>> Начало через: " + secondsLeft + "с (" + playerCount + "/" + minPlayers + ")", barColor));
|
|
||||||
|
|
||||||
if (secondsLeft <= 0) {
|
if (secondsLeft <= 0) {
|
||||||
started = true;
|
started = true;
|
||||||
|
|
@ -129,7 +125,8 @@ public class LobbyPhase implements Phase {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
|
if (p != null && p.isOnline()) p.sendActionBar(Component.empty());
|
||||||
}
|
}
|
||||||
Bukkit.broadcast(Component.text("[DBD] Игра начинается!", NamedTextColor.GREEN));
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
|
Bukkit.broadcast(messages.get("lobby.starting"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -137,11 +134,10 @@ public class LobbyPhase implements Phase {
|
||||||
player.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
player.setGameMode(org.bukkit.GameMode.ADVENTURE);
|
||||||
session.getPlugin().getLobbyManager().teleportToLobby(player);
|
session.getPlugin().getLobbyManager().teleportToLobby(player);
|
||||||
int playerCount = session.getPlayerCount();
|
int playerCount = session.getPlayerCount();
|
||||||
int minPlayers = config.getMinPlayers();
|
int maxPlayers = config.getMaxPlayers();
|
||||||
// Сообщаем только участникам этой сессии, не всему серверу
|
// Сообщаем только участникам этой сессии, не всему серверу
|
||||||
Component msg = Component.text(
|
MessagesConfig messages = session.getPlugin().getConfigManager().getMessages();
|
||||||
"[DBD] " + player.getName() + " присоединился! (" + playerCount + "/" + minPlayers + ")",
|
Component msg = messages.get("lobby.player-joined", player.getName(), playerCount, maxPlayers);
|
||||||
NamedTextColor.YELLOW);
|
|
||||||
for (BasePlayer bp : session.getPlayers().values()) {
|
for (BasePlayer bp : session.getPlayers().values()) {
|
||||||
Player p = bp.getPlayer();
|
Player p = bp.getPlayer();
|
||||||
if (p != null && p.isOnline()) p.sendMessage(msg);
|
if (p != null && p.isOnline()) p.sendMessage(msg);
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package dev.loki.phase;
|
||||||
|
|
||||||
import dev.loki.core.GameSession;
|
import dev.loki.core.GameSession;
|
||||||
import dev.loki.role.RoleAssigner;
|
import dev.loki.role.RoleAssigner;
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
public class RoleAssignPhase implements Phase {
|
public class RoleAssignPhase implements Phase {
|
||||||
|
|
@ -46,7 +44,7 @@ public class RoleAssignPhase implements Phase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exit(GameSession session) {
|
public void exit(GameSession session) {
|
||||||
Bukkit.broadcast(Component.text("[DBD] Роли назначены!", NamedTextColor.GREEN));
|
Bukkit.broadcast(session.getPlugin().getConfigManager().getMessages().get("game.roles-assigned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,12 @@ public class RoleAssigner {
|
||||||
session.getPlugin().getLogger().warning("[RoleAssigner] survivor-spawns не установлены! Выжившие будут спавниться в лобби.");
|
session.getPlugin().getLogger().warning("[RoleAssigner] survivor-spawns не установлены! Выжившие будут спавниться в лобби.");
|
||||||
} else {
|
} else {
|
||||||
session.getPlugin().getLogger().info("[RoleAssigner] Загружено " + survivorSpawns.size() + " спавнов выживших");
|
session.getPlugin().getLogger().info("[RoleAssigner] Загружено " + survivorSpawns.size() + " спавнов выживших");
|
||||||
|
int expectedSurvivors = uuids.size() - 1;
|
||||||
|
if (survivorSpawns.size() < expectedSurvivors) {
|
||||||
|
session.getPlugin().getLogger().warning(
|
||||||
|
"[RoleAssigner] Мало спавнов выживших! Установлено " + survivorSpawns.size()
|
||||||
|
+ ", нужно минимум " + expectedSurvivors + ". Выжившие наложатся друг на друга.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix world references to use game world
|
// Fix world references to use game world
|
||||||
|
|
@ -76,32 +82,33 @@ public class RoleAssigner {
|
||||||
java.util.function.Function<Location, Location> fixWorld = loc ->
|
java.util.function.Function<Location, Location> fixWorld = loc ->
|
||||||
new Location(gameWorld, loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
new Location(gameWorld, loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
||||||
|
|
||||||
// Ensure chunks are loaded at spawn locations before teleporting
|
// Remap all spawn locations to game world and load chunks
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
for (Location spawn : killerSpawns) {
|
List<Location> killerGameSpawns = killerSpawns.stream()
|
||||||
if (spawn != null && spawn.getWorld() != null) {
|
.map(fixWorld).peek(s -> s.getWorld().loadChunk(s.getBlockX() >> 4, s.getBlockZ() >> 4))
|
||||||
spawn.getWorld().loadChunk(spawn.getBlockX() >> 4, spawn.getBlockZ() >> 4);
|
.collect(java.util.stream.Collectors.toList());
|
||||||
}
|
List<Location> survivorGameSpawns = survivorSpawns.stream()
|
||||||
}
|
.map(fixWorld).peek(s -> s.getWorld().loadChunk(s.getBlockX() >> 4, s.getBlockZ() >> 4))
|
||||||
for (Location spawn : survivorSpawns) {
|
.collect(java.util.stream.Collectors.toList());
|
||||||
if (spawn != null && spawn.getWorld() != null) {
|
|
||||||
spawn.getWorld().loadChunk(spawn.getBlockX() >> 4, spawn.getBlockZ() >> 4);
|
// Последовательное назначение спавнов выживших (по циклу, чтобы равномерно распределить)
|
||||||
}
|
int survivorIndex = 0;
|
||||||
}
|
|
||||||
|
|
||||||
for (UUID uuid : uuids) {
|
for (UUID uuid : uuids) {
|
||||||
BasePlayer newPlayer;
|
BasePlayer newPlayer;
|
||||||
if (uuid.equals(killerUuid)) {
|
if (uuid.equals(killerUuid)) {
|
||||||
newPlayer = new Killer(uuid);
|
newPlayer = new Killer(uuid);
|
||||||
if (!killerSpawns.isEmpty()) {
|
if (!killerGameSpawns.isEmpty()) {
|
||||||
Location spawn = fixWorld.apply(killerSpawns.get(random.nextInt(killerSpawns.size())));
|
Location spawn = killerGameSpawns.get(random.nextInt(killerGameSpawns.size()));
|
||||||
newPlayer.setSpawnLocation(spawn);
|
newPlayer.setSpawnLocation(spawn);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newPlayer = new Survivor(uuid);
|
newPlayer = new Survivor(uuid);
|
||||||
if (!survivorSpawns.isEmpty()) {
|
if (!survivorGameSpawns.isEmpty()) {
|
||||||
Location spawn = fixWorld.apply(survivorSpawns.get(random.nextInt(survivorSpawns.size())));
|
int idx = survivorIndex % survivorGameSpawns.size();
|
||||||
|
Location spawn = survivorGameSpawns.get(idx);
|
||||||
newPlayer.setSpawnLocation(spawn);
|
newPlayer.setSpawnLocation(spawn);
|
||||||
|
survivorIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package dev.loki.survivor;
|
package dev.loki.survivor;
|
||||||
|
|
||||||
import dev.loki.config.GameConfig;
|
import dev.loki.config.GameConfig;
|
||||||
|
import dev.loki.mdbd;
|
||||||
import dev.loki.player.PlayerState;
|
import dev.loki.player.PlayerState;
|
||||||
import dev.loki.player.Survivor;
|
import dev.loki.player.Survivor;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
@ -56,7 +57,7 @@ public class SurvivorHealing {
|
||||||
|
|
||||||
// Check heal limit
|
// Check heal limit
|
||||||
if (healsUsed >= config.getMaxHealsPerMatch()) {
|
if (healsUsed >= config.getMaxHealsPerMatch()) {
|
||||||
healer.sendMessage(Component.text("[DBD] Лимит лечений исчерпан!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
healer.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.heal.limit"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ public class SurvivorHealing {
|
||||||
|
|
||||||
// Range check for other-heal
|
// Range check for other-heal
|
||||||
if (!selfHeal && healer.getLocation().distance(target.getLocation()) > config.getHealRange()) {
|
if (!selfHeal && healer.getLocation().distance(target.getLocation()) > config.getHealRange()) {
|
||||||
healer.sendMessage(Component.text("[DBD] Слишком далеко!", net.kyori.adventure.text.format.NamedTextColor.RED));
|
healer.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.heal.too-far"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +121,7 @@ public class SurvivorHealing {
|
||||||
public void cancelHealing() {
|
public void cancelHealing() {
|
||||||
if (healingTarget != null && healingTarget.isOnline() && isHealing()) {
|
if (healingTarget != null && healingTarget.isOnline() && isHealing()) {
|
||||||
healingTarget.sendActionBar(Component.empty());
|
healingTarget.sendActionBar(Component.empty());
|
||||||
healingTarget.sendMessage(Component.text("[DBD] Лечение прервано!", net.kyori.adventure.text.format.NamedTextColor.YELLOW));
|
healingTarget.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.heal.interrupted"));
|
||||||
}
|
}
|
||||||
healingTarget = null;
|
healingTarget = null;
|
||||||
healingTicks = 0;
|
healingTicks = 0;
|
||||||
|
|
@ -145,15 +146,15 @@ public class SurvivorHealing {
|
||||||
|
|
||||||
Player targetPlayer = healingTarget;
|
Player targetPlayer = healingTarget;
|
||||||
if (targetPlayer != null && targetPlayer.isOnline()) {
|
if (targetPlayer != null && targetPlayer.isOnline()) {
|
||||||
String msg = newState == PlayerState.HEALTHY
|
String msgKey = newState == PlayerState.HEALTHY
|
||||||
? "<green>Вы полностью вылечились!"
|
? "survivor.heal.self-done"
|
||||||
: "<yellow>Вы встали на ноги!";
|
: "survivor.heal.other-done";
|
||||||
targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(msg));
|
targetPlayer.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get(msgKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
Player healer = controller.getPlayer();
|
Player healer = controller.getPlayer();
|
||||||
if (healer != null && !healer.getUniqueId().equals(targetPlayer != null ? targetPlayer.getUniqueId() : null)) {
|
if (healer != null && !healer.getUniqueId().equals(targetPlayer != null ? targetPlayer.getUniqueId() : null)) {
|
||||||
healer.sendMessage(Component.text("[DBD] Союзник вылечен!", net.kyori.adventure.text.format.NamedTextColor.GREEN));
|
healer.sendMessage(mdbd.getInstance().getConfigManager().getMessages().get("survivor.heal.complete"));
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelHealing();
|
cancelHealing();
|
||||||
|
|
@ -163,10 +164,9 @@ public class SurvivorHealing {
|
||||||
if (healingTarget == null) return;
|
if (healingTarget == null) return;
|
||||||
double progress = 1.0 - ((double) healingTicks / totalHealingTicks);
|
double progress = 1.0 - ((double) healingTicks / totalHealingTicks);
|
||||||
String bar = renderProgressBar(progress);
|
String bar = renderProgressBar(progress);
|
||||||
Component message = MiniMessage.miniMessage().deserialize(
|
String template = mdbd.getInstance().getConfigManager().getMessages().getString(
|
||||||
isSelfHeal ? "<gray>Лечение себя: " + bar
|
isSelfHeal ? "survivor.heal.self-start" : "survivor.heal.other-start", bar);
|
||||||
: "<gray>Лечение союзника: " + bar
|
Component message = MiniMessage.miniMessage().deserialize(template);
|
||||||
);
|
|
||||||
healingTarget.sendActionBar(message);
|
healingTarget.sendActionBar(message);
|
||||||
// Also show on healer
|
// Also show on healer
|
||||||
Player healer = controller.getPlayer();
|
Player healer = controller.getPlayer();
|
||||||
|
|
|
||||||
|
|
@ -150,8 +150,9 @@ public class TitleUI {
|
||||||
Component title = Component.text(visual.toString())
|
Component title = Component.text(visual.toString())
|
||||||
.color(NamedTextColor.WHITE);
|
.color(NamedTextColor.WHITE);
|
||||||
|
|
||||||
Component subtitle = Component.text("CROUCH when in the zone!")
|
String subStr = plugin.getConfigManager().getMessages()
|
||||||
.color(NamedTextColor.YELLOW);
|
.getString("hook.struggle.subtitle", "&eПрисядь когда будешь в зоне!");
|
||||||
|
Component subtitle = dev.loki.util.TextUtil.color(subStr);
|
||||||
|
|
||||||
Title.Times times = Title.Times.times(Duration.ZERO, Duration.ofMillis(100), Duration.ZERO);
|
Title.Times times = Title.Times.times(Duration.ZERO, Duration.ofMillis(100), Duration.ZERO);
|
||||||
player.showTitle(Title.title(title, subtitle, times));
|
player.showTitle(Title.title(title, subtitle, times));
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public final class SpawnUtil {
|
||||||
// Ground is solid at configured Y — use it as is
|
// Ground is solid at configured Y — use it as is
|
||||||
loc.setX(x + 0.5);
|
loc.setX(x + 0.5);
|
||||||
loc.setZ(z + 0.5);
|
loc.setZ(z + 0.5);
|
||||||
loc.setY(y + 1.0);
|
loc.setY(y);
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,53 @@ package dev.loki.util;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
|
|
||||||
/**
|
import java.util.HashMap;
|
||||||
* Utility for converting legacy &#RRGGBB color codes to MiniMessage format.
|
import java.util.Map;
|
||||||
* Usage: TextUtil.color("&#FFD700Hello") → renders as gold colored text.
|
import java.util.regex.Pattern;
|
||||||
*/
|
|
||||||
public final class TextUtil {
|
public final class TextUtil {
|
||||||
private static final MiniMessage MM = MiniMessage.miniMessage();
|
private static final MiniMessage MM = MiniMessage.miniMessage();
|
||||||
|
private static final Map<Character, String> COLORS = new HashMap<>();
|
||||||
|
private static final Pattern LEGACY = Pattern.compile("&([0-9a-fklmnor])");
|
||||||
|
|
||||||
|
static {
|
||||||
|
COLORS.put('0', "black");
|
||||||
|
COLORS.put('1', "dark_blue");
|
||||||
|
COLORS.put('2', "dark_green");
|
||||||
|
COLORS.put('3', "dark_aqua");
|
||||||
|
COLORS.put('4', "dark_red");
|
||||||
|
COLORS.put('5', "dark_purple");
|
||||||
|
COLORS.put('6', "gold");
|
||||||
|
COLORS.put('7', "gray");
|
||||||
|
COLORS.put('8', "dark_gray");
|
||||||
|
COLORS.put('9', "blue");
|
||||||
|
COLORS.put('a', "green");
|
||||||
|
COLORS.put('b', "aqua");
|
||||||
|
COLORS.put('c', "red");
|
||||||
|
COLORS.put('d', "light_purple");
|
||||||
|
COLORS.put('e', "yellow");
|
||||||
|
COLORS.put('f', "white");
|
||||||
|
COLORS.put('k', "obfuscated");
|
||||||
|
COLORS.put('l', "bold");
|
||||||
|
COLORS.put('m', "strikethrough");
|
||||||
|
COLORS.put('n', "underline");
|
||||||
|
COLORS.put('o', "italic");
|
||||||
|
COLORS.put('r', "reset");
|
||||||
|
}
|
||||||
|
|
||||||
private TextUtil() {}
|
private TextUtil() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts &#RRGGBB color codes to MiniMessage <#RRGGBB> and deserializes.
|
|
||||||
*/
|
|
||||||
public static Component color(String text) {
|
public static Component color(String text) {
|
||||||
return MM.deserialize(toMiniMessage(text));
|
return MM.deserialize(toMiniMessage(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts &#RRGGBB to MiniMessage <#RRGGBB> syntax without deserializing.
|
|
||||||
*/
|
|
||||||
public static String toMiniMessage(String text) {
|
public static String toMiniMessage(String text) {
|
||||||
// &#RRGGBB → <#RRGGBB>
|
String result = text.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>");
|
||||||
return text.replaceAll("&#([0-9A-Fa-f]{6})", "<#$1>");
|
result = result.replace('§', '&');
|
||||||
|
result = LEGACY.matcher(result).replaceAll(m -> {
|
||||||
|
String tag = COLORS.get(m.group(1).charAt(0));
|
||||||
|
return tag != null ? "<" + tag + ">" : m.group();
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,314 @@
|
||||||
# ============================================
|
# ============================================
|
||||||
# Mdbd Plugin - Сообщения
|
# Mdbd Plugin - Сообщения
|
||||||
# Поддерживаются цвета в формате &#rrggbb
|
# Поддерживаются цвета в формате &#rrggbb
|
||||||
|
# %prefix% - глобальный префикс (DBD в красном)
|
||||||
|
# %admin-prefix% - префикс для админ-команд
|
||||||
|
# %setup-prefix% - префикс для команд установки
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# Префикс всех сообщений плагина
|
prefix: "&#FF2121D&#E81F1FB&#D11E1ED &7| "
|
||||||
prefix: "&#FF6B00&l[DBD]&r "
|
admin-prefix: "&#FF2121D&#E81F1FB&#D11E1ED &7| &7» "
|
||||||
|
setup-prefix: "&#FF2121D&#E81F1FB&#D11E1ED &7| &7▶ "
|
||||||
|
|
||||||
|
# ── Команды ─────────────────────────────────────
|
||||||
|
|
||||||
|
cmd:
|
||||||
|
no-permission: "%admin-prefix%&cНет прав!"
|
||||||
|
players-only: "%admin-prefix%&cТолько игроки!"
|
||||||
|
|
||||||
|
# ── /dbd ────────────────────────────────────────
|
||||||
|
|
||||||
|
dbd:
|
||||||
|
join:
|
||||||
|
success: "%prefix%&aВы присоединились к игре!"
|
||||||
|
fail: "%prefix%&cНе удалось присоединиться. Возможно, игра уже идёт."
|
||||||
|
not-in-game: "%prefix%&cВы не в игре."
|
||||||
|
leave:
|
||||||
|
success: "%prefix%&eВы покинули игру."
|
||||||
|
create:
|
||||||
|
usage: "%prefix%&cИспользование: /dbd create <имя> [пароль] [private]"
|
||||||
|
delete:
|
||||||
|
usage: "%prefix%&cИспользование: /dbd delete <имя>"
|
||||||
|
forcestart:
|
||||||
|
success: "%prefix%&aИгра форсирована!"
|
||||||
|
fail: "%admin-prefix%&cНельзя форсировать сейчас."
|
||||||
|
no-game: "%admin-prefix%&cНет активной игры!"
|
||||||
|
stop:
|
||||||
|
success: "%prefix%&eИгра остановлена."
|
||||||
|
reload:
|
||||||
|
success: "%admin-prefix%&aКонфиги перезагружены!"
|
||||||
|
map:
|
||||||
|
save-success: "%prefix%&aКарта «%s» сохранена в plugins/Mdbd/maps/"
|
||||||
|
save-fail: "%prefix%&cНе удалось сохранить карту."
|
||||||
|
load-success: "%prefix%&aКарта «%s» загружена!"
|
||||||
|
load-warn: "%prefix%&eПерезагрузите карту или перезапустите сервер для применения."
|
||||||
|
load-not-found: "%prefix%&cКарта «%s» не найдена."
|
||||||
|
save-usage: "%admin-prefix%&cИспользование: /dbd save <название>"
|
||||||
|
load-usage: "%admin-prefix%&cИспользование: /dbd load <название>"
|
||||||
|
lobby:
|
||||||
|
teleport: "%prefix%&aТелепорт в лобби!"
|
||||||
|
setlobby:
|
||||||
|
success: "%admin-prefix%&aТочка лобби установлена!"
|
||||||
|
|
||||||
|
# ── /dbd admin ──────────────────────────────────
|
||||||
|
|
||||||
|
dbdadmin:
|
||||||
|
no-session: "%admin-prefix%&cНет активной игровой сессии."
|
||||||
|
heal:
|
||||||
|
target: "%prefix%&aАдмин вылечил вас."
|
||||||
|
sender: "%admin-prefix%&a%s → HEALTHY"
|
||||||
|
state:
|
||||||
|
sender: "%admin-prefix%&e%s → %s"
|
||||||
|
usage: "%admin-prefix%&c/dbd admin state <игрок> <HEALTHY|INJURED|DOWNED|HOOKED|...>"
|
||||||
|
unknown: "%admin-prefix%&cНеизвестный статус."
|
||||||
|
hooks:
|
||||||
|
usage: "%admin-prefix%&c/dbd admin hooks <игрок> <0-3>"
|
||||||
|
sender: "%admin-prefix%&a%s hooks=%d"
|
||||||
|
invalid: "%admin-prefix%&cЧисло 0–3."
|
||||||
|
kill:
|
||||||
|
sender: "%admin-prefix%&c%s → DEAD"
|
||||||
|
resolve:
|
||||||
|
no-player: "%admin-prefix%&cУкажите игрока."
|
||||||
|
offline: "%admin-prefix%&cИгрок не в сети."
|
||||||
|
not-in-game: "%admin-prefix%&cИгрок не в этой игре или не выживший."
|
||||||
|
jump:
|
||||||
|
sender: "%admin-prefix%&6Прыжки %s"
|
||||||
|
unknown-type: "%admin-prefix%&cНеизвестный тип: %s"
|
||||||
|
|
||||||
|
# ── /dbdadmin (список объектов) ─────────────────
|
||||||
|
|
||||||
|
dbdadmin-list:
|
||||||
|
header: "%setup-prefix%=== Все установленные объекты ==="
|
||||||
|
none: "&7%s: нет"
|
||||||
|
count: "&6%s (&e%s&6):"
|
||||||
|
entry: "�CED1 #%d %s [%.1f, %.1f, %.1f]"
|
||||||
|
world-usage: "%setup-prefix%&cИспользование: /dbdadmin world <game|название мира>"
|
||||||
|
world-not-found: "%setup-prefix%&cМир game не загружен!"
|
||||||
|
world-not-exist: "%setup-prefix%&cМир не найден: %s"
|
||||||
|
world-teleport: "%setup-prefix%&aТелепортирован в мир: &6%s"
|
||||||
|
|
||||||
|
# ── /dbdsetup ───────────────────────────────────
|
||||||
|
|
||||||
|
setup:
|
||||||
|
wand-given: "%setup-prefix%&aПолучен DBD Setup Wand"
|
||||||
|
set-usage: "%setup-prefix%&cИспользование: /dbdsetup set <тип> [номер]"
|
||||||
|
set-types: "&6Типы: hook, chest, generator, killer-spawn, survivor-spawn, gate"
|
||||||
|
set-spawn-info: "&6Для спавнов можно указать номер: /dbdsetup set killer-spawn 0"
|
||||||
|
gate-usage: "%setup-prefix%&cУкажи: /dbdsetup set gate A (или B)"
|
||||||
|
gate-invalid: "%setup-prefix%&cВорота могут быть только A или B"
|
||||||
|
unknown-type: "%setup-prefix%&cНеизвестный тип: %s"
|
||||||
|
survivor-spawn-range: "%setup-prefix%&cНомер спавна выжившего должен быть от 1 до 4"
|
||||||
|
killer-spawn-range: "%setup-prefix%&cНомер спавна киллера должен быть >= 0"
|
||||||
|
invalid-number: "%setup-prefix%&cНомер спавна должен быть числом: %s"
|
||||||
|
mode-off: "&eРежим установки &6%s &eвыключен"
|
||||||
|
mode-on-spawn: "%setup-prefix%&aРежим установки: &6%s%s\n&aСломай блок чтобы установить спавн (блок восстановится)\n&eПовторно введи команду для выключения"
|
||||||
|
mode-on: "%setup-prefix%&aРежим установки: &6%s%s\n&aСломай блок чтобы установить (блок восстановится)\n&eВведи команду снова чтобы выключить режим"
|
||||||
|
gate-need-points: "%setup-prefix%&cСначала выбери оба угла топориком! (ЛКМ=точка 1, ПКМ=точка 2)"
|
||||||
|
gate-same-world: "%setup-prefix%&cТочки должны быть в одном мире!"
|
||||||
|
gate-placed: "%setup-prefix%&aВорота &f%s &aустановлены: %s"
|
||||||
|
hook-placed: "%setup-prefix%&aКрюк установлен: %s"
|
||||||
|
chest-placed: "%setup-prefix%&aСундук добавлен: %s"
|
||||||
|
generator-placed: "%setup-prefix%&aГенератор добавлен: %s"
|
||||||
|
killer-spawn-exists: "%setup-prefix%&cСпавн киллера уже установлен! Используй /dbdsetup clear-spawns для сброса."
|
||||||
|
killer-spawn-placed: "&#FF4444Спавн киллера установлен: %s"
|
||||||
|
survivor-spawn-limit: "%setup-prefix%&cДостигнут лимит спавнов выживших (4)! Используй /dbdsetup clear-spawns для сброса."
|
||||||
|
survivor-spawn-placed: "%setup-prefix%&aСпавн для &6%s &aвыжившего установлен! (всего: %d/4) %s"
|
||||||
|
wand-point: "%setup-prefix%&a[Wand] Точка %d: %s"
|
||||||
|
map-cleared: "%setup-prefix%&aКарта очищена!"
|
||||||
|
spawns-cleared: "%setup-prefix%&aВсе спавны (киллер + выжившие) очищены!"
|
||||||
|
lobby-set: "%setup-prefix%&aЛобби установлено: %s"
|
||||||
|
|
||||||
|
# ── Игровой процесс ─────────────────────────────
|
||||||
|
|
||||||
# Сообщения игры
|
|
||||||
game:
|
game:
|
||||||
# Старт/конец
|
start: "%prefix%&aИгра началась! Выжившие, чините генераторы!"
|
||||||
start: "�FF00Игра началась! Бегите от Киллера!"
|
end: "&6&l=== РЕЗУЛЬТАТЫ ==="
|
||||||
end-victory-escape: "&#FFD700Выжившие сбежали! Победа!"
|
killer-win: "%prefix%&4Маньяк победил! Все выжившие мертвы!"
|
||||||
end-victory-killer: "&#DC143CКиллер победил!"
|
survivors-win: "%prefix%&aВыжившие победили!"
|
||||||
|
killer-disconnect: "%prefix%&aМаньяк покинул игру! Выжившие победили!"
|
||||||
|
no-survivors: "%prefix%&4В игре нет выживших! Маньяк победил!"
|
||||||
|
all-incapacitated: "%prefix%&4Все выжившие обезврежены!"
|
||||||
|
all-dead: "%prefix%&4Все выжившие мертвы. Киллер победил!"
|
||||||
|
time-up: "%prefix%&4Время вышло! Маньяк победил!"
|
||||||
|
roles-assigned: "%prefix%&aРоли назначены!"
|
||||||
|
|
||||||
# Ожидание
|
# ── Лобби ──────────────────────────────────────
|
||||||
waiting: "&#FFFF00Ожидание игроков... (%s/%s)"
|
|
||||||
countdown: "&#FFA500До начала: %s секунд"
|
|
||||||
countdown-short: "&#FF0000%s..."
|
|
||||||
|
|
||||||
# Роли
|
lobby:
|
||||||
role-killer: "&#DC143CВы Киллер! Поймайте всех выживших."
|
opened: "%prefix%&aЛобби открыто! Ожидание игроков..."
|
||||||
role-survivor: "�CED1Вы Выживший! Почините генераторы и сбегите."
|
waiting: "&e⏳ Ожидание игроков: %d/%d"
|
||||||
|
all-ready: "&a[DBD] Все игроки вошли! Старт через %d сек!"
|
||||||
|
countdown: "&e[DBD] До начала: %d сек."
|
||||||
|
starting: "&a[DBD] Игра начинается!"
|
||||||
|
actionbar-waiting: "&e>>> Начало через: %dс (%d/%d)"
|
||||||
|
player-joined: "&e[DBD] %s присоединился! (%d/%d)"
|
||||||
|
|
||||||
|
# ── Роли ───────────────────────────────────────
|
||||||
|
|
||||||
|
role:
|
||||||
|
killer: "%prefix%&4Вы — Маньяк! Поймайте всех выживших!"
|
||||||
|
survivor: "%prefix%&bВы — Выживший! Почините генераторы и сбегите!"
|
||||||
|
|
||||||
|
# ── Генераторы ─────────────────────────────────
|
||||||
|
|
||||||
# Генераторы
|
|
||||||
generator:
|
generator:
|
||||||
repair-start: "�CED1Вы начали чинить генератор..."
|
repair:
|
||||||
repair-stop: "WCEEBВы остановились."
|
start: "&bВы начали чинить генератор..."
|
||||||
repair-complete: "�FF00✓ Генератор активирован! (%s/%s)"
|
stop: "&eВы остановились."
|
||||||
repair-progress: "&#FFD700Прогресс: %s%%"
|
complete: "&a✔ Генератор активирован!"
|
||||||
|
broadcast: "&aГенератор починен! &7(%d/%d)"
|
||||||
|
cannot: "&cГенератор не может быть починен!"
|
||||||
|
busy: "&cКто-то уже чинит этот генератор!"
|
||||||
|
actionbar: "&7Починка: %s"
|
||||||
|
skillcheck-title: "&eSkill Check! %s"
|
||||||
|
skillcheck-success: "&a✓ Отлично! +%d%%"
|
||||||
|
skillcheck-fail: "&c✗ Ошибка! -%d%%. Продолжай чинить."
|
||||||
|
kicked:
|
||||||
|
begin: "%prefix%&cДержите Shift для разрушения генератора..."
|
||||||
|
cooldown: "%prefix%&cКулдаун удара: %d сек"
|
||||||
|
already-broken: "%prefix%&eГенератор уже сломан! Нечего разрушать."
|
||||||
|
actionbar: "&c⚡ Разрушение: %d%%"
|
||||||
|
success: "%prefix%&cГенератор поврежден! -%d%% прогресса"
|
||||||
|
broadcast: "%prefix%&4⚡ Маньяк разрушил генератор! -%d%%"
|
||||||
|
complete: "&aГенератор починен!\n%e"
|
||||||
|
all-done: "%prefix%&aВсе генераторы починены! Открывайте ворота!"
|
||||||
|
notification: "&4[DBD] ⚡ Выживший допустил ошибку! Компас укажет на генератор!"
|
||||||
|
|
||||||
skillcheck-success: "�FF00Отлично! +%s%%"
|
# ── Маньяк ─────────────────────────────────────
|
||||||
skillcheck-super: "&#FFD700Великолепно! +%s%%"
|
|
||||||
skillcheck-fail: "&#FF0000✗ Провал! -%s%%"
|
|
||||||
|
|
||||||
explosion: "&#FF4500⚠ Взрыв генератора!"
|
|
||||||
regression: "&#FF6347Генератор откатывается..."
|
|
||||||
kicked: "&#FF4500Киллер пнул генератор!"
|
|
||||||
|
|
||||||
# Киллер
|
|
||||||
killer:
|
killer:
|
||||||
terror-near: "B0000Сердце колотится..."
|
carried-escape: "&6Попытка вырваться: %d%%"
|
||||||
terror-medium: "&#FF4500Опасно рядом..."
|
|
||||||
terror-far: "&#FFA500Киллер где-то рядом"
|
|
||||||
|
|
||||||
chase-start: "&#FF0000⚠ Киллер преследует вас!"
|
# ── Выжившие ───────────────────────────────────
|
||||||
chase-stop: "�FF00Вы ушли от погони."
|
|
||||||
|
|
||||||
hit: "&#DC143CВы ранены! Ищите укрытие!"
|
|
||||||
down: "B0000Вы в нокдауне! Ползите!"
|
|
||||||
pickup: "󃕐Киллер поднял вас..."
|
|
||||||
hook: "B0082Вас повесили на крюк!"
|
|
||||||
|
|
||||||
# Выжившие
|
|
||||||
survivor:
|
survivor:
|
||||||
heal-self-start: "�FA9AЛечите себя..."
|
heal:
|
||||||
heal-self-complete: "�FF00Вылечены!"
|
limit: "%prefix%&cЛимит лечений исчерпан!"
|
||||||
heal-other-start: "�FA9AЛечите союзника..."
|
too-far: "%prefix%&cСлишком далеко!"
|
||||||
heal-other-complete: "�FF00Союзник вылечен!"
|
self-start: "&aЛечение себя: %s"
|
||||||
heal-interrupt: "&#FFD700Лечение прервано!"
|
other-start: "&7Лечение союзника: %s"
|
||||||
|
self-done: "&aВы полностью вылечились!"
|
||||||
|
other-done: "&eВы встали на ноги!"
|
||||||
|
complete: "%prefix%&aСоюзник вылечен!"
|
||||||
|
interrupted: "%prefix%&eЛечение прервано!"
|
||||||
|
medkit:
|
||||||
|
need-shift: "%prefix%&eЗажмите Shift и стойте на месте для лечения."
|
||||||
|
not-injured: "%prefix%&cВы не ранены!"
|
||||||
|
empty: "%prefix%&cМедкит пуст!"
|
||||||
|
start: "%prefix%&aСамолечение... держите Shift и не двигайтесь."
|
||||||
|
actionbar: "&a⚕ %s %d%%"
|
||||||
|
done: "%prefix%&a✔ Вы вылечились!"
|
||||||
|
cancelled: "%prefix%&eЛечение прервано."
|
||||||
|
depleted: "&cYour medkit has been depleted!"
|
||||||
|
|
||||||
sprint-exhausted: "&#FF8C00Вы устали... (Истощение)"
|
# ── Крюки ──────────────────────────────────────
|
||||||
sprint-ready: "�FF00Можно бежать!"
|
|
||||||
|
|
||||||
vault-fast: "�CED1Быстрый прыжок!"
|
|
||||||
vault-slow: "WCEEBМедленный прыжок..."
|
|
||||||
|
|
||||||
rescue-start: "�CED1Спасаете с крюка..."
|
|
||||||
rescue-complete: "�FF00Союзник спасён!"
|
|
||||||
|
|
||||||
# Крюки
|
|
||||||
hook:
|
hook:
|
||||||
stage1: "&#FFFF00Крюк: Фаза 1 — Не двигайтесь"
|
sabotage:
|
||||||
stage2: "&#FFA500Крюк: Фаза 2 — БОРИТЕСЬ!"
|
start: "%prefix%&eДержите Shift для саботажа крюка..."
|
||||||
struggle-success: "�FF00Успех! Держитесь!"
|
need-cutter: "%prefix%&cДля саботажа крюка нужны ножницы!"
|
||||||
struggle-fail: "&#FF0000Провал! Этап ускорен!"
|
actionbar: "&e⚙ Саботаж крюка: %d%%"
|
||||||
sacrifice: "B0000☠ Вы жертвованы..."
|
done: "%prefix%&aКрюк сломан!"
|
||||||
|
broadcast: "%prefix%&e⚙ Крюк был сломан выжившим!"
|
||||||
|
recovery: "%prefix%&7🔧 Крюк восстановлен."
|
||||||
|
rescue:
|
||||||
|
start: "%prefix%&eСпасаем... держите Shift!"
|
||||||
|
too-far: "%prefix%&cСлишком далеко! Спасение прервано."
|
||||||
|
victim-saved: "%prefix%&aВас спасли! Бегите!"
|
||||||
|
rescuer-actionbar: "%prefix%&aСоюзник спасён!"
|
||||||
|
rescuer-done: "%prefix%&aСпасение завершено!"
|
||||||
|
struggle:
|
||||||
|
hold-shift: "%prefix%&eДержите Shift чтобы вырваться с крюка..."
|
||||||
|
subtitle: "&eПрисядь когда будешь в зоне!"
|
||||||
|
actionbar: "&6Попытка вырваться: %d%%"
|
||||||
|
success: "%prefix%&aВы вырвались с крюка!"
|
||||||
|
fail: "%prefix%&cПопытка не удалась! (%d/%d)"
|
||||||
|
sacrifice: "%prefix%&4Сущность поглотила вас!"
|
||||||
|
sacrifice:
|
||||||
|
message: "%prefix%&4Вы были принесены в жертву. Наблюдайте за матчем."
|
||||||
|
title: "☠"
|
||||||
|
subtitle: "&cYou have been sacrificed"
|
||||||
|
broadcast: "%s has been sacrificed to the Entity"
|
||||||
|
|
||||||
# Предметы
|
# ── Сундуки ────────────────────────────────────
|
||||||
item:
|
|
||||||
found: "&#FFD700Найдено: %s"
|
|
||||||
|
|
||||||
medkit-use: "�FA9AИспользуете аптечку..."
|
chest:
|
||||||
medkit-depleted: "&#FF6347Аптечка закончилась"
|
empty: "&cThis chest is empty!"
|
||||||
|
opening: "&eOpening chest..."
|
||||||
|
cancelled: "&cOpening cancelled!"
|
||||||
|
too-far: "%prefix%&cСлишком далеко!"
|
||||||
|
actionbar: "&6Открытие: %s %d%%"
|
||||||
|
found: "%prefix%&aВы нашли: %s"
|
||||||
|
door-open: "&aОткрываем ворота... (зажмите ПКМ)"
|
||||||
|
door-opened: "&aВорота открыты!"
|
||||||
|
door-nearby: "&cНет ворот поблизости!"
|
||||||
|
door-no-power: "&cВорота обесточены!"
|
||||||
|
door-already: "&aВорота уже открыты!"
|
||||||
|
door-interrupted: "&7Открытие прервано!"
|
||||||
|
door-actionbar: "&aОткрываем ворота... (зажмите ПКМ)"
|
||||||
|
|
||||||
flashlight-blind: "&#FFFF00Ослепили киллера! Уходите!"
|
# ── Люк ────────────────────────────────────────
|
||||||
flashlight-depleted: "&#FF6347Батарейка села"
|
|
||||||
|
|
||||||
key-hatch: "�CED1Ключ открывает люк!"
|
hatch:
|
||||||
map-reveal: "WCEEBОбъекты показаны на карте"
|
too-far: "&cПодойдите ближе к люку!"
|
||||||
|
hidden: "&7Люк скрыт..."
|
||||||
|
closed: "&cЛюк закрыт!"
|
||||||
|
open-notify: "&e&lЛюк открыт!"
|
||||||
|
escape-title: "&a&lПОБЕГ!"
|
||||||
|
escape-subtitle: "&7Вы сбежали через люк!"
|
||||||
|
reward: "&a+5000 Очков — Побег через люк!"
|
||||||
|
|
||||||
chest-opening: "&#FFD700Открываете сундук..."
|
# ── Игрок вышел ────────────────────────────────
|
||||||
chest-found: "�FF00Нашли: %s"
|
|
||||||
|
|
||||||
# Эндгейм
|
quit:
|
||||||
endgame:
|
killer-disconnect: "%prefix%&cКиллер покинул игру. Игра завершена."
|
||||||
gates-powered: "�FF00Ворота запитаны! Бегите!"
|
survivor-left: "%prefix%&eВыживший покинул игру. Осталось живых: %d"
|
||||||
gates-opening: "&#FFD700Открываете ворота..."
|
|
||||||
|
|
||||||
hatch-open: "B0082Люк открыт! Сбегите!"
|
# ── Мини-игры ──────────────────────────────────
|
||||||
hatch-escape: "�FF00✓ Вы сбежали через люк!"
|
|
||||||
|
|
||||||
escape: "�FF00✓ ПОБЕГ!"
|
minigames:
|
||||||
|
generator-roulette:
|
||||||
|
title: "⚡ Skill Check!"
|
||||||
|
success-label: "✓ Нажми!"
|
||||||
|
text-skillcheck:
|
||||||
|
subtitle: "&eНажми ПКМ или отпусти шифт в зоне!"
|
||||||
|
|
||||||
# Ошибки
|
# ── Использование команд ───────────────────────
|
||||||
error:
|
|
||||||
no-permission: "&#FF0000У вас нет прав!"
|
usage:
|
||||||
not-in-game: "&#FF6347Вы не в игре!"
|
dbd-header: "&6=== DBD Команды ==="
|
||||||
already-in-game: "&#FF6347Вы уже в игре!"
|
dbd-join: "&7/dbd join [name] — присоединиться к игре/лобби"
|
||||||
game-full: "&#FF6347Игра заполнена!"
|
dbd-leave: "&7/dbd leave — покинуть игру"
|
||||||
no-game: "&#FF6347Нет активной игры!"
|
dbd-lobby: "&7/dbd lobby — телепорт в лобби"
|
||||||
not-survivor: "&#FF6347Только для выживших!"
|
dbd-create: "&7/dbd create — создать лобби"
|
||||||
not-killer: "&#FF6347Только для киллера!"
|
dbd-list: "&7/dbd list — список лобби"
|
||||||
|
dbd-delete: "&7/dbd delete — удалить лобби"
|
||||||
|
dbd-admin: "&e/dbd admin — управление игроками в матче"
|
||||||
|
dbd-forcestart: "&e/dbd forcestart — форс старт"
|
||||||
|
dbd-stop: "&e/dbd stop — остановить игру"
|
||||||
|
dbd-reload: "&e/dbd reload — перезагрузить конфиги"
|
||||||
|
dbd-setlobby: "&e/dbd setlobby — установить точку лобби"
|
||||||
|
dbd-save: "&e/dbd save — сохранить карту"
|
||||||
|
dbd-load: "&e/dbd load — загрузить карту"
|
||||||
|
admin-header: "&6=== /dbd admin ==="
|
||||||
|
admin-heal: "&6/dbd admin heal [игрок]"
|
||||||
|
admin-down: "&6/dbd admin down [игрок]"
|
||||||
|
admin-unhook: "&6/dbd admin unhook [игрок]"
|
||||||
|
admin-state: "&6/dbd admin state <игрок> <статус>"
|
||||||
|
admin-hooks: "&6/dbd admin hooks <игрок> <0-3>"
|
||||||
|
admin-kill: "&6/dbd admin kill [игрок]"
|
||||||
|
admin-jump: "&6/dbd admin jump [on/off] — вкл/выкл прыжки"
|
||||||
|
setup-header: "&6=== DBD Setup ==="
|
||||||
|
setup-wand: "&6/dbdsetup wand — Получить ванд (ЛКМ/ПКМ для ворот)"
|
||||||
|
setup-hook: "&6/dbdsetup set hook — Установить крюк (ломай блок)"
|
||||||
|
setup-chest: "&6/dbdsetup set chest — Установить сундук"
|
||||||
|
setup-generator: "&6/dbdsetup set generator — Установить генератор"
|
||||||
|
setup-killer-spawn: "&6/dbdsetup set killer-spawn — Спавн киллера (только 1)"
|
||||||
|
setup-survivor-spawn: "&6/dbdsetup set survivor-spawn — Спавн выжившего (до 4)"
|
||||||
|
setup-gate: "&6/dbdsetup set gate A|B — Установить ворота (из точек ванда)"
|
||||||
|
setup-clear-spawns: "&6/dbdsetup clear-spawns — Очистить все спавны"
|
||||||
|
setup-lobby: "&6/dbdsetup lobby — Установить точку лобби"
|
||||||
|
admin-list-header: "&6=== DBD Admin ==="
|
||||||
|
admin-list-list: "&6/dbdadmin list — Список всех объектов"
|
||||||
|
admin-list-type: "&6/dbdadmin list <тип> — Список по типу"
|
||||||
|
admin-world: "&6/dbdadmin world <game|мир> — Телепорт в мир"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue