Update from Git Manager GUI

This commit is contained in:
2026-02-01 22:36:30 +01:00
parent 4c476a1a1d
commit b36bdaa3ec
22 changed files with 988 additions and 346 deletions

View File

@@ -4,37 +4,47 @@ import de.velocityfall.commands.VelocityCommand;
import de.velocityfall.listeners.*; import de.velocityfall.listeners.*;
import de.velocityfall.manager.*; import de.velocityfall.manager.*;
import de.velocityfall.utils.*; import de.velocityfall.utils.*;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap;
public class VelocityFall extends JavaPlugin { public class VelocityFall extends JavaPlugin implements Listener {
private static VelocityFall instance; private static volatile VelocityFall instance;
public static VelocityFall getInstance() { return instance; }
public static VelocityFall getInstance() {
return instance;
}
public static String prefix; public static String prefix;
public static String noperms; public static String noperms;
// Spiel-Daten // ===== SPIEL-DATEN - THREAD-SAFE COLLECTIONS =====
public ArrayList<Player> ingamePlayers = new ArrayList<>(); public ArrayList<Player> ingamePlayers = new ArrayList<>();
public HashMap<String, Integer> arenaPlayersCount = new HashMap<>(); public ConcurrentHashMap<String, Integer> arenaPlayersCount = new ConcurrentHashMap<>();
public HashMap<Player, String> playerArena = new HashMap<>(); public ConcurrentHashMap<Player, String> playerArena = new ConcurrentHashMap<>();
public ArrayList<String> ingameArenas = new ArrayList<>(); public ArrayList<String> ingameArenas = new ArrayList<>();
public HashMap<String, Boolean> cdstarted = new HashMap<>(); public ConcurrentHashMap<String, Boolean> cdstarted = new ConcurrentHashMap<>();
public HashMap<String, Integer> arenaCountdown = new HashMap<>(); public ConcurrentHashMap<String, Integer> arenaCountdown = new ConcurrentHashMap<>();
public HashMap<Block, String> arenaBlocks = new HashMap<>(); public ConcurrentHashMap<Block, String> arenaBlocks = new ConcurrentHashMap<>();
public HashMap<String, Boolean> gameStarted = new HashMap<>(); public ConcurrentHashMap<String, Boolean> gameStarted = new ConcurrentHashMap<>();
public ArrayList<Player> cooldown = new ArrayList<>(); public ArrayList<Player> cooldown = new ArrayList<>();
public HashMap<Player, String> spectators = new HashMap<>(); public ConcurrentHashMap<Player, String> spectators = new ConcurrentHashMap<>();
public HashMap<Player, Integer> checkint = new HashMap<>(); public ConcurrentHashMap<Player, Integer> checkint = new ConcurrentHashMap<>();
// Manager // ===== MANAGER =====
private ArenaManager arenaManager; private ArenaManager arenaManager;
private JoinManager joinManager; private JoinManager joinManager;
private SignManager signManager; private SignManager signManager;
@@ -44,9 +54,14 @@ public class VelocityFall extends JavaPlugin {
private Stats stats; private Stats stats;
private DisableReset disablereset; private DisableReset disablereset;
// Listener-Referenzen (für GameManager-Zugriff) // ===== LISTENER-REFERENZEN =====
private FloorLoseListener floorLoseListener; private FloorLoseListener floorLoseListener;
// ===== UPDATE CHECKER DATEN =====
private String latestVersionFound;
private final int RESOURCE_ID = 132255;
private final String releaseUrl = "https://www.spigotmc.org/resources/" + RESOURCE_ID;
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
@@ -55,8 +70,11 @@ public class VelocityFall extends JavaPlugin {
getConfig().options().copyDefaults(true); getConfig().options().copyDefaults(true);
saveConfig(); saveConfig();
prefix = ChatColor.translateAlternateColorCodes('&', getConfig().getString("Prefix", "&6Velocity&fFall &8» ")); // Encoding-Fixed: Korrekte Umlaute
noperms = ChatColor.translateAlternateColorCodes('&', getConfig().getString("NoPermission", "&cKeine Rechte!")); prefix = ChatColor.translateAlternateColorCodes('&',
getConfig().getString("Prefix", "&6Velocity&fFall &8» "));
noperms = ChatColor.translateAlternateColorCodes('&',
getConfig().getString("NoPermission", "&cKeine Rechte!"));
ConfigManager.initializeFiles(this); ConfigManager.initializeFiles(this);
@@ -79,9 +97,9 @@ public class VelocityFall extends JavaPlugin {
getCommand("velocityfall").setExecutor(new VelocityCommand(this)); getCommand("velocityfall").setExecutor(new VelocityCommand(this));
// Listener registrieren // Listener registrieren
Bukkit.getPluginManager().registerEvents(this, this);
Bukkit.getPluginManager().registerEvents(new FloorListener(this), this); Bukkit.getPluginManager().registerEvents(new FloorListener(this), this);
// FloorLoseListener mit Referenz speichern (für GameManager)
this.floorLoseListener = new FloorLoseListener(this); this.floorLoseListener = new FloorLoseListener(this);
Bukkit.getPluginManager().registerEvents(floorLoseListener, this); Bukkit.getPluginManager().registerEvents(floorLoseListener, this);
@@ -89,9 +107,44 @@ public class VelocityFall extends JavaPlugin {
Bukkit.getPluginManager().registerEvents(new SetupListener(this), this); Bukkit.getPluginManager().registerEvents(new SetupListener(this), this);
Bukkit.getPluginManager().registerEvents(new SignListener(this), this); Bukkit.getPluginManager().registerEvents(new SignListener(this), this);
// Update Check starten
checkForUpdates();
getLogger().info("VelocityFall aktiviert Version " + getDescription().getVersion()); getLogger().info("VelocityFall aktiviert Version " + getDescription().getVersion());
} }
private void checkForUpdates() {
new UpdateChecker(this, RESOURCE_ID).getLatestVersion(version -> {
this.latestVersionFound = version;
if (!this.getDescription().getVersion().equalsIgnoreCase(version)) {
getLogger().warning("====================================================");
getLogger().warning("[VelocityFall] Eine neue Version ist verfügbar: v" + version);
getLogger().warning("[VelocityFall] Download: " + releaseUrl);
getLogger().warning("====================================================");
}
});
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (player.hasPermission("velocityfall.admin") || player.isOp()) {
if (latestVersionFound != null && !this.getDescription().getVersion().equalsIgnoreCase(latestVersionFound)) {
player.sendMessage(" ");
player.sendMessage(prefix + "§eEine neue Version (§b" + latestVersionFound + "§e) ist verfügbar!");
TextComponent message = new TextComponent(prefix + "§eDownload: ");
TextComponent link = new TextComponent("§6§l[KLICK HIER]");
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, releaseUrl));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§7Öffnet die Spigot-Seite")));
message.addExtra(link);
player.spigot().sendMessage(message);
player.sendMessage(" ");
}
}
}
private void setupMySQL() { private void setupMySQL() {
var cfg = stats.getStatsConfig(); var cfg = stats.getStatsConfig();
String host = cfg.getString("MySQL.HOST", "localhost"); String host = cfg.getString("MySQL.HOST", "localhost");
@@ -114,8 +167,12 @@ public class VelocityFall extends JavaPlugin {
@Override @Override
public void onDisable() { public void onDisable() {
if (disablereset != null) disablereset.disableReset(); if (disablereset != null) {
if (mysqlConnector != null) mysqlConnector.close(); disablereset.disableReset();
}
if (mysqlConnector != null) {
mysqlConnector.close();
}
getLogger().info("VelocityFall deaktiviert"); getLogger().info("VelocityFall deaktiviert");
} }
@@ -141,7 +198,7 @@ public class VelocityFall extends JavaPlugin {
return list; return list;
} }
// Manager-Getter // ===== MANAGER-GETTER =====
public ArenaManager getArenaManager() { return arenaManager; } public ArenaManager getArenaManager() { return arenaManager; }
public JoinManager getJoinManager() { return joinManager; } public JoinManager getJoinManager() { return joinManager; }
public SignManager getSignManager() { return signManager; } public SignManager getSignManager() { return signManager; }
@@ -149,13 +206,11 @@ public class VelocityFall extends JavaPlugin {
public Configs getConfigs() { return configs; } public Configs getConfigs() { return configs; }
public Messages getMessages() { return messages; } public Messages getMessages() { return messages; }
public Stats getStats() { return stats; } public Stats getStats() { return stats; }
// Listener-Getter (für GameManager)
public FloorLoseListener getFloorLoseListener() { return floorLoseListener; } public FloorLoseListener getFloorLoseListener() { return floorLoseListener; }
// Map-Getter // ===== MAP-GETTER =====
public HashMap<Player, String> getPlayerArenaMap() { return playerArena; } public ConcurrentHashMap<Player, String> getPlayerArenaMap() { return playerArena; }
public HashMap<String, Integer> getArenaPlayersCountMap() { return arenaPlayersCount; } public ConcurrentHashMap<String, Integer> getArenaPlayersCountMap() { return arenaPlayersCount; }
public HashMap<String, Boolean> getGameStartedMap() { return gameStarted; } public ConcurrentHashMap<String, Boolean> getGameStartedMap() { return gameStarted; }
public HashMap<Block, String> getArenaBlocksMap() { return arenaBlocks; } public ConcurrentHashMap<Block, String> getArenaBlocksMap() { return arenaBlocks; }
} }

View File

@@ -2,6 +2,7 @@ package de.velocityfall.commands;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.manager.GameManager; import de.velocityfall.manager.GameManager;
import de.velocityfall.utils.ScoreboardHelper;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -35,7 +36,7 @@ public class VelocityCommand implements CommandExecutor {
String action = args[0].toLowerCase(); String action = args[0].toLowerCase();
// --- Spieler-Befehle --- // === SPIELER-BEFEHLE ===
if (action.equals("join") && args.length == 2) { if (action.equals("join") && args.length == 2) {
plugin.getJoinManager().join(p, args[1]); plugin.getJoinManager().join(p, args[1]);
@@ -49,22 +50,20 @@ public class VelocityCommand implements CommandExecutor {
return true; return true;
} }
// Entfernen aus Registern
plugin.getPlayerArenaMap().remove(p); plugin.getPlayerArenaMap().remove(p);
plugin.ingamePlayers.remove(p); plugin.ingamePlayers.remove(p);
// Zähler dekrementieren
plugin.getArenaPlayersCountMap().compute(arena, (k, v) -> Math.max(0, (v == null ? 0 : v) - 1)); plugin.getArenaPlayersCountMap().compute(arena, (k, v) -> Math.max(0, (v == null ? 0 : v) - 1));
// Siegprüfung (korrekt über GameManager)
GameManager.checkForWinner(arena); GameManager.checkForWinner(arena);
// Reset Spieler
p.teleport(p.getWorld().getSpawnLocation()); p.teleport(p.getWorld().getSpawnLocation());
p.setGameMode(GameMode.SURVIVAL); p.setGameMode(GameMode.SURVIVAL);
p.setAllowFlight(false); p.setAllowFlight(false);
p.setFlying(false); p.setFlying(false);
p.getInventory().clear(); p.getInventory().clear();
ScoreboardHelper.clearBoard(p);
p.sendMessage(VelocityFall.prefix + "§eArena verlassen."); p.sendMessage(VelocityFall.prefix + "§eArena verlassen.");
@@ -72,7 +71,7 @@ public class VelocityCommand implements CommandExecutor {
return true; return true;
} }
// --- Admin-Befehle --- // === ADMIN-BEFEHLE ===
if (!p.hasPermission("velocityfall.admin")) { if (!p.hasPermission("velocityfall.admin")) {
p.sendMessage(VelocityFall.prefix + "§cKeine Rechte."); p.sendMessage(VelocityFall.prefix + "§cKeine Rechte.");
@@ -95,7 +94,7 @@ public class VelocityCommand implements CommandExecutor {
wand.setItemMeta(meta); wand.setItemMeta(meta);
} }
p.getInventory().addItem(wand); p.getInventory().addItem(wand);
p.sendMessage(VelocityFall.prefix + "§aSetup-Wand erhalten! Nutze Links-/Rechtsklick auf Blöcke."); p.sendMessage(VelocityFall.prefix + "§aSetup-Wand erhalten!");
break; break;
case "savearea": case "savearea":
@@ -184,13 +183,13 @@ public class VelocityCommand implements CommandExecutor {
if (p.hasPermission("velocityfall.admin")) { if (p.hasPermission("velocityfall.admin")) {
p.sendMessage("§6Admin-Befehle:"); p.sendMessage("§6Admin-Befehle:");
p.sendMessage("§6/vfall wand §7- Setup-Wand holen"); p.sendMessage("§6/vfall wand §7- Setup-Wand holen");
p.sendMessage("§6/vfall create <Name> §7- Arena erstellen"); p.sendMessage("§6/vfall create <n> §7- Arena erstellen");
p.sendMessage("§6/vfall savearea <Name> §7- Bereich speichern"); p.sendMessage("§6/vfall savearea <n> §7- Bereich speichern");
p.sendMessage("§6/vfall setlobby <Name> §7- Lobby setzen"); p.sendMessage("§6/vfall setlobby <n> §7- Lobby setzen");
p.sendMessage("§6/vfall setspawn <Name> §7- Spawn setzen"); p.sendMessage("§6/vfall setspawn <n> §7- Spawn setzen");
p.sendMessage("§6/vfall setlose <Name> §7- Verlust-Höhe setzen"); p.sendMessage("§6/vfall setlose <n> §7- Verlust-Höhe setzen");
p.sendMessage("§6/vfall setmax <Name> <Zahl> §7- Max-Spieler setzen"); p.sendMessage("§6/vfall setmax <n> <Zahl> §7- Max-Spieler setzen");
p.sendMessage("§6/vfall start <Name> §7- Spiel sofort starten"); p.sendMessage("§6/vfall start <n> §7- Spiel sofort starten");
} }
} }
} }

View File

@@ -9,12 +9,13 @@ import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class FloorListener implements Listener { public class FloorListener implements Listener {
private final VelocityFall plugin; private final VelocityFall plugin;
private final Set<Block> blocksInProcess = new HashSet<>(); // THREAD-SAFE: ConcurrentHashMap.newKeySet() statt HashSet
private final Set<Block> blocksInProcess = ConcurrentHashMap.newKeySet();
public FloorListener(VelocityFall plugin) { public FloorListener(VelocityFall plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -29,21 +30,29 @@ public class FloorListener implements Listener {
if (!plugin.ingamePlayers.contains(p)) continue; if (!plugin.ingamePlayers.contains(p)) continue;
String arena = plugin.getPlayerArenaMap().get(p); String arena = plugin.getPlayerArenaMap().get(p);
if (arena == null || !plugin.getGameStartedMap().getOrDefault(arena, false)) continue; if (arena == null) continue;
// Nur wenn Spiel läuft UND Spieler nicht Spectator
if (!plugin.getGameStartedMap().getOrDefault(arena, false)) continue;
if (p.getGameMode() == GameMode.SPECTATOR) continue;
// Nur Adventure/Survival Spieler
if (p.getGameMode() != GameMode.ADVENTURE && p.getGameMode() != GameMode.SURVIVAL) continue; if (p.getGameMode() != GameMode.ADVENTURE && p.getGameMode() != GameMode.SURVIVAL) continue;
Block b = p.getLocation().subtract(0, 0.1, 0).getBlock(); Block b = p.getLocation().subtract(0, 0.1, 0).getBlock();
if (b.getType() == Material.AIR || blocksInProcess.contains(b)) continue; if (b.getType() == Material.AIR) continue;
// THREAD-SAFE: putIfAbsent() ist atomar
if (!blocksInProcess.add(b)) continue;
if (plugin.getArenaManager().isInArenaRegion(arena, b.getLocation())) { if (plugin.getArenaManager().isInArenaRegion(arena, b.getLocation())) {
blocksInProcess.add(b);
Bukkit.getScheduler().runTaskLater(plugin, () -> { Bukkit.getScheduler().runTaskLater(plugin, () -> {
updateBlock(b); updateBlock(b);
blocksInProcess.remove(b); blocksInProcess.remove(b);
}, 10L); // 0.5 Sekunden Delay }, 10L); // 0.5 Sekunden Delay
} else {
blocksInProcess.remove(b);
} }
} }
} }
@@ -62,9 +71,8 @@ public class FloorListener implements Listener {
}; };
if (next != null) { if (next != null) {
if (!plugin.getArenaBlocksMap().containsKey(b)) { // Originalblock merken (falls noch nicht vorhanden)
plugin.getArenaBlocksMap().put(b, current.name()); plugin.getArenaBlocksMap().putIfAbsent(b, current.name());
}
b.setType(next); b.setType(next);
@@ -75,4 +83,4 @@ public class FloorListener implements Listener {
} }
} }
} }
} }

