package de.velocityfall.manager; import de.velocityfall.VelocityFall; import de.velocityfall.utils.StatsHandler; import org.bukkit.Bukkit; import org.bukkit.ChatColor; 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) { final VelocityFall plugin = VelocityFall.getInstance(); if (plugin.getGameStartedMap().getOrDefault(arenaName, false)) return; new BukkitRunnable() { int timeLeft = plugin.getConfig().getInt("BeforeGameCD", 15); @Override public void run() { int playerCount = (int) plugin.getPlayerArenaMap().values() .stream().filter(arenaName::equals).count(); if (playerCount < 1) { cancel(); return; } if (timeLeft <= 5 && timeLeft > 0) { broadcast(arenaName, "§aSpiel startet in §e" + timeLeft + "..."); } if (timeLeft <= 0) { plugin.getGameStartedMap().put(arenaName, true); plugin.getArenaPlayersCountMap().put(arenaName, playerCount); plugin.getSignManager().updateSign(arenaName); broadcast(arenaName, "§6§lDAS SPIEL BEGINNT!"); Location spawn = plugin.getArenaManager() .getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); plugin.getPlayerArenaMap().forEach((p, a) -> { if (arenaName.equals(a)) { if (spawn != null) p.teleport(spawn); p.setGameMode(GameMode.ADVENTURE); p.setAllowFlight(false); p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1f, 1f); } }); cancel(); } timeLeft--; } }.runTaskTimer(plugin, 0L, 20L); } public static void startCountdown(String arenaName) { startArenaCountdown(arenaName); } /* --------------------------------------------------- * WIN HANDLING * --------------------------------------------------- */ public static void checkForWinner(String arenaName) { final VelocityFall plugin = VelocityFall.getInstance(); int remaining = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0); if (remaining != 1) 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); // 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; } } } if (winner == null) return; // kein Gewinner auffindbar → exit final Player finalWinner = winner; // 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) {} // 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"); // 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); } /* --------------------------------------------------- * SAFE RESET & LOBBY TELEPORT (robust) * --------------------------------------------------- */ private static void performSafeResetAndLobbyTeleport(String arenaName, Player winner, List losers) { final VelocityFall plugin = VelocityFall.getInstance(); // 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"); // 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); } }); // 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) { final VelocityFall plugin = VelocityFall.getInstance(); plugin.getPlayerArenaMap().forEach((player, a) -> { if (arena.equals(a)) { player.sendMessage(VelocityFall.prefix + msg); } }); } }