init commit

This commit is contained in:
Patrick
2026-05-01 18:43:38 +02:00
commit b9a3f4f0df
26 changed files with 3766 additions and 0 deletions
@@ -0,0 +1,73 @@
package de.winniepat.pierredufaulersack;
import de.winniepat.pierredufaulersack.commands.*;
import de.winniepat.pierredufaulersack.game.*;
import de.winniepat.pierredufaulersack.kits.*;
import de.winniepat.pierredufaulersack.listeners.*;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
public final class Pierredufaulersack extends JavaPlugin {
private KitService kitService;
private GameManager gameManager;
private SidebarService sidebarService;
private LuckPermsService luckPermsService;
@Override
public void onEnable() {
saveDefaultConfig();
this.kitService = new KitService(this);
this.luckPermsService = new LuckPermsService(this);
this.gameManager = new GameManager(this, kitService, luckPermsService);
this.sidebarService = new SidebarService(this, gameManager);
registerCommands();
getServer().getPluginManager().registerEvents(new FriendlyFireListener(gameManager), this);
getServer().getPluginManager().registerEvents(new PlayerConnectionListener(gameManager, sidebarService), this);
getServer().getPluginManager().registerEvents(new RoundOutcomeListener(gameManager), this);
getServer().getPluginManager().registerEvents(new RoundWaterCleanupListener(gameManager), this);
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this, gameManager, kitService), this);
getServer().getPluginManager().registerEvents(new CobwebDecayListener(this, gameManager), this);
getServer().getPluginManager().registerEvents(new PlayerDeathListener(), this);
sidebarService.start();
getLogger().info("Pierredufaulersack enabled.");
}
@Override
public void onDisable() {
if (sidebarService != null) {
sidebarService.shutdown();
}
if (gameManager != null) {
gameManager.shutdown();
gameManager.stopRound(true);
}
}
private void registerCommands() {
PluginCommand bossHunt = getCommand("bosshunt");
PluginCommand bossKit = getCommand("bosskit");
PluginCommand hunterKit = getCommand("hunterkit");
if (bossHunt != null) {
BossHuntCommand bossHuntCommand = new BossHuntCommand(gameManager);
bossHunt.setExecutor(bossHuntCommand);
bossHunt.setTabCompleter(bossHuntCommand);
}
if (bossKit != null) {
KitCommand cmd = new KitCommand(kitService, KitType.BOSS);
bossKit.setExecutor(cmd);
bossKit.setTabCompleter(cmd);
}
if (hunterKit != null) {
KitCommand cmd = new KitCommand(kitService, KitType.HUNTER);
hunterKit.setExecutor(cmd);
hunterKit.setTabCompleter(cmd);
}
}
}
@@ -0,0 +1,167 @@
package de.winniepat.pierredufaulersack.commands;
import de.winniepat.pierredufaulersack.game.GameManager;
import de.winniepat.pierredufaulersack.game.GameState;
import java.util.*;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
public class BossHuntCommand implements CommandExecutor, TabCompleter {
private final GameManager gameManager;
private static final String PREFIX = "&8[&6BossHunt&8] &r";
public BossHuntCommand(GameManager gameManager) {
this.gameManager = gameManager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length < 1) {
send(sender, "&cNutzung: &f/" + label + " <queues|select|join|leave|start|stop|setlobby|setarena|status>");
return true;
}
String sub = args[0].toLowerCase();
switch (sub) {
case "queues" -> sendQueues(sender);
case "select" -> {
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können eine Warteschlange auswählen.");
return true;
}
if (args.length < 2) {
send(sender, "&cNutzung: &f/" + label + " select <queue>");
return true;
}
gameManager.selectQueue(player, args[1]);
}
case "join" -> {
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können einer Warteschlange beitreten.");
return true;
}
gameManager.join(player, args.length >= 2 ? args[1] : null);
}
case "leave" -> {
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können die Warteschlange verlassen.");
return true;
}
gameManager.leave(player);
}
case "setlobby" -> {
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können die Lobby setzen.");
return true;
}
gameManager.setLobby(player);
}
case "setarena" -> {
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können Arenen setzen.");
return true;
}
if (args.length < 2) {
send(sender, "&cNutzung: &f/" + label + " setarena <queue>");
return true;
}
gameManager.setArena(player, args[1]);
}
case "start" -> gameManager.startRound(sender, args.length >= 2 ? args[1] : null);
case "stop" -> gameManager.stopRoundByCommand(sender, args.length >= 2 ? args[1] : null);
case "status" -> sendStatus(sender, args.length >= 2 ? args[1] : null);
default -> send(sender, "&cUnbekannter Unterbefehl.");
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
return List.of("queues", "select", "join", "leave", "start", "stop", "setlobby", "setarena", "status").stream()
.filter(option -> option.startsWith(args[0].toLowerCase()))
.toList();
}
if (args.length == 2) {
String sub = args[0].toLowerCase();
if (sub.equals("select") || sub.equals("join") || sub.equals("setarena") || sub.equals("start") || sub.equals("status")) {
return gameManager.queueIds().stream()
.filter(id -> id.toLowerCase().startsWith(args[1].toLowerCase()))
.sorted()
.toList();
}
if (sub.equals("stop")) {
List<String> options = new ArrayList<>(gameManager.queueIds());
options.add("all");
return options.stream()
.filter(id -> id.toLowerCase().startsWith(args[1].toLowerCase()))
.sorted()
.toList();
}
}
return List.of();
}
private void sendQueues(CommandSender sender) {
send(sender, "&fWarteschlangen:");
for (String queueId : new TreeSet<>(gameManager.queueIds())) {
GameState state = gameManager.getState(queueId);
send(sender, "&e- " + queueId + " &7(" + stateText(state) + "&7) &f"
+ gameManager.lobbySize(queueId) + "&7/&f" + gameManager.maxPlayers());
}
}
private void sendStatus(CommandSender sender, String queueIdArg) {
String queueId = queueIdArg;
if ((queueId == null || queueId.isBlank()) && sender instanceof Player player) {
queueId = gameManager.getSelectedQueue(player.getUniqueId());
}
if (queueId == null || queueId.isBlank()) {
sendQueues(sender);
return;
}
if (!gameManager.queueIds().contains(queueId)) {
send(sender, "&cUnbekannte Warteschlange. Benutze &f/bosshunt queues&c.");
return;
}
GameState state = gameManager.getState(queueId);
send(sender, "&fWarteschlange: &e" + queueId);
send(sender, "&fStatus: " + stateText(state));
send(sender, "&fSpieler: &b" + gameManager.lobbySize(queueId) + "&7/&b" + gameManager.maxPlayers());
List<String> onlineLobbyPlayers = new ArrayList<>();
for (UUID uuid : gameManager.lobbyPlayers(queueId)) {
Player player = Bukkit.getPlayer(uuid);
if (player != null) {
onlineLobbyPlayers.add(player.getName());
}
}
if (onlineLobbyPlayers.isEmpty()) {
send(sender, "&7Warteschlange ist leer.");
} else {
send(sender, "&fIn der Warteschlange: &e" + String.join("&7, &e", onlineLobbyPlayers));
}
}
private String stateText(GameState state) {
return switch (state) {
case WAITING -> "&7WAITING";
case COUNTDOWN -> "&eCOUNTDOWN";
case RUNNING -> "&aRUNNING";
};
}
private void send(CommandSender sender, String message) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', PREFIX + message));
}
}
@@ -0,0 +1,126 @@
package de.winniepat.pierredufaulersack.commands;
import de.winniepat.pierredufaulersack.kits.*;
import java.util.*;
import org.bukkit.ChatColor;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
public class KitCommand implements CommandExecutor, TabCompleter {
private static final String ADMIN_PERMISSION = "pierredufaulersack.admin";
private static final String PREFIX = "&8[&6BossHunt&8] &r";
private final KitService kitService;
private final KitType type;
public KitCommand(KitService kitService, KitType type) {
this.kitService = kitService;
this.type = type;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission(ADMIN_PERMISSION)) {
send(sender, "&cKeine Berechtigung.");
return true;
}
if (args.length < 1) {
send(sender, "&cNutzung: &f/" + label + " <add|set|del|list> [1-3]");
return true;
}
String action = args[0].toLowerCase();
switch (action) {
case "list" -> handleList(sender, label);
case "add", "set", "del" -> handleKitMutation(sender, label, action, args);
default -> send(sender, "&cUnbekannter Unterbefehl. &7Nutzung: &f/" + label + " <add|set|del|list> [1-3]");
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
return List.of("add", "set", "del", "list").stream()
.filter(opt -> opt.startsWith(args[0].toLowerCase()))
.toList();
}
if (args.length == 2 && !"list".equalsIgnoreCase(args[0])) {
List<String> slots = List.of("1", "2", "3");
List<String> filtered = new ArrayList<>();
for (String slot : slots) {
if (slot.startsWith(args[1])) {
filtered.add(slot);
}
}
return filtered;
}
return List.of();
}
private void handleList(CommandSender sender, String label) {
List<Integer> slots = kitService.listKitSlots(type);
if (slots.isEmpty()) {
send(sender, "&eKeine Kits für &f/" + label + " &egespeichert.");
return;
}
send(sender, "&aVerfügbare Kits fuer &f/" + label + "&a: &e" + slots);
}
private void handleKitMutation(CommandSender sender, String label, String action, String[] args) {
if (args.length != 2) {
send(sender, "&cNutzung: &f/" + label + " " + action + " <1-3>");
return;
}
Integer slot = parseSlot(args[1]);
if (slot == null) {
send(sender, "&cSlot muss 1, 2 oder 3 sein.");
return;
}
if ("del".equals(action)) {
boolean deleted = kitService.deleteKit(type, slot);
send(sender, deleted ? "&aKit &e" + slot + " &agelöscht." : "&eKit &f" + slot + " &eexistiert nicht.");
return;
}
if (!(sender instanceof Player player)) {
send(sender, "&cNur Spieler können Inventar-Kits speichern.");
return;
}
if ("add".equals(action)) {
boolean added = kitService.addKit(type, slot, player);
send(sender, added
? "&aKit &e" + slot + " &agespeichert."
: "&eKit &f" + slot + " &eexistiert bereits. Nutze &fset &ezum Ueberschreiben.");
return;
}
kitService.setKit(type, slot, player);
send(sender, "&aKit &e" + slot + " &agesetzt.");
}
private Integer parseSlot(String value) {
try {
int parsed = Integer.parseInt(value);
if (parsed < 1 || parsed > 3) {
return null;
}
return parsed;
} catch (NumberFormatException ignored) {
return null;
}
}
private void send(CommandSender sender, String message) {
sender.sendMessage(ChatColor.translateAlternateColorCodes('&', PREFIX + message));
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
package de.winniepat.pierredufaulersack.game;
public enum GameState {
WAITING,
COUNTDOWN,
RUNNING
}
@@ -0,0 +1,94 @@
package de.winniepat.pierredufaulersack.game;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.node.types.PrefixNode;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
public class LuckPermsService {
private static final String BOSS_PREFIX = "&c[ʙᴏꜱꜱ] &r";
private static final String HUNTER_PREFIX = "&9[ʜᴜɴᴛᴇʀ] &r";
private final JavaPlugin plugin;
private final Map<UUID, PrefixNode> appliedPrefixes = new ConcurrentHashMap<>();
private LuckPerms luckPerms;
public LuckPermsService(JavaPlugin plugin) {
this.plugin = plugin;
initialize();
}
public boolean isAvailable() {
return luckPerms != null;
}
public void applyBossPrefix(Player player) {
applyPrefix(player.getUniqueId(), BOSS_PREFIX, 100);
}
public void applyHunterPrefix(Player player) {
applyPrefix(player.getUniqueId(), HUNTER_PREFIX, 90);
}
public void clearPrefix(Player player) {
clearPrefix(player.getUniqueId());
}
public void clearPrefix(UUID playerId) {
if (!isAvailable()) {
return;
}
PrefixNode previous = appliedPrefixes.remove(playerId);
if (previous == null) {
return;
}
luckPerms.getUserManager().modifyUser(playerId, user -> user.transientData().remove(previous));
}
public void clearAll() {
Set<UUID> tracked = Set.copyOf(appliedPrefixes.keySet());
for (UUID playerId : tracked) {
clearPrefix(playerId);
}
}
private void applyPrefix(UUID playerId, String prefix, int priority) {
if (!isAvailable()) {
return;
}
PrefixNode next = PrefixNode.builder(prefix, priority).build();
PrefixNode previous = appliedPrefixes.put(playerId, next);
luckPerms.getUserManager().modifyUser(playerId, user -> {
if (previous != null) {
user.transientData().remove(previous);
}
user.transientData().add(next);
});
}
private void initialize() {
if (Bukkit.getPluginManager().getPlugin("LuckPerms") == null) {
plugin.getLogger().info("LuckPerms not found. Prefix integration disabled.");
return;
}
try {
luckPerms = LuckPermsProvider.get();
plugin.getLogger().info("LuckPerms detected. Prefix integration enabled.");
} catch (IllegalStateException ex) {
plugin.getLogger().warning("LuckPerms detected but API is not ready. Prefix integration disabled.");
luckPerms = null;
}
}
}
@@ -0,0 +1,7 @@
package de.winniepat.pierredufaulersack.game;
public enum Role {
BOSS,
HUNTER
}
@@ -0,0 +1,150 @@
package de.winniepat.pierredufaulersack.game;
import java.util.TreeSet;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.*;
public class SidebarService {
private static final String OBJECTIVE_ID = "bosshunt";
private static final String TEAM_BOSS = "0_boss";
private static final String TEAM_HUNTER_SAME_QUEUE = "1_hunter_same";
private static final String TEAM_HUNTER_OTHER_QUEUE = "1_hunter_other";
private static final String TEAM_NORMAL = "2_normal";
private final JavaPlugin plugin;
private final GameManager gameManager;
private BukkitTask refreshTask;
public SidebarService(JavaPlugin plugin, GameManager gameManager) {
this.plugin = plugin;
this.gameManager = gameManager;
}
public void start() {
refreshTask = Bukkit.getScheduler().runTaskTimer(plugin, this::refreshAll, 0L, 20L);
}
public void refreshAll() {
for (Player player : Bukkit.getOnlinePlayers()) {
showFor(player);
}
}
public void showFor(Player player) {
ScoreboardManager manager = Bukkit.getScoreboardManager();
if (manager == null) {
return;
}
Scoreboard board = manager.getNewScoreboard();
Objective objective = board.registerNewObjective(OBJECTIVE_ID, "dummy", color("&6&lʙᴏꜱꜱʜᴜɴᴛ"));
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
applyTablistOrderingTeams(board, player);
Role role = gameManager.getRole(player.getUniqueId());
if (role != null) {
String queueId = gameManager.queueOfPlayer(player.getUniqueId());
showRunningSession(objective, player, role, queueId);
} else {
showLobbyView(objective, player);
}
player.setScoreboard(board);
}
public void clearFor(Player player) {
ScoreboardManager manager = Bukkit.getScoreboardManager();
if (manager != null) {
player.setScoreboard(manager.getMainScoreboard());
}
}
public void shutdown() {
if (refreshTask != null) {
refreshTask.cancel();
refreshTask = null;
}
for (Player player : Bukkit.getOnlinePlayers()) {
clearFor(player);
}
}
private void showRunningSession(Objective objective, Player player, Role role, String queueId) {
int line = 9;
objective.getScore(" ").setScore(line--);
objective.getScore(color("&7Status: &aLÄUFT")).setScore(line--);
objective.getScore(" ").setScore(line--);
objective.getScore(color("&fQueue: &e" + queueId)).setScore(line--);
objective.getScore(color("&fRolle: " + roleText(role))).setScore(line--);
objective.getScore(color("&fHunter Verbleibend: &b" + gameManager.huntersLeft(player.getUniqueId()))).setScore(line--);
objective.getScore(" ").setScore(line--);
objective.getScore(color("&eKämpft!")).setScore(line);
}
private void applyTablistOrderingTeams(Scoreboard board, Player viewer) {
Team bosses = board.registerNewTeam(TEAM_BOSS);
Team huntersSameQueue = board.registerNewTeam(TEAM_HUNTER_SAME_QUEUE);
Team huntersOtherQueue = board.registerNewTeam(TEAM_HUNTER_OTHER_QUEUE);
Team normals = board.registerNewTeam(TEAM_NORMAL);
huntersSameQueue.setColor(ChatColor.RED);
String viewerQueue = gameManager.queueOfPlayer(viewer.getUniqueId());
for (Player online : Bukkit.getOnlinePlayers()) {
Role onlineRole = gameManager.getRole(online.getUniqueId());
if (onlineRole == Role.BOSS) {
bosses.addEntry(online.getName());
continue;
}
if (onlineRole == Role.HUNTER) {
String onlineQueue = gameManager.queueOfPlayer(online.getUniqueId());
if (viewerQueue != null && viewerQueue.equals(onlineQueue)) {
huntersSameQueue.addEntry(online.getName());
} else {
huntersOtherQueue.addEntry(online.getName());
}
continue;
}
normals.addEntry(online.getName());
}
}
private void showLobbyView(Objective objective, Player player) {
int line = 12;
String selected = gameManager.getSelectedQueue(player.getUniqueId());
String currentQueue = gameManager.queueOfPlayer(player.getUniqueId());
String selectedText = (selected == null || selected.isBlank()) ? "-" : selected;
objective.getScore(color("&7Status: &7LOBBY")).setScore(line--);
if (currentQueue != null) {
objective.getScore(color("&fQueued: &aJa (&e" + currentQueue + "&a)")).setScore(line--);
}
objective.getScore(" ").setScore(line--);
for (String queueId : new TreeSet<>(gameManager.queueIds())) {
objective.getScore(color("&e" + queueId + ": &f" + gameManager.lobbySize(queueId) + "&7/&f" + gameManager.maxPlayers())).setScore(line--);
}
}
private String roleText(Role role) {
if (role == Role.BOSS) {
return "&6BOSS";
}
return "&bHUNTER";
}
private String color(String text) {
return ChatColor.translateAlternateColorCodes('&', text);
}
}
@@ -0,0 +1,346 @@
package de.winniepat.pierredufaulersack.kits;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.IntPredicate;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
public class KitService {
private static final int MIN_SLOT = 1;
private static final int MAX_SLOT = 3;
private static final int KIT_CONTENT_SIZE = 41;
private final JavaPlugin plugin;
public KitService(JavaPlugin plugin) {
this.plugin = plugin;
}
public boolean addKit(KitType type, int slot, Player player) {
validateSlot(slot);
String path = path(type, slot);
if (plugin.getConfig().contains(path)) {
return false;
}
setKit(type, slot, player);
return true;
}
public void setKit(KitType type, int slot, Player player) {
setKit(type, slot, player.getInventory().getContents());
}
public void setKit(KitType type, int slot, ItemStack[] contents) {
validateSlot(slot);
ItemStack[] normalizedContents = normalizeContents(contents);
List<Map<String, Object>> serialized = new ArrayList<>(normalizedContents.length);
for (ItemStack item : normalizedContents) {
serialized.add(item == null ? null : item.clone().serialize());
}
plugin.getConfig().set(path(type, slot), serialized);
plugin.saveConfig();
}
public boolean deleteKit(KitType type, int slot) {
validateSlot(slot);
String path = path(type, slot);
if (!plugin.getConfig().contains(path)) {
return false;
}
plugin.getConfig().set(path, null);
plugin.saveConfig();
return true;
}
public List<Integer> listKitSlots(KitType type) {
List<Integer> slots = new ArrayList<>();
for (int slot = MIN_SLOT; slot <= MAX_SLOT; slot++) {
if (hasKit(type, slot)) {
slots.add(slot);
}
}
return slots;
}
public boolean hasKit(KitType type, int slot) {
validateSlot(slot);
return plugin.getConfig().contains(path(type, slot));
}
public Optional<List<ItemStack>> getKit(KitType type, int slot) {
validateSlot(slot);
List<?> raw = plugin.getConfig().getList(path(type, slot));
if (raw == null || raw.isEmpty()) {
return Optional.empty();
}
List<ItemStack> saved = new ArrayList<>(KIT_CONTENT_SIZE);
boolean hasItem = false;
for (int index = 0; index < KIT_CONTENT_SIZE; index++) {
Object element = index < raw.size() ? raw.get(index) : null;
ItemStack item = deserializeItem(element);
if (item != null) {
hasItem = true;
}
saved.add(item);
}
if (!hasItem) {
return Optional.empty();
}
return Optional.of(cloneInventory(saved));
}
public Optional<List<ItemStack>> getKitForPlayer(UUID playerId, KitType type, int slot) {
Optional<List<ItemStack>> maybeBase = getKit(type, slot);
if (maybeBase.isEmpty()) {
return Optional.empty();
}
List<ItemStack> base = maybeBase.get();
Optional<int[]> maybeLayout = getPlayerLayout(playerId, type, slot);
if (maybeLayout.isEmpty()) {
return Optional.of(cloneInventory(base));
}
return Optional.of(applyLayout(base, maybeLayout.get()));
}
public boolean setPlayerLayout(UUID playerId, KitType type, int slot, ItemStack[] arrangedContents) {
validateSlot(slot);
Optional<List<ItemStack>> maybeBase = getKit(type, slot);
if (maybeBase.isEmpty()) {
return false;
}
List<ItemStack> base = maybeBase.get();
ItemStack[] arranged = normalizeContents(arrangedContents);
int[] layout = deriveLayout(base, arranged);
List<Integer> serialized = new ArrayList<>(layout.length);
for (int value : layout) {
serialized.add(value);
}
plugin.getConfig().set(playerLayoutPath(playerId, type, slot), serialized);
plugin.saveConfig();
return true;
}
private Optional<int[]> getPlayerLayout(UUID playerId, KitType type, int slot) {
String path = playerLayoutPath(playerId, type, slot);
List<?> raw = plugin.getConfig().getList(path);
if (raw == null || raw.size() != KIT_CONTENT_SIZE) {
return Optional.empty();
}
int[] layout = new int[KIT_CONTENT_SIZE];
for (int i = 0; i < KIT_CONTENT_SIZE; i++) {
Object element = raw.get(i);
if (!(element instanceof Number number)) {
return Optional.empty();
}
int value = number.intValue();
if (value < 0 || value >= KIT_CONTENT_SIZE) {
return Optional.empty();
}
layout[i] = value;
}
return Optional.of(layout);
}
private static List<ItemStack> applyLayout(List<ItemStack> base, int[] layout) {
List<ItemStack> arranged = new ArrayList<>(KIT_CONTENT_SIZE);
boolean[] consumed = new boolean[KIT_CONTENT_SIZE];
for (int target = 0; target < KIT_CONTENT_SIZE; target++) {
int source = target < layout.length ? layout[target] : target;
if (source < 0 || source >= KIT_CONTENT_SIZE || consumed[source]) {
source = firstUnused(consumed, target);
}
consumed[source] = true;
ItemStack item = source < base.size() ? base.get(source) : null;
arranged.add(item == null ? null : item.clone());
}
return arranged;
}
private static int[] deriveLayout(List<ItemStack> base, ItemStack[] arranged) {
int[] layout = new int[KIT_CONTENT_SIZE];
boolean[] used = new boolean[KIT_CONTENT_SIZE];
for (int target = 0; target < KIT_CONTENT_SIZE; target++) {
ItemStack desired = arranged[target];
int matched = -1;
for (int source = 0; source < KIT_CONTENT_SIZE; source++) {
if (used[source]) {
continue;
}
ItemStack sourceItem = source < base.size() ? base.get(source) : null;
if (isSameStack(sourceItem, desired)) {
matched = source;
break;
}
}
if (matched == -1) {
matched = firstUnused(used, target);
}
used[matched] = true;
layout[target] = matched;
}
return layout;
}
private static int firstUnused(boolean[] used, int fallback) {
if (fallback >= 0 && fallback < used.length && !used[fallback]) {
return fallback;
}
for (int i = 0; i < used.length; i++) {
if (!used[i]) {
return i;
}
}
return 0;
}
private static boolean isSameStack(ItemStack a, ItemStack b) {
if (a == null || a.getType().isAir()) {
return b == null || b.getType().isAir();
}
if (b == null || b.getType().isAir()) {
return false;
}
return a.getAmount() == b.getAmount() && a.isSimilar(b);
}
private static ItemStack deserializeItem(Object element) {
if (element == null) {
return null;
}
if (element instanceof ItemStack itemStack) {
return itemStack.clone();
}
if (element instanceof Map<?, ?> map) {
Map<String, Object> serialized = new LinkedHashMap<>();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() instanceof String key) {
serialized.put(key, entry.getValue());
}
}
if (!serialized.isEmpty()) {
try {
return ItemStack.deserialize(serialized);
} catch (IllegalArgumentException ignored) {
return null;
}
}
}
return null;
}
public OptionalInt rollKitSlot(KitType type, IntPredicate allowedSlot) {
List<Integer> slots = new ArrayList<>();
List<Integer> weights = new ArrayList<>();
int total = 0;
for (int slot = MIN_SLOT; slot <= MAX_SLOT; slot++) {
if (!allowedSlot.test(slot) || !hasKit(type, slot)) {
continue;
}
int weight = getChance(type, slot);
if (weight <= 0) {
continue;
}
slots.add(slot);
weights.add(weight);
total += weight;
}
if (total <= 0) {
return OptionalInt.empty();
}
int roll = ThreadLocalRandom.current().nextInt(total);
int cursor = 0;
for (int i = 0; i < slots.size(); i++) {
cursor += weights.get(i);
if (roll < cursor) {
return OptionalInt.of(slots.get(i));
}
}
return OptionalInt.empty();
}
public OptionalInt rollKitSlot(KitType type) {
return rollKitSlot(type, slot -> true);
}
private int getChance(KitType type, int slot) {
String chancePath = "kit-chances." + type.key() + "." + slot;
return plugin.getConfig().getInt(chancePath, 0);
}
private static ItemStack[] normalizeContents(ItemStack[] contents) {
ItemStack[] normalized = new ItemStack[KIT_CONTENT_SIZE];
if (contents == null) {
return normalized;
}
int copyLength = Math.min(KIT_CONTENT_SIZE, contents.length);
for (int index = 0; index < copyLength; index++) {
ItemStack item = contents[index];
normalized[index] = item == null ? null : item.clone();
}
return normalized;
}
private static List<ItemStack> cloneInventory(List<ItemStack> contents) {
List<ItemStack> cloned = new ArrayList<>(contents.size());
for (ItemStack item : contents) {
cloned.add(item == null ? null : item.clone());
}
return cloned;
}
private static String path(KitType type, int slot) {
return "kits." + type.key() + "." + slot;
}
private static String playerLayoutPath(UUID playerId, KitType type, int slot) {
return "player-kit-layouts." + playerId + "." + type.key() + "." + slot;
}
private static void validateSlot(int slot) {
if (slot < MIN_SLOT || slot > MAX_SLOT) {
throw new IllegalArgumentException("Slot must be between 1 and 3");
}
}
}
@@ -0,0 +1,17 @@
package de.winniepat.pierredufaulersack.kits;
public enum KitType {
BOSS("boss"),
HUNTER("hunter");
private final String key;
KitType(String key) {
this.key = key;
}
public String key() {
return key;
}
}
@@ -0,0 +1,59 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.game.GameManager;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class CobwebDecayListener implements Listener {
private static final long COBWEB_LIFETIME_TICKS = 20L * 10L;
private final JavaPlugin plugin;
private final GameManager gameManager;
public CobwebDecayListener(JavaPlugin plugin, GameManager gameManager) {
this.plugin = plugin;
this.gameManager = gameManager;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlace(BlockPlaceEvent event) {
if (event.getBlockPlaced().getType() != Material.COBWEB) {
return;
}
Player player = event.getPlayer();
if (gameManager.getRole(player.getUniqueId()) == null) {
return;
}
Location location = event.getBlockPlaced().getLocation();
UUID worldId = location.getWorld() == null ? null : location.getWorld().getUID();
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
if (worldId == null) {
return;
}
World world = plugin.getServer().getWorld(worldId);
if (world == null) {
return;
}
if (world.getBlockAt(x, y, z).getType() == Material.COBWEB) {
world.getBlockAt(x, y, z).setType(Material.AIR, false);
}
}, COBWEB_LIFETIME_TICKS);
}
}
@@ -0,0 +1,44 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.game.GameManager;
import org.bukkit.entity.*;
import org.bukkit.event.*;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
public class FriendlyFireListener implements Listener {
private final GameManager gameManager;
public FriendlyFireListener(GameManager gameManager) {
this.gameManager = gameManager;
}
@EventHandler(ignoreCancelled = true)
public void onDamage(EntityDamageByEntityEvent event) {
if (!gameManager.isRunning()) {
return;
}
Player attacker = asPlayerDamager(event.getDamager());
if (attacker == null || !(event.getEntity() instanceof Player victim)) {
return;
}
if (gameManager.areBothHunters(attacker.getUniqueId(), victim.getUniqueId())) {
event.setCancelled(true);
}
}
private Player asPlayerDamager(Entity entity) {
if (entity instanceof Player player) {
return player;
}
if (entity instanceof Projectile projectile && projectile.getShooter() instanceof Player shooter) {
return shooter;
}
return null;
}
}
@@ -0,0 +1,36 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.game.*;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.*;
import org.bukkit.event.player.*;
public class PlayerConnectionListener implements Listener {
private final GameManager gameManager;
private final SidebarService sidebarService;
public PlayerConnectionListener(GameManager gameManager, SidebarService sidebarService) {
this.gameManager = gameManager;
this.sidebarService = sidebarService;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
sidebarService.showFor(event.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
gameManager.handlePlayerQuit(event.getPlayer());
sidebarService.clearFor(event.getPlayer());
event.setQuitMessage(null);
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(Component.text("§4- §r" + event.getPlayer().getDisplayName()));
}
}
}
@@ -0,0 +1,21 @@
package de.winniepat.pierredufaulersack.listeners;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
public class PlayerDeathListener implements Listener {
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
event.setDeathMessage(null);
Player victim = event.getEntity();
victim.getInventory().clear();
victim.getInventory().setArmorContents(new ItemStack[0]);
victim.getInventory().setItemInOffHand(new ItemStack(Material.AIR));
}
}
@@ -0,0 +1,757 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.Pierredufaulersack;
import de.winniepat.pierredufaulersack.game.GameManager;
import de.winniepat.pierredufaulersack.kits.KitService;
import de.winniepat.pierredufaulersack.kits.KitType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.potion.PotionEffect;
public class PlayerJoinListener implements Listener {
private static final String SELECTOR_KEY = "queue_selector";
private static final String QUEUE_ITEM_KEY = "queue_item";
private static final String LEAVE_ITEM_KEY = "leave_item";
private static final String KIT_EDITOR_KEY = "kit_editor";
private static final String KIT_TYPE_ITEM_KEY = "kit_editor_type";
private static final String KIT_SLOT_ITEM_KEY = "kit_editor_slot";
private static final String LEAVE_ACTION = "leave";
private static final String QUEUE_MENU_TITLE = "&8Queue Auswahl";
private static final String KIT_TYPE_MENU_TITLE = "&8Kit Editor - Rolle";
private static final int KIT_STORAGE_SIZE = 36;
private static final int KIT_TOTAL_SIZE = 41;
private static final int EDITOR_SIZE = 54;
private static final int EDITOR_BOOTS_SLOT = 45;
private static final int EDITOR_LEGGINGS_SLOT = 46;
private static final int EDITOR_CHESTPLATE_SLOT = 47;
private static final int EDITOR_HELMET_SLOT = 48;
private static final int EDITOR_OFFHAND_SLOT = 50;
private final Pierredufaulersack plugin;
private final GameManager gameManager;
private final KitService kitService;
private final NamespacedKey selectorItemKey;
private final NamespacedKey queueItemKey;
private final NamespacedKey leaveItemKey;
private final NamespacedKey kitEditorItemKey;
private final NamespacedKey kitTypeItemKey;
private final NamespacedKey kitSlotItemKey;
public PlayerJoinListener(Pierredufaulersack plugin, GameManager gameManager, KitService kitService) {
this.plugin = plugin;
this.gameManager = gameManager;
this.kitService = kitService;
this.selectorItemKey = new NamespacedKey(plugin, SELECTOR_KEY);
this.queueItemKey = new NamespacedKey(plugin, QUEUE_ITEM_KEY);
this.leaveItemKey = new NamespacedKey(plugin, LEAVE_ITEM_KEY);
this.kitEditorItemKey = new NamespacedKey(plugin, KIT_EDITOR_KEY);
this.kitTypeItemKey = new NamespacedKey(plugin, KIT_TYPE_ITEM_KEY);
this.kitSlotItemKey = new NamespacedKey(plugin, KIT_SLOT_ITEM_KEY);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
gameManager.handlePlayerQuit(player);
event.setJoinMessage(null);
for (Player p : Bukkit.getOnlinePlayers()) {
p.sendMessage(Component.text("§a+ §r" + player.getDisplayName()));
}
Location lobby = loadLocation("locations.lobby");
if (lobby != null) {
player.teleport(lobby);
}
player.getInventory().clear();
player.getInventory().setArmorContents(new ItemStack[4]);
player.getInventory().setItemInOffHand(null);
for (PotionEffect effect : player.getActivePotionEffects()) {
player.removePotionEffect(effect.getType());
}
Bukkit.getScheduler().runTask(plugin, () -> giveLobbyItems(player));
}
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
Bukkit.getScheduler().runTask(plugin, () -> {
if (gameManager.getRole(player.getUniqueId()) != null) {
return;
}
if (isLobbyLocation(player.getLocation()) || isLobbyLocation(event.getRespawnLocation())) {
giveLobbyItems(player);
}
});
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent event) {
if (isLobbyLocation(event.getTo())) {
Bukkit.getScheduler().runTask(plugin, () -> giveLobbyItems(event.getPlayer()));
}
}
@EventHandler
public void onSelectorUse(PlayerInteractEvent event) {
if (event.getHand() != EquipmentSlot.HAND) {
return;
}
Action action = event.getAction();
if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) {
return;
}
ItemStack used = event.getItem();
if (used == null) {
used = event.getPlayer().getInventory().getItemInMainHand();
}
Player player = event.getPlayer();
if (gameManager.getRole(player.getUniqueId()) != null) {
return;
}
if (isQueueSelector(used)) {
event.setCancelled(true);
Bukkit.getScheduler().runTask(plugin, () -> openQueueSelectorMenu(player));
return;
}
if (isKitEditorOpener(used)) {
event.setCancelled(true);
Bukkit.getScheduler().runTask(plugin, () -> openKitTypeMenu(player));
}
}
@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
Inventory topInventory = event.getView().getTopInventory();
if (isQueueMenu(topInventory)) {
event.setCancelled(true);
if (event.getClickedInventory() == null || event.getClickedInventory() != topInventory) {
return;
}
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
ItemStack clicked = event.getCurrentItem();
if (clicked == null || !clicked.hasItemMeta()) {
return;
}
ItemMeta meta = clicked.getItemMeta();
if (meta == null) {
return;
}
String queueId = meta.getPersistentDataContainer().get(queueItemKey, PersistentDataType.STRING);
String leaveAction = meta.getPersistentDataContainer().get(leaveItemKey, PersistentDataType.STRING);
if (LEAVE_ACTION.equals(leaveAction)) {
gameManager.leave(player);
gameManager.clearSelectedQueue(player);
giveLobbyItems(player);
player.closeInventory();
return;
}
if (queueId == null || queueId.isBlank()) {
return;
}
gameManager.selectQueue(player, queueId);
gameManager.join(player, queueId);
giveLobbyItems(player);
player.closeInventory();
return;
}
if (isKitTypeMenu(topInventory)) {
event.setCancelled(true);
if (event.getClickedInventory() == null || event.getClickedInventory() != topInventory) {
return;
}
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
ItemStack clicked = event.getCurrentItem();
if (clicked == null || !clicked.hasItemMeta()) {
return;
}
ItemMeta meta = clicked.getItemMeta();
if (meta == null) {
return;
}
String rawType = meta.getPersistentDataContainer().get(kitTypeItemKey, PersistentDataType.STRING);
KitType type = parseKitType(rawType);
if (type == null) {
return;
}
openKitSlotMenu(player, type);
return;
}
if (topInventory != null && topInventory.getHolder() instanceof KitSlotMenuHolder slotHolder) {
event.setCancelled(true);
if (event.getClickedInventory() == null || event.getClickedInventory() != topInventory) {
return;
}
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
ItemStack clicked = event.getCurrentItem();
if (clicked == null || !clicked.hasItemMeta()) {
return;
}
ItemMeta meta = clicked.getItemMeta();
if (meta == null) {
return;
}
Integer slot = meta.getPersistentDataContainer().get(kitSlotItemKey, PersistentDataType.INTEGER);
if (slot == null || slot < 1 || slot > 3) {
return;
}
openKitEditor(player, slotHolder.type(), slot);
return;
}
if (topInventory.getHolder() instanceof KitEditorMenuHolder) {
if (event.getClickedInventory() == null) {
return;
}
if (event.getClickedInventory() != topInventory) {
event.setCancelled(true);
return;
}
if (event.isShiftClick()
|| event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY
|| event.getAction() == InventoryAction.DROP_ALL_SLOT
|| event.getAction() == InventoryAction.DROP_ONE_SLOT
|| event.getAction() == InventoryAction.DROP_ALL_CURSOR
|| event.getAction() == InventoryAction.DROP_ONE_CURSOR
|| event.getClick() == ClickType.NUMBER_KEY
|| event.getClick() == ClickType.SWAP_OFFHAND
|| event.getClick() == ClickType.DOUBLE_CLICK
|| event.getClick() == ClickType.DROP
|| event.getClick() == ClickType.CONTROL_DROP) {
event.setCancelled(true);
}
}
}
@EventHandler(ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
Inventory topInventory = event.getView().getTopInventory();
if (isQueueMenu(topInventory)
|| isKitTypeMenu(topInventory)
|| topInventory.getHolder() instanceof KitSlotMenuHolder) {
event.setCancelled(true);
return;
}
if (topInventory.getHolder() instanceof KitEditorMenuHolder) {
int topSize = topInventory.getSize();
for (int rawSlot : event.getRawSlots()) {
if (rawSlot >= topSize) {
event.setCancelled(true);
return;
}
}
}
}
@EventHandler
public void onKitEditorClose(InventoryCloseEvent event) {
Inventory inventory = event.getInventory();
if (!(inventory.getHolder() instanceof KitEditorMenuHolder editorHolder)) {
return;
}
if (event.getPlayer() instanceof Player player) {
ItemStack cursor = player.getItemOnCursor();
if (hasItem(cursor)) {
ItemStack leftover = placeIntoEditableSlots(inventory, cursor.clone());
if (hasItem(leftover)) {
player.getWorld().dropItemNaturally(player.getLocation(), leftover);
}
player.setItemOnCursor(null);
}
boolean saved = kitService.setPlayerLayout(
player.getUniqueId(),
editorHolder.type(),
editorHolder.slot(),
extractKitFromEditor(inventory));
if (!saved) {
player.sendMessage(color("&8[&6BossHunt&8] &cKein Basis-Kit vorhanden. Layout nicht gespeichert."));
return;
}
player.sendMessage(color("&8[&6BossHunt&8] &aKit &e" + editorHolder.slot() + " &afür &e"
+ typeDisplayName(editorHolder.type()) + " &aAnordnung gespeichert."));
return;
}
// Non-player close is rare, but keep layout persistence consistent.
kitService.setPlayerLayout(
event.getPlayer().getUniqueId(),
editorHolder.type(),
editorHolder.slot(),
extractKitFromEditor(inventory));
}
private void giveLobbyItems(Player player) {
if (gameManager.getRole(player.getUniqueId()) != null) {
return;
}
ItemStack selector = new ItemStack(Material.COMPASS);
ItemMeta selectorMeta = selector.getItemMeta();
if (selectorMeta != null) {
String selected = gameManager.getSelectedQueue(player.getUniqueId());
String selectedText = selected == null || selected.isBlank() ? "None" : selected;
selectorMeta.setDisplayName(color("&6Queue Auswahl"));
selectorMeta.setLore(List.of(
color("&7Gerade: &e" + selectedText),
color("&7Clicke um das Menü zu öffnen")));
selectorMeta.getPersistentDataContainer().set(selectorItemKey, PersistentDataType.BYTE, (byte) 1);
selector.setItemMeta(selectorMeta);
}
ItemStack kitEditor = new ItemStack(Material.CHEST);
ItemMeta editorMeta = kitEditor.getItemMeta();
if (editorMeta != null) {
editorMeta.setDisplayName(color("&dKit Editor"));
editorMeta.setLore(List.of(
color("&7Clicke um Boss/Hunter Kits"),
color("&7für Slot 1-3 zu bearbeiten")));
editorMeta.getPersistentDataContainer().set(kitEditorItemKey, PersistentDataType.BYTE, (byte) 1);
kitEditor.setItemMeta(editorMeta);
}
player.getInventory().setItem(4, selector);
player.getInventory().setItem(6, kitEditor);
}
private boolean isQueueSelector(ItemStack item) {
if (item == null || item.getType() != Material.COMPASS || !item.hasItemMeta()) {
return false;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}
Byte marker = meta.getPersistentDataContainer().get(selectorItemKey, PersistentDataType.BYTE);
if (marker != null && marker == (byte) 1) {
return true;
}
String displayName = meta.getDisplayName();
return displayName != null && displayName.equals(color("&6Queue Selector"));
}
private boolean isKitEditorOpener(ItemStack item) {
if (item == null || item.getType() != Material.CHEST || !item.hasItemMeta()) {
return false;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}
Byte marker = meta.getPersistentDataContainer().get(kitEditorItemKey, PersistentDataType.BYTE);
return marker != null && marker == (byte) 1;
}
private boolean isLobbyLocation(Location location) {
Location lobby = loadLocation("locations.lobby");
if (location == null || lobby == null) {
return false;
}
if (location.getWorld() == null || lobby.getWorld() == null) {
return false;
}
if (!location.getWorld().getUID().equals(lobby.getWorld().getUID())) {
return false;
}
return location.distanceSquared(lobby) <= 4.0;
}
private void openQueueSelectorMenu(Player player) {
List<String> queueIds = new ArrayList<>(gameManager.queueIds());
if (queueIds.isEmpty()) {
return;
}
Collections.sort(queueIds);
int requiredSlots = queueIds.size() + 1;
int size = 9;
while (size < requiredSlots) {
size += 9;
}
Inventory menu = Bukkit.createInventory(new QueueMenuHolder(), size, color(QUEUE_MENU_TITLE));
String selectedQueue = gameManager.getSelectedQueue(player.getUniqueId());
int slot = 0;
for (String queueId : queueIds) {
if (slot == 8) {
slot++;
}
if (slot >= size) {
break;
}
boolean selected = queueId.equals(selectedQueue);
menu.setItem(slot, createQueueMenuItem(queueId, selected));
slot++;
}
if (size > 8) {
menu.setItem(8, createLeaveMenuItem());
}
player.openInventory(menu);
}
private void openKitTypeMenu(Player player) {
Inventory menu = Bukkit.createInventory(new KitTypeMenuHolder(), 9, color(KIT_TYPE_MENU_TITLE));
menu.setItem(3, createKitTypeMenuItem(KitType.BOSS));
menu.setItem(5, createKitTypeMenuItem(KitType.HUNTER));
player.openInventory(menu);
}
private void openKitSlotMenu(Player player, KitType type) {
Inventory menu = Bukkit.createInventory(new KitSlotMenuHolder(type), 9,
color("&8Kit Editor - " + typeDisplayName(type)));
menu.setItem(2, createKitSlotMenuItem(type, 1));
menu.setItem(4, createKitSlotMenuItem(type, 2));
menu.setItem(6, createKitSlotMenuItem(type, 3));
player.openInventory(menu);
}
private void openKitEditor(Player player, KitType type, int slot) {
Inventory editor = Bukkit.createInventory(new KitEditorMenuHolder(type, slot), EDITOR_SIZE,
color("&8Edit " + typeDisplayName(type) + " Kit " + slot));
kitService.getKitForPlayer(player.getUniqueId(), type, slot)
.ifPresent(kit -> editor.setContents(buildEditorContents(kit)));
player.openInventory(editor);
player.sendMessage(color("&8[&6BossHunt&8] &7Bearbeite das Kit und schließe das Inventar zum Speichern."));
player.sendMessage(color("&8[&6BossHunt&8] &7Armor: Slots 46-49, Offhand: Slot 51."));
}
private ItemStack createQueueMenuItem(String queueId, boolean selected) {
Material material = selected ? Material.LIME_WOOL : Material.GRAY_WOOL;
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
meta.setDisplayName(color((selected ? "&a" : "&e") + queueId));
meta.setLore(List.of(
color("&7Spieler: &f" + gameManager.lobbySize(queueId) + "&7/&f" + gameManager.maxPlayers()),
color("&7Status: &f" + gameManager.getState(queueId).name()),
color(selected ? "&aGerade ausgewählt" : "&eClicke zum auswählen")));
meta.getPersistentDataContainer().set(queueItemKey, PersistentDataType.STRING, queueId);
item.setItemMeta(meta);
return item;
}
private ItemStack createLeaveMenuItem() {
ItemStack item = new ItemStack(Material.BARRIER);
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
meta.setDisplayName(color("&cLeave Queue/Lobby"));
meta.setLore(List.of(
color("&7Click um die queue zu verlassen")));
meta.getPersistentDataContainer().set(leaveItemKey, PersistentDataType.STRING, LEAVE_ACTION);
item.setItemMeta(meta);
return item;
}
private ItemStack createKitTypeMenuItem(KitType type) {
Material material = type == KitType.BOSS ? Material.NETHERITE_CHESTPLATE : Material.IRON_CHESTPLATE;
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
String display = typeDisplayName(type);
meta.setDisplayName(color("&e" + display));
meta.setLore(List.of(color("&7Clicke um " + display + " Kits zu bearbeiten")));
meta.getPersistentDataContainer().set(kitTypeItemKey, PersistentDataType.STRING, type.key());
item.setItemMeta(meta);
return item;
}
private ItemStack createKitSlotMenuItem(KitType type, int slot) {
boolean hasKit = kitService.hasKit(type, slot);
ItemStack item = new ItemStack(hasKit ? Material.LIME_SHULKER_BOX : Material.GRAY_SHULKER_BOX);
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
meta.setDisplayName(color("&eKit " + slot));
meta.setLore(List.of(
color(hasKit ? "&aBereits gespeichert" : "&7Noch leer"),
color("&7Clicke zum Bearbeiten")));
meta.getPersistentDataContainer().set(kitSlotItemKey, PersistentDataType.INTEGER, slot);
item.setItemMeta(meta);
return item;
}
private KitType parseKitType(String raw) {
if (raw == null) {
return null;
}
for (KitType type : KitType.values()) {
if (type.key().equalsIgnoreCase(raw)) {
return type;
}
}
return null;
}
private String typeDisplayName(KitType type) {
return type == KitType.BOSS ? "Boss" : "Hunter";
}
private ItemStack[] buildEditorContents(List<ItemStack> kit) {
ItemStack[] editor = new ItemStack[EDITOR_SIZE];
for (int i = 0; i < Math.min(KIT_STORAGE_SIZE, kit.size()); i++) {
ItemStack item = kit.get(i);
editor[i] = item == null ? null : item.clone();
}
editor[EDITOR_BOOTS_SLOT] = cloneAt(kit, 36);
editor[EDITOR_LEGGINGS_SLOT] = cloneAt(kit, 37);
editor[EDITOR_CHESTPLATE_SLOT] = cloneAt(kit, 38);
editor[EDITOR_HELMET_SLOT] = cloneAt(kit, 39);
editor[EDITOR_OFFHAND_SLOT] = cloneAt(kit, 40);
return editor;
}
private ItemStack[] extractKitFromEditor(Inventory inventory) {
ItemStack[] editorContents = inventory.getContents();
ItemStack[] kit = new ItemStack[KIT_TOTAL_SIZE];
for (int i = 0; i < KIT_STORAGE_SIZE; i++) {
ItemStack item = editorContents[i];
kit[i] = item == null ? null : item.clone();
}
kit[36] = cloneAt(editorContents, EDITOR_BOOTS_SLOT);
kit[37] = cloneAt(editorContents, EDITOR_LEGGINGS_SLOT);
kit[38] = cloneAt(editorContents, EDITOR_CHESTPLATE_SLOT);
kit[39] = cloneAt(editorContents, EDITOR_HELMET_SLOT);
kit[40] = cloneAt(editorContents, EDITOR_OFFHAND_SLOT);
return kit;
}
private ItemStack cloneAt(List<ItemStack> items, int index) {
if (index < 0 || index >= items.size()) {
return null;
}
ItemStack item = items.get(index);
return item == null ? null : item.clone();
}
private ItemStack cloneAt(ItemStack[] items, int index) {
if (index < 0 || index >= items.length) {
return null;
}
ItemStack item = items[index];
return item == null ? null : item.clone();
}
private boolean hasItem(ItemStack item) {
return item != null && item.getType() != Material.AIR;
}
private ItemStack placeIntoEditableSlots(Inventory inventory, ItemStack stack) {
ItemStack remaining = stack;
for (int slot = 0; slot < KIT_STORAGE_SIZE; slot++) {
if (remaining == null || remaining.getType() == Material.AIR) {
return null;
}
ItemStack existing = inventory.getItem(slot);
if (existing == null || existing.getType() == Material.AIR) {
inventory.setItem(slot, remaining);
return null;
}
if (existing.isSimilar(remaining) && existing.getAmount() < existing.getMaxStackSize()) {
int free = existing.getMaxStackSize() - existing.getAmount();
int move = Math.min(free, remaining.getAmount());
existing.setAmount(existing.getAmount() + move);
remaining.setAmount(remaining.getAmount() - move);
}
}
return remaining;
}
private boolean isQueueMenu(Inventory inventory) {
if (inventory == null) {
return false;
}
return inventory.getHolder() instanceof QueueMenuHolder;
}
private boolean isKitTypeMenu(Inventory inventory) {
if (inventory == null) {
return false;
}
return inventory.getHolder() instanceof KitTypeMenuHolder;
}
private static final class QueueMenuHolder implements InventoryHolder {
@Override
public Inventory getInventory() {
return null;
}
}
private static final class KitTypeMenuHolder implements InventoryHolder {
@Override
public Inventory getInventory() {
return null;
}
}
private static final class KitSlotMenuHolder implements InventoryHolder {
private final KitType type;
private KitSlotMenuHolder(KitType type) {
this.type = type;
}
private KitType type() {
return type;
}
@Override
public Inventory getInventory() {
return null;
}
}
private static final class KitEditorMenuHolder implements InventoryHolder {
private final KitType type;
private final int slot;
private KitEditorMenuHolder(KitType type, int slot) {
this.type = type;
this.slot = slot;
}
private KitType type() {
return type;
}
private int slot() {
return slot;
}
@Override
public Inventory getInventory() {
return null;
}
}
private String color(String text) {
return ChatColor.translateAlternateColorCodes('&', text);
}
private Location loadLocation(String path) {
FileConfiguration config = plugin.getConfig();
ConfigurationSection section = config.getConfigurationSection(path);
if (section == null) {
return null;
}
String worldName = section.getString("world");
if (worldName == null) {
return null;
}
World world = Bukkit.getWorld(worldName);
if (world == null) {
return null;
}
return new Location(
world,
section.getDouble("x"),
section.getDouble("y"),
section.getDouble("z"),
(float) section.getDouble("yaw"),
(float) section.getDouble("pitch"));
}
}
@@ -0,0 +1,38 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.game.GameManager;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.*;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
public class RoundOutcomeListener implements Listener {
private final GameManager gameManager;
public RoundOutcomeListener(GameManager gameManager) {
this.gameManager = gameManager;
}
@EventHandler(ignoreCancelled = true)
public void onDeath(PlayerDeathEvent event) {
Player victim = event.getEntity();
Player killer = victim.getKiller();
if (killer == null) {
return;
}
gameManager.handleKill(killer, victim);
}
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
Location lobby = gameManager.consumePendingLobbyRespawn(event.getPlayer().getUniqueId());
if (lobby != null) {
event.setRespawnLocation(lobby);
}
}
}
@@ -0,0 +1,35 @@
package de.winniepat.pierredufaulersack.listeners;
import de.winniepat.pierredufaulersack.game.GameManager;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
public class RoundWaterCleanupListener implements Listener {
private final GameManager gameManager;
public RoundWaterCleanupListener(GameManager gameManager) {
this.gameManager = gameManager;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onWaterBucketEmpty(PlayerBucketEmptyEvent event) {
if (event.getBucket() != Material.WATER_BUCKET) {
return;
}
Player player = event.getPlayer();
if (gameManager.getRole(player.getUniqueId()) == null) {
return;
}
Block placedAt = event.getBlockClicked().getRelative(event.getBlockFace());
gameManager.trackRoundWaterPlacement(player, placedAt.getLocation());
}
}
+66
View File
@@ -0,0 +1,66 @@
max-players: 5
max-concurrent-games: 3
hunter-kit-max-duplicates: 2
countdown:
seconds: 10
full-seconds: 10
not-full-seconds: 20
min-players: 2
announce-every-second: true
sounds:
countdown:
sound: BLOCK_NOTE_BLOCK_HAT
volume: 1.0
pitch: 1.4
countdown-final:
sound: BLOCK_NOTE_BLOCK_PLING
volume: 1.0
pitch: 1.8
start:
sound: ENTITY_PLAYER_LEVELUP
volume: 1.0
pitch: 1.0
stop:
sound: ENTITY_ITEM_BREAK
volume: 1.0
pitch: 1.0
killed:
sound: ENTITY_PLAYER_HURT
volume: 1.0
pitch: 1.0
hunter-win:
sound: ENTITY_PLAYER_LEVELUP
volume: 1.0
pitch: 1.2
boss-lose:
sound: ENTITY_VILLAGER_NO
volume: 1.0
pitch: 1.0
boss-win:
sound: ENTITY_ENDER_DRAGON_GROWL
volume: 1.0
pitch: 1.0
kit-chances:
boss:
1: 40
2: 40
3: 20
hunter:
1: 40
2: 40
3: 20
locations:
lobby: {}
arenas:
game1: {}
game2: {}
game3: {}
kits:
boss: {}
hunter: {}
+26
View File
@@ -0,0 +1,26 @@
name: Pierredufaulersack
version: '${version}'
main: de.winniepat.pierredufaulersack.Pierredufaulersack
api-version: '1.21'
authors: [ WinniePatGG ]
website: https://winniepat.de
description: Plugin für Boss Hunt Minigame by WinniePatGG
depend: [ LuckPerms ]
commands:
bosshunt:
description: Hauptbefehl für Boss Hunt
usage: /bosshunt <queues|select|join|leave|start|stop|setlobby|setarena|status>
bosskit:
description: Verwalte Boss Kits
usage: /bosskit <add|set|del|list> [1-3]
permission: pierredufaulersack.admin
hunterkit:
description: Verwalte Hunter Kits
usage: /hunterkit <add|set|del|list> [1-3]
permission: pierredufaulersack.admin
permissions:
pierredufaulersack.admin:
description: Admin Rechte für Boss Hunt
default: op