View File

@@ -3,27 +3,24 @@ package de.velocityfall.listeners;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.manager.GameManager; import de.velocityfall.manager.GameManager;
import de.velocityfall.utils.ScoreboardHelper; import de.velocityfall.utils.ScoreboardHelper;
import de.velocityfall.utils.StatsHandler;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World;
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;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class FloorLoseListener implements Listener { public class FloorLoseListener implements Listener {
private final VelocityFall plugin; private final VelocityFall plugin;
// Tracking: welche Spieler bereits als "verloren" markiert wurden // THREAD-SAFE: ConcurrentHashMap.newKeySet() statt HashSet
private final Set<Player> alreadyLost = new HashSet<>(); private final Set<Player> alreadyLost = ConcurrentHashMap.newKeySet();
public FloorLoseListener(VelocityFall plugin) { public FloorLoseListener(VelocityFall plugin) {
this.plugin = plugin; this.plugin = plugin;
@@ -59,21 +56,20 @@ public class FloorLoseListener implements Listener {
} }
/** /**
* Markiert Spieler als "verloren" - reduziert nur den Counter, entfernt ihn aber * Markiert Spieler als "verloren" - reduziert nur den Counter
* NICHT aus playerArenaMap oder ingamePlayers (das macht später der GameManager) * THREAD-SAFE durch ConcurrentHashMap.newKeySet()
*/ */
private void markPlayerAsLost(Player p, String arena) { private void markPlayerAsLost(Player p, String arena) {
// Verhindere Mehrfach-Verarbeitung // Verhindere Mehrfach-Verarbeitung (atomar durch ConcurrentHashSet)
if (alreadyLost.contains(p)) return; if (!alreadyLost.add(p)) return;
alreadyLost.add(p);
var arenaPlayersCountMap = plugin.getArenaPlayersCountMap(); var arenaPlayersCountMap = plugin.getArenaPlayersCountMap();
// Lose-Nachricht // Lose-Nachricht
p.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', String loseMsg = plugin.getMessages().getMessagesConfig().getString("LoseMessage", "&cDu bist rausgefallen!");
plugin.getMessages().getMessagesConfig().getString("LoseMessage", "&cDu bist rausgefallen!"))); p.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', loseMsg));
// Setze Spieler sofort in Spectator, damit er nicht weiter interagieren kann // Setze Spieler sofort in Spectator
p.setGameMode(GameMode.SPECTATOR); p.setGameMode(GameMode.SPECTATOR);
// Counter reduzieren (wichtig für checkForWinner) // Counter reduzieren (wichtig für checkForWinner)
@@ -81,34 +77,37 @@ public class FloorLoseListener implements Listener {
// Scoreboard leeren // Scoreboard leeren
try { try {
p.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); ScoreboardHelper.clearBoard(p);
} catch (Exception ignored) {} } catch (Exception e) {
plugin.getLogger().warning("Scoreboard Clear fehlgeschlagen: " + e.getMessage());
}
// Scoreboard für andere updaten // Scoreboard für andere updaten
try { try {
ScoreboardHelper.updateAllInArena(arena, true); ScoreboardHelper.updateAllInArena(arena, true);
} catch (Exception ignored) {} } catch (Exception e) {
plugin.getLogger().warning("Scoreboard Update fehlgeschlagen: " + e.getMessage());
}
// Sign updaten // Sign updaten
try { try {
plugin.getSignManager().updateSign(arena); plugin.getSignManager().updateSign(arena);
} catch (Exception ignored) {} } catch (Exception e) {
plugin.getLogger().warning("Sign Update fehlgeschlagen: " + e.getMessage());
// WICHTIG: Spieler bleibt in ingamePlayers und playerArenaMap! }
// Er wird erst in performSafeResetAndLobbyTeleport entfernt
} }
/** /**
* Cleanup-Methode für den GameManager - wird nach Arena-Reset aufgerufen * Cleanup-Methode für den GameManager
*/ */
public void clearLostPlayers() { public void clearLostPlayers() {
alreadyLost.clear(); alreadyLost.clear();
} }
/** /**
* Gibt die Menge der verlorenen Spieler zurück (für GameManager) * Gibt eine Kopie der verlorenen Spieler zurück
*/ */
public Set<Player> getAlreadyLost() { public Set<Player> getAlreadyLost() {
return new HashSet<>(alreadyLost); return new HashSet<>(alreadyLost);
} }
} }

