From 6e4513129371879933493d8f9aebcb3f1f25c9af Mon Sep 17 00:00:00 2001 From: M_Viper Date: Tue, 27 Jan 2026 14:43:48 +0100 Subject: [PATCH] Update from Git Manager GUI --- .../java/de/velocityfall/VelocityFall.java | 15 +- .../listeners/FloorLoseListener.java | 99 ++--- .../de/velocityfall/manager/GameManager.java | 346 ++++++++++++++++-- src/main/resources/config.yml | 1 + 4 files changed, 380 insertions(+), 81 deletions(-) diff --git a/src/main/java/de/velocityfall/VelocityFall.java b/src/main/java/de/velocityfall/VelocityFall.java index 27b74e6..bb9e1b9 100644 --- a/src/main/java/de/velocityfall/VelocityFall.java +++ b/src/main/java/de/velocityfall/VelocityFall.java @@ -44,6 +44,9 @@ public class VelocityFall extends JavaPlugin { private Stats stats; private DisableReset disablereset; + // Listener-Referenzen (für GameManager-Zugriff) + private FloorLoseListener floorLoseListener; + @Override public void onEnable() { instance = this; @@ -75,8 +78,13 @@ public class VelocityFall extends JavaPlugin { getCommand("velocityfall").setExecutor(new VelocityCommand(this)); + // Listener registrieren Bukkit.getPluginManager().registerEvents(new FloorListener(this), this); - Bukkit.getPluginManager().registerEvents(new FloorLoseListener(this), this); + + // FloorLoseListener mit Referenz speichern (für GameManager) + this.floorLoseListener = new FloorLoseListener(this); + Bukkit.getPluginManager().registerEvents(floorLoseListener, this); + Bukkit.getPluginManager().registerEvents(new GameListener(this), this); Bukkit.getPluginManager().registerEvents(new SetupListener(this), this); Bukkit.getPluginManager().registerEvents(new SignListener(this), this); @@ -133,6 +141,7 @@ public class VelocityFall extends JavaPlugin { return list; } + // Manager-Getter public ArenaManager getArenaManager() { return arenaManager; } public JoinManager getJoinManager() { return joinManager; } public SignManager getSignManager() { return signManager; } @@ -141,6 +150,10 @@ public class VelocityFall extends JavaPlugin { public Messages getMessages() { return messages; } public Stats getStats() { return stats; } + // Listener-Getter (für GameManager) + public FloorLoseListener getFloorLoseListener() { return floorLoseListener; } + + // Map-Getter public HashMap getPlayerArenaMap() { return playerArena; } public HashMap getArenaPlayersCountMap() { return arenaPlayersCount; } public HashMap getGameStartedMap() { return gameStarted; } diff --git a/src/main/java/de/velocityfall/listeners/FloorLoseListener.java b/src/main/java/de/velocityfall/listeners/FloorLoseListener.java index 3b7b49f..ff50935 100644 --- a/src/main/java/de/velocityfall/listeners/FloorLoseListener.java +++ b/src/main/java/de/velocityfall/listeners/FloorLoseListener.java @@ -6,6 +6,7 @@ import de.velocityfall.utils.ScoreboardHelper; import de.velocityfall.utils.StatsHandler; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -15,10 +16,14 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.inventory.ItemStack; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; public class FloorLoseListener implements Listener { private final VelocityFall plugin; + // Tracking: welche Spieler bereits als "verloren" markiert wurden + private final Set alreadyLost = new HashSet<>(); public FloorLoseListener(VelocityFall plugin) { this.plugin = plugin; @@ -32,70 +37,78 @@ public class FloorLoseListener implements Listener { String arena = VelocityFall.getArena(p); if (arena == null) return; + // Wenn Spieler bereits verloren hat, nicht nochmal verarbeiten + if (alreadyLost.contains(p)) return; + Location loc = p.getLocation(); double minY = plugin.getArenaManager().getMinY(arena); double buffer = 5.0; // Puffer für mehrstöckige Arenen // Verlierer nur, wenn wirklich unter der Arena + Puffer if (loc.getY() < minY - buffer) { - removePlayerFromGame(p, arena); + markPlayerAsLost(p, arena); GameManager.checkForWinner(arena); return; } // Optional: Wenn außerhalb der Region und unter MinY if (!plugin.getArenaManager().isInArenaRegion(arena, loc) && loc.getY() < minY) { - removePlayerFromGame(p, arena); + markPlayerAsLost(p, arena); GameManager.checkForWinner(arena); } } - private void removePlayerFromGame(Player p, String arena) { - var playerArenaMap = plugin.getPlayerArenaMap(); + /** + * Markiert Spieler als "verloren" - reduziert nur den Counter, entfernt ihn aber + * NICHT aus playerArenaMap oder ingamePlayers (das macht später der GameManager) + */ + private void markPlayerAsLost(Player p, String arena) { + // Verhindere Mehrfach-Verarbeitung + if (alreadyLost.contains(p)) return; + alreadyLost.add(p); + var arenaPlayersCountMap = plugin.getArenaPlayersCountMap(); - // Potions entfernen (falls du eine Methode hast) - // methodes.removepotions(p); - - if (plugin.getConfig().getBoolean("SaveInv", false)) { - try { - // methodes.restore(p); - } catch (Exception ex) { - ex.printStackTrace(); - } - } else { - p.getInventory().clear(); - p.getInventory().setArmorContents(null); - - String uuid = p.getUniqueId().toString(); - var saveFile = plugin.getConfigs().getSaveConfig(); - double x = saveFile.getDouble(uuid + ".NoSave.X", p.getWorld().getSpawnLocation().getX()); - double y = saveFile.getDouble(uuid + ".NoSave.Y", p.getWorld().getSpawnLocation().getY()); - double z = saveFile.getDouble(uuid + ".NoSave.Z", p.getWorld().getSpawnLocation().getZ()); - String worldName = saveFile.getString(uuid + ".NoSave.World", p.getWorld().getName()); - - World w = Bukkit.getWorld(worldName); - if (w != null) { - p.teleport(new Location(w, x, y, z)); - } - - saveFile.set(uuid, null); - try { - saveFile.save(plugin.getConfigs().getSaveFile()); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - + // Lose-Nachricht p.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', plugin.getMessages().getMessagesConfig().getString("LoseMessage", "&cDu bist rausgefallen!"))); - arenaPlayersCountMap.compute(arena, (k, v) -> (v == null ? 0 : v) - 1); - plugin.ingamePlayers.remove(p); - playerArenaMap.remove(p); - p.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); + // Setze Spieler sofort in Spectator, damit er nicht weiter interagieren kann + p.setGameMode(GameMode.SPECTATOR); - ScoreboardHelper.updateAllInArena(arena, true); - plugin.getSignManager().updateSign(arena); + // Counter reduzieren (wichtig für checkForWinner) + arenaPlayersCountMap.compute(arena, (k, v) -> Math.max(0, (v == null ? 0 : v) - 1)); + + // Scoreboard leeren + try { + p.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); + } catch (Exception ignored) {} + + // Scoreboard für andere updaten + try { + ScoreboardHelper.updateAllInArena(arena, true); + } catch (Exception ignored) {} + + // Sign updaten + try { + plugin.getSignManager().updateSign(arena); + } catch (Exception ignored) {} + + // WICHTIG: Spieler bleibt in ingamePlayers und playerArenaMap! + // Er wird erst in performSafeResetAndLobbyTeleport entfernt + } + + /** + * Cleanup-Methode für den GameManager - wird nach Arena-Reset aufgerufen + */ + public void clearLostPlayers() { + alreadyLost.clear(); + } + + /** + * Gibt die Menge der verlorenen Spieler zurück (für GameManager) + */ + public Set getAlreadyLost() { + return new HashSet<>(alreadyLost); } } \ No newline at end of file diff --git a/src/main/java/de/velocityfall/manager/GameManager.java b/src/main/java/de/velocityfall/manager/GameManager.java index c45e636..b1f8c4d 100644 --- a/src/main/java/de/velocityfall/manager/GameManager.java +++ b/src/main/java/de/velocityfall/manager/GameManager.java @@ -8,13 +8,25 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; +import org.bukkit.entity.Firework; import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.FireworkEffect; +import org.bukkit.Color; import org.bukkit.scheduler.BukkitRunnable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class GameManager { + /* --------------------------------------------------- + * GAME START COUNTDOWN + * --------------------------------------------------- */ public static void startArenaCountdown(String arenaName) { - VelocityFall plugin = VelocityFall.getInstance(); + final VelocityFall plugin = VelocityFall.getInstance(); if (plugin.getGameStartedMap().getOrDefault(arenaName, false)) return; new BukkitRunnable() { @@ -22,7 +34,8 @@ public class GameManager { @Override public void run() { - int playerCount = (int) plugin.getPlayerArenaMap().values().stream().filter(arenaName::equals).count(); + int playerCount = (int) plugin.getPlayerArenaMap().values() + .stream().filter(arenaName::equals).count(); if (playerCount < 1) { cancel(); @@ -38,10 +51,10 @@ public class GameManager { plugin.getArenaPlayersCountMap().put(arenaName, playerCount); plugin.getSignManager().updateSign(arenaName); - broadcast(arenaName, "§6§lDAS SPIEL BEGINNT!"); - Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); + Location spawn = plugin.getArenaManager() + .getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); plugin.getPlayerArenaMap().forEach((p, a) -> { if (arenaName.equals(a)) { @@ -62,65 +75,324 @@ public class GameManager { startArenaCountdown(arenaName); } + /* --------------------------------------------------- + * WIN HANDLING + * --------------------------------------------------- */ public static void checkForWinner(String arenaName) { - VelocityFall plugin = VelocityFall.getInstance(); - var countMap = plugin.getArenaPlayersCountMap(); - int remaining = countMap.getOrDefault(arenaName, 0); - + final VelocityFall plugin = VelocityFall.getInstance(); + int remaining = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0); if (remaining != 1) return; - var arenaPlayers = VelocityFall.getArenaPlayers(arenaName); - if (arenaPlayers.isEmpty()) return; + // 1) Versuche Gewinner zuverlässig über playerArenaMap (Single source) + Player winner = plugin.getPlayerArenaMap().entrySet().stream() + .filter(e -> arenaName.equals(e.getValue())) + .map(Map.Entry::getKey) + .filter(p -> p.getGameMode() != GameMode.SPECTATOR) // Nicht-Spectator = Gewinner + .findFirst() + .orElse(null); - Player winner = arenaPlayers.get(0); + // 2) Fallback: VelocityFall.getArenaPlayers(arenaName) + if (winner == null) { + List arenaPlayers = VelocityFall.getArenaPlayers(arenaName); + for (Player p : arenaPlayers) { + if (p != null && p.getGameMode() != GameMode.SPECTATOR) { + winner = p; + break; + } + } + } - winner.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', - plugin.getMessages().getMessagesConfig().getString("WinMessage", "&aDu hast gewonnen!"))); + if (winner == null) return; // kein Gewinner auffindbar → exit - StatsHandler.addWin(winner.getUniqueId().toString()); + final Player finalWinner = winner; - plugin.ingameArenas.remove(arenaName); - plugin.ingamePlayers.remove(winner); - plugin.getPlayerArenaMap().remove(winner); - countMap.remove(arenaName); - plugin.getGameStartedMap().remove(arenaName); - winner.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); + // Nachricht + Stats + Scoreboard + try { + finalWinner.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', + plugin.getMessages().getMessagesConfig() + .getString("WinMessage", "&aDu hast gewonnen!"))); + } catch (Exception ignored) {} + try { + StatsHandler.addWin(finalWinner.getUniqueId().toString()); + } catch (Exception ignored) {} + try { + finalWinner.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); + } catch (Exception ignored) {} - resetArena(arenaName); + // Lose-Location: mehrere mögliche Keys supporten + YLose fallback + Location loseLocation = plugin.getArenaManager() + .getArenaConfig().getLocation("Arenas." + arenaName + ".Lose"); + if (loseLocation == null) loseLocation = plugin.getArenaManager() + .getArenaConfig().getLocation("Arenas." + arenaName + ".LoseLocation"); + if (loseLocation == null) loseLocation = plugin.getArenaManager() + .getArenaConfig().getLocation("Arenas." + arenaName + ".LosePoint"); + if (loseLocation == null) loseLocation = plugin.getArenaManager() + .getArenaConfig().getLocation("Arenas." + arenaName + ".SetLose"); - plugin.getSignManager().updateSign(arenaName); + // Spezial-Fallback: YLose vorhanden? Dann baue Location auf Basis von Spawn oder Lobby X/Z + if (loseLocation == null) { + Double yLose = plugin.getArenaManager().getArenaConfig().getDouble("Arenas." + arenaName + ".YLose", Double.NaN); + if (!yLose.isNaN()) { + // Priorität: Spawn -> Lobby -> arena center (Pos1/Pos2 midpoint) + Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); + Location lobby = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby"); + if (spawn != null) { + loseLocation = spawn.clone(); + loseLocation.setY(yLose); + } else if (lobby != null) { + loseLocation = lobby.clone(); + loseLocation.setY(yLose); + } else { + // midpoint of Pos1 and Pos2 if available + Location pos1 = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Pos1"); + Location pos2 = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Pos2"); + if (pos1 != null && pos2 != null && pos1.getWorld() != null && pos1.getWorld().equals(pos2.getWorld())) { + double midX = (pos1.getX() + pos2.getX()) / 2.0; + double midZ = (pos1.getZ() + pos2.getZ()) / 2.0; + loseLocation = new Location(pos1.getWorld(), midX, yLose, midZ, 0f, 0f); + } + } + } + } + + final Location finalLoseLocation = loseLocation; // final für Lambdas + + // Ermittlung der Verlierer: alle Spectator-Spieler in dieser Arena + List losers = new ArrayList<>(); + + // Strategie A: playerArenaMap - alle Spectators sind Verlierer + plugin.getPlayerArenaMap().forEach((p, a) -> { + if (arenaName.equals(a) && !p.equals(finalWinner) && p.getGameMode() == GameMode.SPECTATOR) { + losers.add(p); + } + }); + + // Strategie B: Falls keine Verlierer gefunden -> ingamePlayers Liste + if (losers.isEmpty()) { + try { + for (Player p : plugin.ingamePlayers) { + if (p != null && !p.equals(finalWinner) && p.getGameMode() == GameMode.SPECTATOR) { + losers.add(p); + } + } + } catch (Exception ignored) {} + } + + // Strategie C: VelocityFall.getArenaPlayers(arenaName) + if (losers.isEmpty()) { + List arenaPlayers = VelocityFall.getArenaPlayers(arenaName); + if (!arenaPlayers.isEmpty()) { + for (Player p : arenaPlayers) { + if (p != null && !p.equals(finalWinner) && p.getGameMode() == GameMode.SPECTATOR) { + losers.add(p); + } + } + } + } + + // Erstelle finale Liste der Verlierer (damit sie auch nach Delays verfügbar ist) + final List finalLosers = new ArrayList<>(losers); + + // Teleportiere Verlierer SOFORT zum Lose-Punkt (sie sind bereits in GM3 durch FloorLoseListener) + for (Player loser : finalLosers) { + try { + // Sicherstellen, dass Spectator-Modus gesetzt ist + if (loser.getGameMode() != GameMode.SPECTATOR) { + loser.setGameMode(GameMode.SPECTATOR); + } + + if (finalLoseLocation != null) { + loser.teleport(finalLoseLocation); + } else { + // fallback: direkt neben Gewinner + Location fallback = finalWinner.getLocation().clone().add(1.0, 0.0, 0.0); + loser.teleport(fallback); + } + } catch (Exception e) { + // Falls Teleport fehlschlägt, loggen wir es serverseitig + e.printStackTrace(); + } + } + + // Delay 3-5 Sekunden (konfigurierbar) + int delaySeconds = plugin.getConfig().getInt("PostWinDelay", 4); + delaySeconds = Math.min(5, Math.max(3, delaySeconds)); + final long delayTicks = delaySeconds * 20L; + + // Nach Delay: Feuerwerk beim Gewinner und dann Arena resetten / Lobby teleport + new BukkitRunnable() { + @Override + public void run() { + try { + spawnFireworks(finalWinner.getLocation(), 2); + finalWinner.playSound(finalWinner.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1f); + } catch (Exception ignored) {} + + // 1 Sekunde später: Reset & Teleport aller zur Lobby (GM1) + new BukkitRunnable() { + @Override + public void run() { + try { + // Robustes Reset: teleportiere Gewinner + finale Verlierer-Liste zur Lobby + performSafeResetAndLobbyTeleport(arenaName, finalWinner, finalLosers); + } catch (Exception ignored) {} + + // Aufräumen maps / Signs + try { + plugin.ingameArenas.remove(arenaName); + } catch (Exception ignored) {} + try { + plugin.getSignManager().updateSign(arenaName); + } catch (Exception ignored) {} + } + }.runTaskLater(plugin, 20L); // 1 Sekunde nach Feuerwerk + } + }.runTaskLater(plugin, delayTicks); } - public static void resetArena(String arenaName) { - VelocityFall plugin = VelocityFall.getInstance(); + /* --------------------------------------------------- + * SAFE RESET & LOBBY TELEPORT (robust) + * --------------------------------------------------- */ + private static void performSafeResetAndLobbyTeleport(String arenaName, Player winner, List losers) { + final VelocityFall plugin = VelocityFall.getInstance(); - plugin.getArenaBlocksMap().forEach((block, matName) -> block.setType(Material.valueOf(matName))); + // Blöcke zurücksetzen + plugin.getArenaBlocksMap().forEach((block, matName) -> { + try { + block.setType(Material.valueOf(matName)); + } catch (Exception ignored) {} + }); plugin.getArenaBlocksMap().clear(); + // Spielzustand zurücksetzen plugin.getGameStartedMap().put(arenaName, false); plugin.getArenaPlayersCountMap().remove(arenaName); + // Lobby-Location (kann null sein) Location lobby = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby"); - plugin.getPlayerArenaMap().entrySet().removeIf(e -> { - if (arenaName.equals(e.getValue())) { - Player p = e.getKey(); - p.setGameMode(GameMode.SURVIVAL); - p.getInventory().clear(); - if (lobby != null) p.teleport(lobby); - plugin.ingamePlayers.remove(p); - return true; + // Sammle alle Spieler, die teleportiert werden müssen + List toHandle = new ArrayList<>(); + + // Gewinner hinzufügen + if (winner != null) { + toHandle.add(winner); + } + + // Verlierer hinzufügen (bereits als finale Liste übergeben) + for (Player loser : losers) { + if (loser != null && !toHandle.contains(loser)) { + toHandle.add(loser); + } + } + + // Zusätzlich: alle restlichen Spieler aus playerArenaMap (Sicherheit) + plugin.getPlayerArenaMap().forEach((p, a) -> { + if (arenaName.equals(a) && !toHandle.contains(p)) { + toHandle.add(p); } - return false; }); - plugin.getSignManager().updateSign(arenaName); + // Fallback: ingamePlayers + try { + for (Player p : new ArrayList<>(plugin.ingamePlayers)) { + if (p != null && !toHandle.contains(p)) { + // Prüfe ob Spieler möglicherweise zu dieser Arena gehört + String playerArena = plugin.getPlayerArenaMap().get(p); + if (arenaName.equals(playerArena)) { + toHandle.add(p); + } + } + } + } catch (Exception ignored) {} + + // Fallback: VelocityFall.getArenaPlayers + try { + List arenaPlayers = VelocityFall.getArenaPlayers(arenaName); + for (Player p : arenaPlayers) { + if (p != null && !toHandle.contains(p)) toHandle.add(p); + } + } catch (Exception ignored) {} + + // Verarbeite alle gesammelten Spieler: GM1 (SURVIVAL), Inventar clear, teleport zur Lobby, entferne aus Maps + for (Player p : toHandle) { + try { + p.setGameMode(GameMode.SURVIVAL); // GM1 + p.getInventory().clear(); + p.getInventory().setArmorContents(null); + p.setHealth(20.0); + p.setFoodLevel(20); + + if (lobby != null) { + p.teleport(lobby); + } else { + // Fallback: Spawn-Location oder World-Spawn + Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); + if (spawn != null) { + p.teleport(spawn); + } else if (p.getWorld() != null) { + p.teleport(p.getWorld().getSpawnLocation()); + } + } + + // Jetzt erst aus den Maps entfernen + plugin.ingamePlayers.remove(p); + plugin.getPlayerArenaMap().remove(p); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // FloorLoseListener cleanup + try { + if (plugin.getFloorLoseListener() != null) { + plugin.getFloorLoseListener().clearLostPlayers(); + } + } catch (Exception ignored) {} + + // Signs updaten + try { + plugin.getSignManager().updateSign(arenaName); + } catch (Exception ignored) {} } + /* --------------------------------------------------- + * FIREWORKS + * --------------------------------------------------- */ + private static void spawnFireworks(Location loc, int count) { + if (loc == null || loc.getWorld() == null) return; + + for (int i = 0; i < Math.max(1, count); i++) { + try { + Firework fw = loc.getWorld().spawn(loc.clone().add(0, 1 + i * 0.5, 0), Firework.class); + FireworkMeta meta = fw.getFireworkMeta(); + + FireworkEffect effect = FireworkEffect.builder() + .with(FireworkEffect.Type.BALL_LARGE) + .withColor(Color.LIME, Color.YELLOW) + .withFade(Color.WHITE) + .trail(true) + .flicker(true) + .build(); + + meta.addEffect(effect); + meta.setPower(1); + fw.setFireworkMeta(meta); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + + /* --------------------------------------------------- + * BROADCAST + * --------------------------------------------------- */ private static void broadcast(String arena, String msg) { - VelocityFall plugin = VelocityFall.getInstance(); + final VelocityFall plugin = VelocityFall.getInstance(); plugin.getPlayerArenaMap().forEach((player, a) -> { - if (arena.equals(a)) player.sendMessage(VelocityFall.prefix + msg); + if (arena.equals(a)) { + player.sendMessage(VelocityFall.prefix + msg); + } }); } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f8cb624..ca26055 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,6 +2,7 @@ Prefix: '&c[VelocityFall] ' BeforeGameCD: 15 CountdownSound: ENTITY_EXPERIENCE_ORB_PICKUP SaveInv: true +PostWinDelay: 4 WinCommands: - eco give %player% 10 - msg %player% Du hast gewonnen!