View File

@@ -1,6 +1,7 @@
package de.velocityfall.listeners; package de.velocityfall.listeners;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.utils.ScoreboardHelper;
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;
@@ -25,9 +26,13 @@ public class GameListener implements Listener {
if (plugin.ingamePlayers.contains(p)) { if (plugin.ingamePlayers.contains(p)) {
plugin.ingamePlayers.remove(p); plugin.ingamePlayers.remove(p);
plugin.getPlayerArenaMap().remove(p);
var countMap = plugin.getArenaPlayersCountMap(); var countMap = plugin.getArenaPlayersCountMap();
countMap.compute(arena, (k, v) -> Math.max(0, (v == null ? 0 : v) - 1)); countMap.compute(arena, (k, v) -> Math.max(0, (v == null ? 0 : v) - 1));
ScoreboardHelper.clearBoard(p);
Bukkit.getScheduler().runTaskLater(plugin, () -> { Bukkit.getScheduler().runTaskLater(plugin, () -> {
de.velocityfall.manager.GameManager.checkForWinner(arena); de.velocityfall.manager.GameManager.checkForWinner(arena);
}, 1L); }, 1L);
@@ -48,4 +53,4 @@ public class GameListener implements Listener {
p.setFoodLevel(20); p.setFoodLevel(20);
} }
} }
} }

View File

@@ -39,4 +39,4 @@ public class SetupListener implements Listener {
e.getPlayer().sendMessage(VelocityFall.prefix + "§ePos2 gesetzt."); e.getPlayer().sendMessage(VelocityFall.prefix + "§ePos2 gesetzt.");
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package de.velocityfall.listeners; package de.velocityfall.listeners;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import org.bukkit.ChatColor;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@@ -19,7 +20,7 @@ public class SignListener implements Listener {
@EventHandler @EventHandler
public void onSignChange(SignChangeEvent e) { public void onSignChange(SignChangeEvent e) {
String line0 = e.getLine(0); String line0 = e.getLine(0);
if (line0 == null || !(line0.equalsIgnoreCase("[VelocityFall]") || line0.equalsIgnoreCase("[LKF]"))) return; if (line0 == null || !(line0.equalsIgnoreCase("[VelocityFall]") || line0.equalsIgnoreCase("[VF]"))) return;
String arenaName = e.getLine(1); String arenaName = e.getLine(1);
if (arenaName == null || arenaName.trim().isEmpty()) return; if (arenaName == null || arenaName.trim().isEmpty()) return;
@@ -55,10 +56,10 @@ public class SignListener implements Listener {
e.setCancelled(true); e.setCancelled(true);
String arenaName = sign.getLine(1).replaceAll("§.", ""); String arenaName = ChatColor.stripColor(sign.getLine(1));
if (arenaName.trim().isEmpty()) return; if (arenaName == null || arenaName.trim().isEmpty()) return;
plugin.getJoinManager().join(e.getPlayer(), arenaName); plugin.getJoinManager().join(e.getPlayer(), arenaName);
plugin.getSignManager().updateSign(arenaName); plugin.getSignManager().updateSign(arenaName);
} }
} }

View File

@@ -66,7 +66,7 @@ public class ArenaManager {
arenaConfig.set(path + "Pos2", pos2); arenaConfig.set(path + "Pos2", pos2);
arenaConfig.set(path + "World", pos1.getWorld().getName()); arenaConfig.set(path + "World", pos1.getWorld().getName());
// Automatisch MinY berechnen und speichern (wichtig für mehrstöckige Arenen) // Automatisch MinY berechnen und speichern
double minY = Math.min(pos1.getY(), pos2.getY()); double minY = Math.min(pos1.getY(), pos2.getY());
arenaConfig.set(path + "MinY", minY); arenaConfig.set(path + "MinY", minY);
@@ -82,6 +82,10 @@ public class ArenaManager {
saveArenaRegion(player, arenaName); saveArenaRegion(player, arenaName);
} }
/**
* Überprüft, ob eine Location innerhalb der Arena ist
* VERBESSERT: Konsistente Buffer-Werte (0.5 statt 0.1 / 2.0)
*/
public boolean isInArenaRegion(String arenaName, Location loc) { public boolean isInArenaRegion(String arenaName, Location loc) {
if (loc == null || loc.getWorld() == null) return false; if (loc == null || loc.getWorld() == null) return false;
@@ -92,23 +96,35 @@ public class ArenaManager {
if (p1 == null || p2 == null) return false; if (p1 == null || p2 == null) return false;
if (!loc.getWorld().equals(p1.getWorld())) return false; if (!loc.getWorld().equals(p1.getWorld())) return false;
double minX = Math.min(p1.getX(), p2.getX()); // KONSISTENTER BUFFER: 0.5 Blöcke Toleranz (horizontal UND vertikal)
double maxX = Math.max(p1.getX(), p2.getX()); // Konfigurierbar machen über Config falls gewünscht
double minY = Math.min(p1.getY(), p2.getY()); double buffer = plugin.getConfig().getDouble("ArenaRegionBuffer", 0.5);
double maxY = Math.max(p1.getY(), p2.getY());
double minZ = Math.min(p1.getZ(), p2.getZ()); double minX = Math.min(p1.getX(), p2.getX()) - buffer;
double maxZ = Math.max(p1.getZ(), p2.getZ()); double maxX = Math.max(p1.getX(), p2.getX()) + buffer;
double minY = Math.min(p1.getY(), p2.getY()) - buffer;
double maxY = Math.max(p1.getY(), p2.getY()) + buffer;
double minZ = Math.min(p1.getZ(), p2.getZ()) - buffer;
double maxZ = Math.max(p1.getZ(), p2.getZ()) + buffer;
return loc.getX() >= minX && loc.getX() <= maxX && return loc.getX() >= minX && loc.getX() <= maxX &&
loc.getY() >= minY && loc.getY() <= maxY && loc.getY() >= minY && loc.getY() <= maxY &&
loc.getZ() >= minZ && loc.getZ() <= maxZ; loc.getZ() >= minZ && loc.getZ() <= maxZ;
} }
/**
* Gibt die minimale Y-Koordinate der Arena zurück
*/
public double getMinY(String arenaName) { public double getMinY(String arenaName) {
return arenaConfig.getDouble("Arenas." + arenaName + ".MinY", -100.0); return arenaConfig.getDouble("Arenas." + arenaName + ".MinY", -100.0);
} }
/**
* Gibt die Arena-Config zurück
*/
public FileConfiguration getArenaConfig() { public FileConfiguration getArenaConfig() {
return arenaConfig; return arenaConfig;
} }
} }

View File

@@ -34,4 +34,4 @@ public class ConfigManager {
plugin.getLogger().severe("Konnte " + filename + " nicht erstellen: " + e.getMessage()); plugin.getLogger().severe("Konnte " + filename + " nicht erstellen: " + e.getMessage());
} }
} }
} }

View File

@@ -61,4 +61,4 @@ public class Configs {
public File getSaveFile() { public File getSaveFile() {
return saveFile; return saveFile;
} }
} }

View File

@@ -1,6 +1,14 @@
package de.velocityfall.manager; package de.velocityfall.manager;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.utils.ScoreboardHelper;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.util.Map;
public class DisableReset { public class DisableReset {
@@ -10,7 +18,116 @@ public class DisableReset {
this.plugin = plugin; this.plugin = plugin;
} }
/**
* Wird beim Plugin-Shutdown aufgerufen
* Resettet alle Arenas und teleportiert Spieler zurück
*/
public void disableReset() { public void disableReset() {
plugin.getLogger().info("DisableReset ausgeführt Plugin heruntergefahren."); plugin.getLogger().info("DisableReset gestartet Räume Arenas auf...");
try {
resetAllArenas();
teleportAllPlayersToSpawn();
clearAllScoreboards();
} catch (Exception e) {
plugin.getLogger().severe("Fehler beim DisableReset: " + e.getMessage());
e.printStackTrace();
}
plugin.getLogger().info("DisableReset abgeschlossen Plugin heruntergefahren.");
} }
}
/**
* Setzt alle Arena-Blöcke zurück
*/
private void resetAllArenas() {
if (plugin.getArenaBlocksMap().isEmpty()) {
plugin.getLogger().info("Keine Blöcke zum Zurücksetzen gefunden.");
return;
}
int resetCount = 0;
for (Map.Entry<Block, String> entry : plugin.getArenaBlocksMap().entrySet()) {
try {
Block block = entry.getKey();
String materialName = entry.getValue();
Material material = Material.valueOf(materialName);
block.setType(material);
resetCount++;
} catch (Exception e) {
plugin.getLogger().warning("Fehler beim Block-Reset: " + e.getMessage());
}
}
plugin.getArenaBlocksMap().clear();
plugin.getLogger().info(resetCount + " Blöcke zurückgesetzt.");
}
/**
* Teleportiert alle Spieler aus Arenas zurück zum Spawn
*/
private void teleportAllPlayersToSpawn() {
if (plugin.getPlayerArenaMap().isEmpty()) {
plugin.getLogger().info("Keine Spieler in Arenas gefunden.");
return;
}
int teleportCount = 0;
for (Map.Entry<Player, String> entry : plugin.getPlayerArenaMap().entrySet()) {
try {
Player player = entry.getKey();
String arenaName = entry.getValue();
if (player == null || !player.isOnline()) continue;
// Versuche Lobby-Location zu finden
Location lobby = plugin.getArenaManager()
.getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby");
if (lobby != null) {
player.teleport(lobby);
} else if (player.getWorld() != null) {
// Fallback: Welt-Spawn
player.teleport(player.getWorld().getSpawnLocation());
}
// Spieler zurücksetzen
player.setGameMode(GameMode.SURVIVAL);
player.setAllowFlight(false);
player.setFlying(false);
player.getInventory().clear();
player.getInventory().setArmorContents(null);
player.setHealth(20.0);
player.setFoodLevel(20);
player.resetTitle();
player.sendMessage(VelocityFall.prefix + "§eServer wird heruntergefahren Du wurdest aus der Arena teleportiert.");
teleportCount++;
} catch (Exception e) {
plugin.getLogger().warning("Fehler beim Spieler-Teleport: " + e.getMessage());
}
}
plugin.getPlayerArenaMap().clear();
plugin.ingamePlayers.clear();
plugin.getLogger().info(teleportCount + " Spieler aus Arenas teleportiert.");
}
/**
* Entfernt alle Scoreboards
*/
private void clearAllScoreboards() {
int cleared = 0;
for (Player player : plugin.getServer().getOnlinePlayers()) {
try {
ScoreboardHelper.clearBoard(player);
cleared++;
} catch (Exception e) {
plugin.getLogger().warning("Fehler beim Scoreboard-Clear für " + player.getName());
}
}
plugin.getLogger().info(cleared + " Scoreboards entfernt.");
}
}

View File

@@ -1,6 +1,7 @@
package de.velocityfall.manager; package de.velocityfall.manager;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.utils.ScoreboardHelper;
import de.velocityfall.utils.StatsHandler; import de.velocityfall.utils.StatsHandler;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@@ -18,7 +19,6 @@ import org.bukkit.scheduler.BukkitRunnable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
public class GameManager { public class GameManager {
@@ -27,6 +27,7 @@ public class GameManager {
* --------------------------------------------------- */ * --------------------------------------------------- */
public static void startArenaCountdown(String arenaName) { public static void startArenaCountdown(String arenaName) {
final VelocityFall plugin = VelocityFall.getInstance(); final VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return;
if (plugin.getGameStartedMap().getOrDefault(arenaName, false)) return; if (plugin.getGameStartedMap().getOrDefault(arenaName, false)) return;
new BukkitRunnable() { new BukkitRunnable() {
@@ -56,12 +57,24 @@ public class GameManager {
Location spawn = plugin.getArenaManager() Location spawn = plugin.getArenaManager()
.getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); .getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn");
// PLAYED Stats erhöhen für alle Spieler
plugin.getPlayerArenaMap().forEach((p, a) -> { plugin.getPlayerArenaMap().forEach((p, a) -> {
if (arenaName.equals(a)) { if (arenaName.equals(a)) {
// Stats tracken
StatsHandler.addPlayed(p.getUniqueId().toString());
// Teleport & Setup
if (spawn != null) p.teleport(spawn); if (spawn != null) p.teleport(spawn);
p.setGameMode(GameMode.ADVENTURE); p.setGameMode(GameMode.ADVENTURE);
p.setAllowFlight(false); p.setAllowFlight(false);
p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1f, 1f); p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1f, 1f);
// Scoreboard auf InGame umschalten
try {
ScoreboardHelper.updateBoard(p, arenaName, true);
} catch (Exception e) {
plugin.getLogger().warning("Scoreboard Update fehlgeschlagen: " + e.getMessage());
}
} }
}); });
cancel(); cancel();
@@ -76,22 +89,24 @@ public class GameManager {
} }
/* --------------------------------------------------- /* ---------------------------------------------------
* WIN HANDLING * WIN HANDLING - MIT STATS TRACKING
* --------------------------------------------------- */ * --------------------------------------------------- */
public static void checkForWinner(String arenaName) { public static void checkForWinner(String arenaName) {
final VelocityFall plugin = VelocityFall.getInstance(); final VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return;
int remaining = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0); int remaining = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0);
if (remaining != 1) return; if (remaining != 1) return;
// 1) Versuche Gewinner zuverlässig über playerArenaMap (Single source) // 1) Gewinner finden (Nicht-Spectator)
Player winner = plugin.getPlayerArenaMap().entrySet().stream() Player winner = plugin.getPlayerArenaMap().entrySet().stream()
.filter(e -> arenaName.equals(e.getValue())) .filter(e -> arenaName.equals(e.getValue()))
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.filter(p -> p.getGameMode() != GameMode.SPECTATOR) // Nicht-Spectator = Gewinner .filter(p -> p.getGameMode() != GameMode.SPECTATOR)
.findFirst() .findFirst()
.orElse(null); .orElse(null);
// 2) Fallback: VelocityFall.getArenaPlayers(arenaName) // Fallback
if (winner == null) { if (winner == null) {
List<Player> arenaPlayers = VelocityFall.getArenaPlayers(arenaName); List<Player> arenaPlayers = VelocityFall.getArenaPlayers(arenaName);
for (Player p : arenaPlayers) { for (Player p : arenaPlayers) {
@@ -102,40 +117,155 @@ public class GameManager {
} }
} }
if (winner == null) return; // kein Gewinner auffindbar → exit if (winner == null) return;
final Player finalWinner = winner; final Player finalWinner = winner;
// Nachricht + Stats + Scoreboard // 2) Lose-Location ermitteln
try { Location loseLocation = findLoseLocation(arenaName);
finalWinner.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&', final Location finalLoseLocation = loseLocation;
plugin.getMessages().getMessagesConfig()
.getString("WinMessage", "&aDu hast gewonnen!"))); // 3) Verlierer sammeln (alle Spectators)
} catch (Exception ignored) {} List<Player> losers = new ArrayList<>();
plugin.getPlayerArenaMap().forEach((p, a) -> {
if (arenaName.equals(a) && !p.equals(finalWinner) && p.getGameMode() == GameMode.SPECTATOR) {
losers.add(p);
}
});
final List<Player> finalLosers = new ArrayList<>(losers);
// 4) STATS TRACKING - GEWINNER
try { try {
StatsHandler.addWin(finalWinner.getUniqueId().toString()); StatsHandler.addWin(finalWinner.getUniqueId().toString());
} catch (Exception ignored) {} } catch (Exception e) {
plugin.getLogger().warning("Win-Stats konnten nicht gespeichert werden: " + e.getMessage());
}
// 5) STATS TRACKING - VERLIERER
for (Player loser : finalLosers) {
try {
StatsHandler.addLose(loser.getUniqueId().toString());
} catch (Exception e) {
plugin.getLogger().warning("Lose-Stats konnten nicht gespeichert werden: " + e.getMessage());
}
}
// 6) GEWINNER VORBEREITEN
try { try {
finalWinner.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); finalWinner.setAllowFlight(true);
} catch (Exception ignored) {} finalWinner.setFlying(true);
Location winnerSafeLoc = plugin.getArenaManager()
.getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn");
if (winnerSafeLoc != null) {
finalWinner.teleport(winnerSafeLoc);
}
finalWinner.sendMessage(VelocityFall.prefix + ChatColor.translateAlternateColorCodes('&',
plugin.getMessages().getMessagesConfig().getString("WinMessage", "&aDu hast gewonnen!")));
finalWinner.sendTitle(
ChatColor.translateAlternateColorCodes('&', "&6&lSIEG!"),
ChatColor.translateAlternateColorCodes('&', "&eDu hast gewonnen!"),
10, 70, 20
);
finalWinner.playSound(finalWinner.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f);
ScoreboardHelper.clearBoard(finalWinner);
} catch (Exception e) {
plugin.getLogger().warning("Gewinner-Setup fehlgeschlagen: " + e.getMessage());
}
// Lose-Location: mehrere mögliche Keys supporten + YLose fallback // 7) VERLIERER BENACHRICHTIGEN
Location loseLocation = plugin.getArenaManager() for (Player loser : finalLosers) {
.getArenaConfig().getLocation("Arenas." + arenaName + ".Lose"); try {
if (loseLocation == null) loseLocation = plugin.getArenaManager() if (finalLoseLocation != null && loser.getGameMode() == GameMode.SPECTATOR) {
.getArenaConfig().getLocation("Arenas." + arenaName + ".LoseLocation"); loser.teleport(finalLoseLocation);
if (loseLocation == null) loseLocation = plugin.getArenaManager() }
.getArenaConfig().getLocation("Arenas." + arenaName + ".LosePoint");
if (loseLocation == null) loseLocation = plugin.getArenaManager() loser.sendTitle(
.getArenaConfig().getLocation("Arenas." + arenaName + ".SetLose"); ChatColor.translateAlternateColorCodes('&', "&c&lNIEDERLAGE"),
ChatColor.translateAlternateColorCodes('&', "&e" + finalWinner.getName() + " &7hat gewonnen!"),
10, 70, 20
);
loser.playSound(loser.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
ScoreboardHelper.clearBoard(loser);
} catch (Exception e) {
plugin.getLogger().warning("Verlierer-Setup fehlgeschlagen: " + e.getMessage());
}
}
// Spezial-Fallback: YLose vorhanden? Dann baue Location auf Basis von Spawn oder Lobby X/Z // 8) FEUERWERK MIT DELAY
int delaySeconds = plugin.getConfig().getInt("PostWinDelay", 4);
delaySeconds = Math.min(5, Math.max(3, delaySeconds));
final long delayTicks = delaySeconds * 20L;
new BukkitRunnable() {
int fireworksSpawned = 0;
@Override
public void run() {
if (fireworksSpawned < 5) {
try {
spawnFireworks(finalWinner.getLocation().clone().add(0, 1, 0), 1);
finalWinner.playSound(finalWinner.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1f, 1f);
for (Player loser : finalLosers) {
if (loser != null && loser.isOnline()) {
loser.playSound(loser.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_LAUNCH, 1f, 1f);
}
}
} catch (Exception e) {
plugin.getLogger().warning("Feuerwerk-Spawn fehlgeschlagen: " + e.getMessage());
}
fireworksSpawned++;
} else {
cancel();
// Nach Feuerwerk: Arena resetten
Bukkit.getScheduler().runTaskLater(plugin, () -> {
try {
performSafeResetAndLobbyTeleport(arenaName, finalWinner, finalLosers);
} catch (Exception e) {
plugin.getLogger().severe("Arena-Reset fehlgeschlagen: " + e.getMessage());
}
try {
plugin.ingameArenas.remove(arenaName);
plugin.getSignManager().updateSign(arenaName);
} catch (Exception ignored) {}
}, 40L);
}
}
}.runTaskTimer(plugin, delayTicks, 10L);
}
/* ---------------------------------------------------
* LOSE LOCATION FINDER - HELPER
* --------------------------------------------------- */
private static Location findLoseLocation(String arenaName) {
final VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return null;
var arenaConfig = plugin.getArenaManager().getArenaConfig();
// Verschiedene mögliche Keys probieren
Location loseLocation = arenaConfig.getLocation("Arenas." + arenaName + ".Lose");
if (loseLocation == null) loseLocation = arenaConfig.getLocation("Arenas." + arenaName + ".LoseLocation");
if (loseLocation == null) loseLocation = arenaConfig.getLocation("Arenas." + arenaName + ".LosePoint");
if (loseLocation == null) loseLocation = arenaConfig.getLocation("Arenas." + arenaName + ".SetLose");
// YLose Fallback
if (loseLocation == null) { if (loseLocation == null) {
Double yLose = plugin.getArenaManager().getArenaConfig().getDouble("Arenas." + arenaName + ".YLose", Double.NaN); Double yLose = arenaConfig.getDouble("Arenas." + arenaName + ".YLose", Double.NaN);
if (!yLose.isNaN()) { if (!yLose.isNaN()) {
// Priorität: Spawn -> Lobby -> arena center (Pos1/Pos2 midpoint) Location spawn = arenaConfig.getLocation("Arenas." + arenaName + ".Spawn");
Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); Location lobby = arenaConfig.getLocation("Arenas." + arenaName + ".Lobby");
Location lobby = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby");
if (spawn != null) { if (spawn != null) {
loseLocation = spawn.clone(); loseLocation = spawn.clone();
loseLocation.setY(yLose); loseLocation.setY(yLose);
@@ -143,9 +273,9 @@ public class GameManager {
loseLocation = lobby.clone(); loseLocation = lobby.clone();
loseLocation.setY(yLose); loseLocation.setY(yLose);
} else { } else {
// midpoint of Pos1 and Pos2 if available Location pos1 = arenaConfig.getLocation("Arenas." + arenaName + ".Pos1");
Location pos1 = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Pos1"); Location pos2 = arenaConfig.getLocation("Arenas." + arenaName + ".Pos2");
Location pos2 = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Pos2");
if (pos1 != null && pos2 != null && pos1.getWorld() != null && pos1.getWorld().equals(pos2.getWorld())) { if (pos1 != null && pos2 != null && pos1.getWorld() != null && pos1.getWorld().equals(pos2.getWorld())) {
double midX = (pos1.getX() + pos2.getX()) / 2.0; double midX = (pos1.getX() + pos2.getX()) / 2.0;
double midZ = (pos1.getZ() + pos2.getZ()) / 2.0; double midZ = (pos1.getZ() + pos2.getZ()) / 2.0;
@@ -155,112 +285,23 @@ public class GameManager {
} }
} }
final Location finalLoseLocation = loseLocation; // final für Lambdas return loseLocation;
// Ermittlung der Verlierer: alle Spectator-Spieler in dieser Arena
List<Player> 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<Player> 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<Player> 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) * SAFE RESET & LOBBY TELEPORT
* --------------------------------------------------- */ * --------------------------------------------------- */
private static void performSafeResetAndLobbyTeleport(String arenaName, Player winner, List<Player> losers) { private static void performSafeResetAndLobbyTeleport(String arenaName, Player winner, List<Player> losers) {
final VelocityFall plugin = VelocityFall.getInstance(); final VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return;
// Blöcke zurücksetzen // Blöcke zurücksetzen
plugin.getArenaBlocksMap().forEach((block, matName) -> { plugin.getArenaBlocksMap().forEach((block, matName) -> {
try { try {
block.setType(Material.valueOf(matName)); block.setType(Material.valueOf(matName));
} catch (Exception ignored) {} } catch (Exception e) {
plugin.getLogger().warning("Block-Reset fehlgeschlagen: " + e.getMessage());
}
}); });
plugin.getArenaBlocksMap().clear(); plugin.getArenaBlocksMap().clear();
@@ -268,65 +309,42 @@ public class GameManager {
plugin.getGameStartedMap().put(arenaName, false); plugin.getGameStartedMap().put(arenaName, false);
plugin.getArenaPlayersCountMap().remove(arenaName); plugin.getArenaPlayersCountMap().remove(arenaName);
// Lobby-Location (kann null sein) // Lobby-Location
Location lobby = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby"); Location lobby = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Lobby");
// Sammle alle Spieler, die teleportiert werden müssen // Alle Spieler sammeln
List<Player> toHandle = new ArrayList<>(); List<Player> toHandle = new ArrayList<>();
if (winner != null) toHandle.add(winner);
// Gewinner hinzufügen
if (winner != null) {
toHandle.add(winner);
}
// Verlierer hinzufügen (bereits als finale Liste übergeben)
for (Player loser : losers) { for (Player loser : losers) {
if (loser != null && !toHandle.contains(loser)) { if (loser != null && !toHandle.contains(loser)) {
toHandle.add(loser); toHandle.add(loser);
} }
} }
// Zusätzlich: alle restlichen Spieler aus playerArenaMap (Sicherheit) // Sicherheit: Alle aus playerArenaMap
plugin.getPlayerArenaMap().forEach((p, a) -> { plugin.getPlayerArenaMap().forEach((p, a) -> {
if (arenaName.equals(a) && !toHandle.contains(p)) { if (arenaName.equals(a) && !toHandle.contains(p)) {
toHandle.add(p); toHandle.add(p);
} }
}); });
// Fallback: ingamePlayers // Alle Spieler resetten
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<Player> 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) { for (Player p : toHandle) {
try { try {
p.setGameMode(GameMode.SURVIVAL); // GM1 p.setGameMode(GameMode.SURVIVAL);
p.setAllowFlight(false);
p.setFlying(false);
p.getInventory().clear(); p.getInventory().clear();
p.getInventory().setArmorContents(null); p.getInventory().setArmorContents(null);
p.setHealth(20.0); p.setHealth(20.0);
p.setFoodLevel(20); p.setFoodLevel(20);
p.resetTitle();
ScoreboardHelper.clearBoard(p);
if (lobby != null) { if (lobby != null) {
p.teleport(lobby); p.teleport(lobby);
} else { } else {
// Fallback: Spawn-Location oder World-Spawn
Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn"); Location spawn = plugin.getArenaManager().getArenaConfig().getLocation("Arenas." + arenaName + ".Spawn");
if (spawn != null) { if (spawn != null) {
p.teleport(spawn); p.teleport(spawn);
@@ -335,11 +353,10 @@ public class GameManager {
} }
} }
// Jetzt erst aus den Maps entfernen
plugin.ingamePlayers.remove(p); plugin.ingamePlayers.remove(p);
plugin.getPlayerArenaMap().remove(p); plugin.getPlayerArenaMap().remove(p);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); plugin.getLogger().warning("Spieler-Reset fehlgeschlagen für " + p.getName() + ": " + e.getMessage());
} }
} }
@@ -364,12 +381,16 @@ public class GameManager {
for (int i = 0; i < Math.max(1, count); i++) { for (int i = 0; i < Math.max(1, count); i++) {
try { try {
Firework fw = loc.getWorld().spawn(loc.clone().add(0, 1 + i * 0.5, 0), Firework.class); Firework fw = loc.getWorld().spawn(loc.clone().add(0, i * 0.5, 0), Firework.class);
FireworkMeta meta = fw.getFireworkMeta(); FireworkMeta meta = fw.getFireworkMeta();
Color[] colors = {Color.LIME, Color.YELLOW, Color.ORANGE, Color.RED, Color.AQUA, Color.FUCHSIA};
Color randomColor1 = colors[(int)(Math.random() * colors.length)];
Color randomColor2 = colors[(int)(Math.random() * colors.length)];
FireworkEffect effect = FireworkEffect.builder() FireworkEffect effect = FireworkEffect.builder()
.with(FireworkEffect.Type.BALL_LARGE) .with(FireworkEffect.Type.BALL_LARGE)
.withColor(Color.LIME, Color.YELLOW) .withColor(randomColor1, randomColor2)
.withFade(Color.WHITE) .withFade(Color.WHITE)
.trail(true) .trail(true)
.flicker(true) .flicker(true)
@@ -379,7 +400,7 @@ public class GameManager {
meta.setPower(1); meta.setPower(1);
fw.setFireworkMeta(meta); fw.setFireworkMeta(meta);
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace(); VelocityFall.getInstance().getLogger().warning("Firework-Spawn fehlgeschlagen: " + t.getMessage());
} }
} }
} }
@@ -389,10 +410,12 @@ public class GameManager {
* --------------------------------------------------- */ * --------------------------------------------------- */
private static void broadcast(String arena, String msg) { private static void broadcast(String arena, String msg) {
final VelocityFall plugin = VelocityFall.getInstance(); final VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return;
plugin.getPlayerArenaMap().forEach((player, a) -> { plugin.getPlayerArenaMap().forEach((player, a) -> {
if (arena.equals(a)) { if (arena.equals(a)) {
player.sendMessage(VelocityFall.prefix + msg); player.sendMessage(VelocityFall.prefix + msg);
} }
}); });
} }
} }

View File

@@ -1,6 +1,7 @@
package de.velocityfall.manager; package de.velocityfall.manager;
import de.velocityfall.VelocityFall; import de.velocityfall.VelocityFall;
import de.velocityfall.utils.ScoreboardHelper;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -54,6 +55,9 @@ public class JoinManager {
player.sendMessage(VelocityFall.prefix + "§aBeigetreten: §e" + arenaName); player.sendMessage(VelocityFall.prefix + "§aBeigetreten: §e" + arenaName);
// Scoreboard setzen (Lobby-Scoreboard)
ScoreboardHelper.updateBoard(player, arenaName, false);
int min = plugin.getConfig().getInt("MinPlayers", 2); int min = plugin.getConfig().getInt("MinPlayers", 2);
if (current + 1 >= min && !plugin.getGameStartedMap().getOrDefault(arenaName, false)) { if (current + 1 >= min && !plugin.getGameStartedMap().getOrDefault(arenaName, false)) {
GameManager.startArenaCountdown(arenaName); GameManager.startArenaCountdown(arenaName);
@@ -62,6 +66,9 @@ public class JoinManager {
} }
plugin.getSignManager().updateSign(arenaName); plugin.getSignManager().updateSign(arenaName);
// Scoreboard für alle anderen in der Arena updaten
ScoreboardHelper.updateAllInArena(arenaName, false);
} }
public void join(Player player, String arenaName) { public void join(Player player, String arenaName) {
@@ -73,4 +80,4 @@ public class JoinManager {
if (arena.equals(a)) p.sendMessage(VelocityFall.prefix + msg); if (arena.equals(a)) p.sendMessage(VelocityFall.prefix + msg);
}); });
} }
} }

View File

@@ -44,4 +44,4 @@ public class Messages {
public YamlConfiguration getMessagesConfig() { public YamlConfiguration getMessagesConfig() {
return messagesConfig; return messagesConfig;
} }
} }

View File

@@ -34,6 +34,9 @@ public class MySQLConnector {
} }
} }
/**
* Führt ein UPDATE, INSERT oder DELETE Statement aus
*/
public void executeUpdate(String sql, Object... params) { public void executeUpdate(String sql, Object... params) {
try (PreparedStatement ps = connection.prepareStatement(sql)) { try (PreparedStatement ps = connection.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) { for (int i = 0; i < params.length; i++) {
@@ -42,23 +45,85 @@ public class MySQLConnector {
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().warning("Update fehlgeschlagen: " + e.getMessage()); plugin.getLogger().warning("Update fehlgeschlagen: " + e.getMessage());
connect(); reconnect();
} }
} }
public ResultSet executeQuery(String sql, Object... params) throws SQLException { /**
PreparedStatement ps = connection.prepareStatement(sql); * Gibt einen einzelnen Integer-Wert zurück (ohne Connection Leak)
for (int i = 0; i < params.length; i++) { */
ps.setObject(i + 1, params[i]); public Integer executeQuerySingleInt(String sql, String columnName, Object... params) {
try (PreparedStatement ps = connection.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getInt(columnName);
}
}
} catch (SQLException e) {
plugin.getLogger().warning("Query fehlgeschlagen: " + e.getMessage());
reconnect();
} }
return ps.executeQuery(); return null;
} }
/**
* Gibt einen einzelnen String-Wert zurück
*/
public String executeQuerySingleString(String sql, String columnName, Object... params) {
try (PreparedStatement ps = connection.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString(columnName);
}
}
} catch (SQLException e) {
plugin.getLogger().warning("Query fehlgeschlagen: " + e.getMessage());
reconnect();
}
return null;
}
/**
* Prüft ob Verbindung noch aktiv ist und reconnected falls nötig
*/
private void reconnect() {
try {
if (connection == null || connection.isClosed()) {
connect();
}
} catch (SQLException e) {
plugin.getLogger().severe("Reconnect fehlgeschlagen: " + e.getMessage());
}
}
/**
* Prüft ob MySQL verbunden ist
*/
public boolean isConnected() {
try {
return connection != null && !connection.isClosed();
} catch (SQLException e) {
return false;
}
}
/**
* Schließt die Verbindung sauber
*/
public void close() { public void close() {
try { try {
if (connection != null && !connection.isClosed()) connection.close(); if (connection != null && !connection.isClosed()) {
connection.close();
plugin.getLogger().info("MySQL Verbindung geschlossen.");
}
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); plugin.getLogger().warning("Fehler beim Schließen der MySQL Verbindung: " + e.getMessage());
} }
} }
} }

View File

@@ -12,19 +12,24 @@ public class SignManager {
} }
public void updateSign(String arenaName) { public void updateSign(String arenaName) {
var arenaConfig = plugin.getArenaManager().getArenaConfig(); try {
Location signLoc = arenaConfig.getLocation("Arenas." + arenaName + ".SignLoc"); var arenaConfig = plugin.getArenaManager().getArenaConfig();
Location signLoc = arenaConfig.getLocation("Arenas." + arenaName + ".SignLoc");
if (signLoc == null || !(signLoc.getBlock().getState() instanceof Sign sign)) return; if (signLoc == null) return;
if (!(signLoc.getBlock().getState() instanceof Sign sign)) return;
int count = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0); int count = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0);
int max = arenaConfig.getInt("Arenas." + arenaName + ".Max", 0); int max = arenaConfig.getInt("Arenas." + arenaName + ".Max", 0);
sign.setLine(0, "§8§l- §cVelocityFall §8§l-"); sign.setLine(0, "§8§l- §cVelocityFall §8§l-");
sign.setLine(1, "§b" + arenaName); sign.setLine(1, "§b" + arenaName);
sign.setLine(2, plugin.getGameStartedMap().getOrDefault(arenaName, false) ? "§cINGAME" : "§aLOBBY"); sign.setLine(2, plugin.getGameStartedMap().getOrDefault(arenaName, false) ? "§cINGAME" : "§aLOBBY");
sign.setLine(3, "§8" + count + "§7/§8" + max); sign.setLine(3, "§8" + count + "§7/§8" + max);
sign.update(true, false); sign.update(true, false);
} catch (Exception e) {
plugin.getLogger().warning("Sign Update fehlgeschlagen für Arena " + arenaName + ": " + e.getMessage());
}
} }
} }

View File

@@ -48,4 +48,4 @@ public class Stats {
public YamlConfiguration getStatsConfig() { public YamlConfiguration getStatsConfig() {
return statsConfig; return statsConfig;
} }
} }

View File

@@ -6,40 +6,164 @@ import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scoreboard.*; import org.bukkit.scoreboard.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ScoreboardHelper { public class ScoreboardHelper {
public static void updateBoard(Player player, String arenaName, boolean isIngame) { public static void updateBoard(Player player, String arenaName, boolean isIngame) {
VelocityFall plugin = VelocityFall.getInstance(); try {
VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) return;
Scoreboard board = Bukkit.getScoreboardManager().getNewScoreboard(); Scoreboard board = Bukkit.getScoreboardManager().getNewScoreboard();
String titlePath = isIngame ? "InGameScoreBoardTitle" : "LobbyScoreBoardTitle"; String titlePath = isIngame ? "InGameScoreBoardTitle" : "LobbyScoreBoardTitle";
String title = ChatColor.translateAlternateColorCodes('&', plugin.getConfig().getString(titlePath, "&6VelocityFall")); String rawTitle = plugin.getConfig().getString(titlePath, "&6&lVELOCITY FALL");
String title = ChatColor.translateAlternateColorCodes('&', rawTitle);
Objective objective = board.registerNewObjective("vfall", Criteria.DUMMY, title); // Längen-Check OHNE ChatColor Codes
objective.setDisplaySlot(DisplaySlot.SIDEBAR); String strippedTitle = ChatColor.stripColor(title);
if (strippedTitle.length() > 32) {
// Intelligentes Kürzen: Behalte Farben bei
title = title.substring(0, Math.min(title.length(), 32));
}
String listPath = isIngame ? "InGameScoreBoard" : "LobbyScoreBoard"; Objective objective = board.registerNewObjective("vfall", "dummy", title);
List<String> lines = plugin.getConfig().getStringList(listPath); objective.setDisplaySlot(DisplaySlot.SIDEBAR);
int scoreValue = lines.size(); String listPath = isIngame ? "InGameScoreBoard" : "LobbyScoreBoard";
for (String rawLine : lines) { List<String> configLines = plugin.getConfig().getStringList(listPath);
String line = ChatColor.translateAlternateColorCodes('&', rawLine
.replace("%arena%", arenaName != null ? arenaName : "???")
.replace("%players%", String.valueOf(plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0)))
.replace("%max%", String.valueOf(plugin.getArenaManager().getArenaConfig().getInt("Arenas." + arenaName + ".Max", 0))));
objective.getScore(line).setScore(scoreValue--); // Fallback falls Config leer ist
if (configLines == null || configLines.isEmpty()) {
configLines = getDefaultLines(isIngame);
}
// Variablen für Replacements
int currentPlayers = plugin.getArenaPlayersCountMap().getOrDefault(arenaName, 0);
int maxPlayers = 0;
if (arenaName != null) {
maxPlayers = plugin.getArenaManager().getArenaConfig().getInt("Arenas." + arenaName + ".Max", 12);
}
// Status-Anzeige
String statusColor = isIngame ? "&c" : "&a";
String statusText = isIngame ? "IM SPIEL" : "LOBBY";
// Spieler-Farbe basierend auf Füllstand
String playerColor = getPlayerCountColor(currentPlayers, maxPlayers);
List<String> processedLines = new ArrayList<>();
int scoreValue = configLines.size();
for (String rawLine : configLines) {
String line = ChatColor.translateAlternateColorCodes('&', rawLine
.replace("%arena%", arenaName != null ? arenaName : "???")
.replace("%players%", String.valueOf(currentPlayers))
.replace("%max%", String.valueOf(maxPlayers))
.replace("%player%", player.getName())
.replace("%status%", statusText)
.replace("%status_color%", statusColor)
.replace("%player_color%", playerColor));
// Zeilen-Länge prüfen
if (line.length() > 40) {
line = line.substring(0, 40);
}
// Duplikate verhindern
while (processedLines.contains(line)) {
line = line + ChatColor.RESET;
}
processedLines.add(line);
// Score setzen
Score score = objective.getScore(line);
score.setScore(scoreValue--);
}
player.setScoreboard(board);
} catch (Exception e) {
VelocityFall plugin = VelocityFall.getInstance();
if (plugin != null) {
plugin.getLogger().warning("Scoreboard Update fehlgeschlagen: " + e.getMessage());
}
} }
player.setScoreboard(board);
} }
/**
* Gibt die passende Farbe basierend auf der Spieleranzahl zurück
*/
private static String getPlayerCountColor(int current, int max) {
if (max == 0) return "&7";
double percentage = (double) current / max;
if (percentage >= 0.8) return "&c"; // 80%+ = rot (fast voll)
if (percentage >= 0.5) return "&e"; // 50%+ = gelb
return "&a"; // <50% = grün (viel Platz)
}
/**
* Updated Scoreboard für alle Spieler in einer Arena
*/
public static void updateAllInArena(String arenaName, boolean isIngame) { public static void updateAllInArena(String arenaName, boolean isIngame) {
VelocityFall plugin = VelocityFall.getInstance(); try {
plugin.getPlayerArenaMap().forEach((p, a) -> { VelocityFall plugin = VelocityFall.getInstance();
if (arenaName.equals(a)) updateBoard(p, arenaName, isIngame); if (plugin == null) return;
});
plugin.getPlayerArenaMap().forEach((p, a) -> {
if (arenaName.equals(a)) {
updateBoard(p, arenaName, isIngame);
}
});
} catch (Exception e) {
VelocityFall plugin = VelocityFall.getInstance();
if (plugin != null) {
plugin.getLogger().warning("Scoreboard Batch-Update fehlgeschlagen: " + e.getMessage());
}
}
} }
}
/**
* Entfernt Scoreboard von einem Spieler
*/
public static void clearBoard(Player player) {
try {
if (player != null && player.isOnline()) {
player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard());
}
} catch (Exception e) {
// Ignoriere Fehler beim Cleanup
}
}
/**
* Standard-Zeilen falls Config leer ist (MODERN DESIGN)
*/
private static List<String> getDefaultLines(boolean isIngame) {
List<String> lines = new ArrayList<>();
if (isIngame) {
lines.add("&8&m--------------------");
lines.add(" ");
lines.add(" &6⚔ &eArena&8: &f%arena%");
lines.add(" &c❤ &eÜbrig&8: %player_color%%players%");
lines.add(" ");
lines.add(" &a✓ &7Überlebe so lange");
lines.add(" &7wie möglich!");
lines.add(" ");
lines.add("&8&m--------------------");
} else {
lines.add("&8&m--------------------");
lines.add(" ");
lines.add(" &6⚔ &eArena&8: &f%arena%");
lines.add(" &b👥 &eSpieler&8: %player_color%%players%&8/&7%max%");
lines.add(" ");
lines.add(" &7Warte auf Spieler...");
lines.add(" ");
lines.add("&8&m--------------------");
}
return lines;
}
}

View File

@@ -5,53 +5,138 @@ import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
public class StatsHandler { public class StatsHandler {
/**
* Erhöht den Win-Counter für einen Spieler
*/
public static void addWin(String uuid) { public static void addWin(String uuid) {
addStat(uuid, "WINS"); addStat(uuid, "WINS");
} }
/**
* Erhöht den Lose-Counter für einen Spieler
*/
public static void addLose(String uuid) { public static void addLose(String uuid) {
addStat(uuid, "LOSE"); addStat(uuid, "LOSE");
} }
/**
* Erhöht den Played-Counter für einen Spieler
* NEU: Wird jetzt beim Game-Start aufgerufen
*/
public static void addPlayed(String uuid) {
addStat(uuid, "PLAYED");
}
/**
* Interne Methode zum Erhöhen eines Stats
*/
private static void addStat(String uuid, String column) { private static void addStat(String uuid, String column) {
VelocityFall plugin = VelocityFall.getInstance(); VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) {
return;
}
if (plugin.getStats().getStatsConfig().getBoolean("EnableMySQL", false)) { if (plugin.getStats().getStatsConfig().getBoolean("EnableMySQL", false)) {
plugin.getMySQLConnector().executeUpdate( // MySQL: INSERT ... ON DUPLICATE KEY UPDATE
"UPDATE VelocityFallStats SET " + column + " = " + column + " + 1 WHERE UUID = ?", // Erstellt automatisch einen Eintrag falls UUID noch nicht existiert
uuid if (plugin.getMySQLConnector() != null && plugin.getMySQLConnector().isConnected()) {
); plugin.getMySQLConnector().executeUpdate(
"INSERT INTO VelocityFallStats (UUID, " + column + ") VALUES (?, 1) " +
"ON DUPLICATE KEY UPDATE " + column + " = " + column + " + 1",
uuid
);
}
} else { } else {
// YAML Fallback
File file = new File(plugin.getDataFolder(), "stats.yml"); File file = new File(plugin.getDataFolder(), "stats.yml");
YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file); YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file);
cfg.set(uuid + "." + column, cfg.getInt(uuid + "." + column, 0) + 1); cfg.set(uuid + "." + column, cfg.getInt(uuid + "." + column, 0) + 1);
try { try {
cfg.save(file); cfg.save(file);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); plugin.getLogger().warning("Konnte stats.yml nicht speichern: " + e.getMessage());
} }
} }
} }
/**
* Holt die Anzahl der Wins
*/
public static int getWins(String uuid) { public static int getWins(String uuid) {
return getStat(uuid, "WINS"); return getStat(uuid, "WINS");
} }
/**
* Holt die Anzahl der Loses
*/
public static int getLoses(String uuid) {
return getStat(uuid, "LOSE");
}
/**
* Holt die Anzahl der gespielten Runden
*/
public static int getPlayed(String uuid) {
return getStat(uuid, "PLAYED");
}
/**
* Interne Methode zum Abrufen eines Stats
*/
private static int getStat(String uuid, String column) { private static int getStat(String uuid, String column) {
VelocityFall plugin = VelocityFall.getInstance(); VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) {
return 0;
}
if (plugin.getStats().getStatsConfig().getBoolean("EnableMySQL", false)) { if (plugin.getStats().getStatsConfig().getBoolean("EnableMySQL", false)) {
try (ResultSet rs = plugin.getMySQLConnector().executeQuery( // MySQL
"SELECT " + column + " FROM VelocityFallStats WHERE UUID = ?", uuid)) { if (plugin.getMySQLConnector() != null && plugin.getMySQLConnector().isConnected()) {
if (rs.next()) return rs.getInt(column); Integer result = plugin.getMySQLConnector().executeQuerySingleInt(
} catch (SQLException e) { "SELECT " + column + " FROM VelocityFallStats WHERE UUID = ?",
e.printStackTrace(); column,
uuid
);
return result != null ? result : 0;
} }
} }
// YAML Fallback
File file = new File(plugin.getDataFolder(), "stats.yml"); File file = new File(plugin.getDataFolder(), "stats.yml");
return YamlConfiguration.loadConfiguration(file).getInt(uuid + "." + column, 0); return YamlConfiguration.loadConfiguration(file).getInt(uuid + "." + column, 0);
} }
}
/**
* Setzt alle Stats für einen Spieler zurück
*/
public static void resetStats(String uuid) {
VelocityFall plugin = VelocityFall.getInstance();
if (plugin == null) {
return;
}
if (plugin.getStats().getStatsConfig().getBoolean("EnableMySQL", false)) {
if (plugin.getMySQLConnector() != null && plugin.getMySQLConnector().isConnected()) {
plugin.getMySQLConnector().executeUpdate(
"DELETE FROM VelocityFallStats WHERE UUID = ?",
uuid
);
}
} else {
File file = new File(plugin.getDataFolder(), "stats.yml");
YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file);
cfg.set(uuid, null);
try {
cfg.save(file);
} catch (IOException e) {
plugin.getLogger().warning("Konnte stats.yml nicht speichern: " + e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,33 @@
package de.velocityfall.utils;
import de.velocityfall.VelocityFall;
import org.bukkit.Bukkit;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Scanner;
import java.util.function.Consumer;
public class UpdateChecker {
private final VelocityFall plugin;
private final int resourceId;
public UpdateChecker(VelocityFall plugin, int resourceId) {
this.plugin = plugin;
this.resourceId = resourceId;
}
public void getLatestVersion(final Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
try (InputStream inputStream = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId).openStream();
Scanner scanner = new Scanner(inputStream)) {
if (scanner.hasNext()) {
consumer.accept(scanner.next());
}
} catch (IOException exception) {
plugin.getLogger().info("Update-Check fehlgeschlagen: " + exception.getMessage());
}
});
}
}

View File

@@ -1,27 +1,127 @@
Prefix: '&c[VelocityFall] ' # _ __ __ _ __ ______ ____
# | | / /__ / /___ _____(_) /___ __/ ____/___ _/ / /
# | | / / _ \/ / __ \/ ___/ / __/ / / / /_ / __ `/ / /
# | |/ / __/ / /_/ / /__/ / /_/ /_/ / __/ / /_/ / / /
# |___/\___/_/\____/\___/_/\__/\__, /_/ \__,_/_/_/
# /____/
# VelocityFall - Minecraft Minigame Plugin
# Beispiel-Konfiguration
Prefix: "&6Velocity&fFall &8» "
NoPermission: "&cKeine Rechte!"
# Minimale Spieleranzahl für Start
MinPlayers: 2
# Countdown vor Spielstart (Sekunden)
BeforeGameCD: 15 BeforeGameCD: 15
CountdownSound: ENTITY_EXPERIENCE_ORB_PICKUP
SaveInv: true # Delay nach Sieg bevor Reset (Sekunden, 3-5)
PostWinDelay: 4 PostWinDelay: 4
WinCommands:
- eco give %player% 10 # Inventar speichern?
- msg %player% Du hast gewonnen! SaveInv: false
CmdWhitelist:
- /vfall leave # ========================================
- /tell # LOBBY SCOREBOARD (vor Spielstart)
MySQL: # ========================================
Enable: false LobbyScoreBoardTitle: "&6&lVELOCITY &f&lFALL"
Host: localhost
Port: '3306'
Database: vfall
User: root
Password: ''
LobbyScoreBoardTitle: '&6&lVelocityFall'
InGameScoreBoardTitle: '&6&lVelocityFall'
LobbyScoreBoard: LobbyScoreBoard:
- '&7---' - "&8&m━━━━━━━━━━━━━━━━━━━━━"
- '&eArena:' - " "
- '&f%arena%' - " &6⚔ &eArena&8: &f%arena%"
- '&7' - " &b👥 &eSpieler&8: %player_color%%players%&8/&7%max%"
- '&eSpieler:' - " "
- '&f%players%' - " &7Warte auf Spieler..."
- " "
- "&8&m━━━━━━━━━━━━━━━━━━━━━"
# ========================================
# INGAME SCOREBOARD (während des Spiels)
# ========================================
InGameScoreBoardTitle: "&c&lIM &f&lSPIEL"
InGameScoreBoard:
- "&8&m━━━━━━━━━━━━━━━━━━━━━"
- " "
- " &6⚔ &eArena&8: &f%arena%"
- " &c❤ &eÜbrig&8: %player_color%%players%"
- " "
- " &a✓ &7Überlebe so lange"
- " &7wie möglich!"
- " "
- " &e⚠ &7Pass auf die"
- " &7Blöcke auf!"
- " "
- "&8&m━━━━━━━━━━━━━━━━━━━━━"
# ========================================
# VERFÜGBARE PLATZHALTER
# ========================================
# %arena% - Arena-Name
# %players% - Aktuelle Spielerzahl
# %max% - Maximale Spielerzahl
# %player% - Name des Spielers
# %status% - Status-Text (LOBBY/IM SPIEL)
# %status_color% - Farbe basierend auf Status
# %player_color% - Farbe basierend auf Füllstand (grün/gelb/rot)
# ========================================
# ALTERNATIVE DESIGNS (kopiere diese in die Scoreboard-Sektion)
# ========================================
# DESIGN 1: Minimalistisch
# LobbyScoreBoard:
# - "&7┌─────────────────┐"
# - " &6⚡ &e%arena%"
# - " &b🎮 %player_color%%players%&7/%max%"
# - "&7└─────────────────┘"
# DESIGN 2: Retro Style
# LobbyScoreBoard:
# - "&8╔═══════════════════╗"
# - " "
# - " &e► Arena: &f%arena%"
# - " &e► Spieler: %player_color%%players%&7/%max%"
# - " "
# - "&8╚═══════════════════╝"
# DESIGN 3: Modern Gradient
# LobbyScoreBoard:
# - "&6▬▬▬&e▬▬▬&f▬▬▬&e▬▬▬&6▬▬▬"
# - " "
# - " &6⚡ &eArena &f%arena%"
# - " &b👤 %player_color%%players%&8/&7%max% &eSpieler"
# - " "
# - " &7Bereit? Warte..."
# - " "
# - "&6▬▬▬&e▬▬▬&f▬▬▬&e▬▬▬&6▬▬▬"
# DESIGN 4: Kompakt
# LobbyScoreBoard:
# - "&8━━━━━━━━━━━━━━━━"
# - "&6⚔ &e%arena%"
# - "&b👥 %player_color%%players%&7/%max%"
# - "&7Warte..."
# - "&8━━━━━━━━━━━━━━━━"
# ========================================
# FARB-CODES REFERENZ
# ========================================
# &0 = Schwarz &8 = Dunkelgrau
# &1 = Dunkelblau &9 = Blau
# &2 = Dunkelgrün &a = Grün
# &3 = Türkis &b = Hellblau
# &4 = Dunkelrot &c = Rot
# &5 = Lila &d = Pink
# &6 = Gold &e = Gelb
# &7 = Grau &f = Weiß
#
# &l = Fett
# &m = Durchgestrichen
# &n = Unterstrichen
# &o = Kursiv
# &r = Reset

View File

@@ -1,5 +1,5 @@
name: VelocityFall name: VelocityFall
version: 1.0.0 version: 1.0.1
main: de.velocityfall.VelocityFall main: de.velocityfall.VelocityFall
api-version: 1.21 api-version: 1.21
author: M_Viper author: M_Viper