Update from Git Manager GUI
This commit is contained in:
@@ -4,6 +4,7 @@ import de.fussball.plugin.arena.Arena;
|
||||
import de.fussball.plugin.arena.ArenaManager;
|
||||
import de.fussball.plugin.commands.FussballCommand;
|
||||
import de.fussball.plugin.game.GameManager;
|
||||
import de.fussball.plugin.hologram.HologramManager;
|
||||
import de.fussball.plugin.listeners.*;
|
||||
import de.fussball.plugin.placeholders.FussballPlaceholders;
|
||||
import de.fussball.plugin.stats.StatsManager;
|
||||
@@ -18,6 +19,7 @@ public class Fussball extends JavaPlugin {
|
||||
private GameManager gameManager;
|
||||
private StatsManager statsManager;
|
||||
private SignListener signListener;
|
||||
private HologramManager hologramManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
@@ -30,6 +32,7 @@ public class Fussball extends JavaPlugin {
|
||||
gameManager = new GameManager(this);
|
||||
statsManager = new StatsManager(this);
|
||||
signListener = new SignListener(this);
|
||||
hologramManager = new HologramManager(this);
|
||||
Messages.init(this);
|
||||
|
||||
registerCommands();
|
||||
@@ -50,6 +53,7 @@ public class Fussball extends JavaPlugin {
|
||||
public void onDisable() {
|
||||
if (gameManager != null) gameManager.stopAllGames();
|
||||
if (statsManager != null) statsManager.save();
|
||||
if (hologramManager != null) hologramManager.removeAll(); // Entities sauber entfernen
|
||||
getLogger().info("⚽ Fußball-Plugin gestoppt!");
|
||||
}
|
||||
|
||||
@@ -71,4 +75,5 @@ public class Fussball extends JavaPlugin {
|
||||
public GameManager getGameManager() { return gameManager; }
|
||||
public StatsManager getStatsManager() { return statsManager; }
|
||||
public SignListener getSignListener() { return signListener; }
|
||||
public HologramManager getHologramManager() { return hologramManager; }
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class Arena implements ConfigurationSerializable {
|
||||
private final String name;
|
||||
private Location center, redSpawn, blueSpawn, ballSpawn;
|
||||
private Location redGoalMin, redGoalMax, blueGoalMin, blueGoalMax, lobby;
|
||||
private Location spectatorSpawn; // Zuschauer-Spawn (optional – Fallback: Spielfeldrand)
|
||||
private Location fieldMin, fieldMax;
|
||||
// Strafräume – optional manuell gesetzt; sonst auto-berechnet aus Tor + config
|
||||
private Location redPenaltyMin, redPenaltyMax, bluePenaltyMin, bluePenaltyMax;
|
||||
@@ -259,6 +260,7 @@ public class Arena implements ConfigurationSerializable {
|
||||
if (bluePenaltyMax != null) map.put("bluePenaltyMax", serLoc(bluePenaltyMax));
|
||||
if (redPenaltySpot != null) map.put("redPenaltySpot", serLoc(redPenaltySpot));
|
||||
if (bluePenaltySpot != null) map.put("bluePenaltySpot", serLoc(bluePenaltySpot));
|
||||
if (spectatorSpawn != null) map.put("spectatorSpawn", serLoc(spectatorSpawn));
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -289,6 +291,7 @@ public class Arena implements ConfigurationSerializable {
|
||||
if (map.containsKey("bluePenaltyMax")) a.bluePenaltyMax = desLoc(map.get("bluePenaltyMax"));
|
||||
if (map.containsKey("redPenaltySpot")) a.redPenaltySpot = desLoc(map.get("redPenaltySpot"));
|
||||
if (map.containsKey("bluePenaltySpot")) a.bluePenaltySpot = desLoc(map.get("bluePenaltySpot"));
|
||||
if (map.containsKey("spectatorSpawn")) a.spectatorSpawn = desLoc(map.get("spectatorSpawn"));
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -364,6 +367,8 @@ public class Arena implements ConfigurationSerializable {
|
||||
}
|
||||
public boolean hasManualPenaltyAreas() { return redPenaltyMin != null && redPenaltyMax != null
|
||||
&& bluePenaltyMin != null && bluePenaltyMax != null; }
|
||||
public Location getSpectatorSpawn() { return spectatorSpawn; }
|
||||
public void setSpectatorSpawn(Location l) { this.spectatorSpawn = l; }
|
||||
public int getMinPlayers() { return minPlayers; }
|
||||
public void setMinPlayers(int n) { this.minPlayers = n; }
|
||||
public int getMaxPlayers() { return maxPlayers; }
|
||||
|
||||
@@ -5,6 +5,8 @@ import de.fussball.plugin.arena.Arena;
|
||||
import de.fussball.plugin.game.Ball;
|
||||
import de.fussball.plugin.game.Game;
|
||||
import de.fussball.plugin.game.GameState;
|
||||
import de.fussball.plugin.hologram.FussballHologram;
|
||||
import de.fussball.plugin.hologram.HologramManager;
|
||||
import de.fussball.plugin.stats.StatsManager;
|
||||
import de.fussball.plugin.utils.MessageUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -200,6 +202,76 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
handleDebug(player, arena);
|
||||
}
|
||||
|
||||
// ── Hologramm-Verwaltung ─────────────────────────────────────────
|
||||
// /fb hologram set <id> goals|wins – Hologramm erstellen
|
||||
// /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
||||
// /fb hologram delete <id> – Hologramm nach ID löschen
|
||||
// /fb hologram reload – Alle Hologramme neu spawnen
|
||||
// /fb hologram list – Alle Hologramme anzeigen
|
||||
case "hologram", "holo" -> {
|
||||
if (!sender.hasPermission("fussball.admin")) { sender.sendMessage(MessageUtil.error("Keine Berechtigung!")); return true; }
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||
if (args.length < 2) {
|
||||
player.sendMessage(MessageUtil.header("Hologramm-Befehle"));
|
||||
player.sendMessage("§e/fb hologram set <id> goals|wins §7– Hologramm setzen");
|
||||
player.sendMessage("§e/fb hologram remove §7– Nächstes entfernen (< 5 Blöcke)");
|
||||
player.sendMessage("§e/fb hologram delete <id> §7– Nach ID löschen");
|
||||
player.sendMessage("§e/fb hologram reload §7– Alle neu laden");
|
||||
player.sendMessage("§e/fb hologram list §7– Alle anzeigen");
|
||||
player.sendMessage("§7Gesamt: §e" + plugin.getHologramManager().getCount() + " §7Hologramme");
|
||||
player.sendMessage("§7§oRechtsklick auf Hologramm → Tore ↔ Siege wechseln");
|
||||
return true;
|
||||
}
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "set" -> {
|
||||
if (args.length < 4) {
|
||||
player.sendMessage(MessageUtil.error("Benutze: /fb hologram set <id> goals|wins"));
|
||||
return true;
|
||||
}
|
||||
String id = args[2];
|
||||
FussballHologram.HoloType type = switch (args[3].toLowerCase()) {
|
||||
case "wins", "siege" -> FussballHologram.HoloType.WINS;
|
||||
default -> FussballHologram.HoloType.GOALS;
|
||||
};
|
||||
plugin.getHologramManager().createHologram(id, player.getLocation(), type);
|
||||
String holoLabel = type == FussballHologram.HoloType.WINS ? "Top-10-Siege" : "Top-10-Tore";
|
||||
player.sendMessage(MessageUtil.success("§e" + id + " §a(" + holoLabel + ") Hologramm gesetzt!"));
|
||||
player.sendMessage("§7§oRechtsklick auf das Hologramm wechselt zwischen Tore und Siege.");
|
||||
}
|
||||
case "remove" -> {
|
||||
String removed = plugin.getHologramManager().removeNearest(player.getLocation());
|
||||
if (removed != null) {
|
||||
player.sendMessage(MessageUtil.success("Hologramm §e" + removed + " §aentfernt!"));
|
||||
} else {
|
||||
player.sendMessage(MessageUtil.error("Kein Hologramm innerhalb von 5 Blöcken gefunden!"));
|
||||
}
|
||||
}
|
||||
case "delete" -> {
|
||||
if (args.length < 3) { player.sendMessage(MessageUtil.error("Benutze: /fb hologram delete <id>")); return true; }
|
||||
if (plugin.getHologramManager().removeHologram(args[2])) {
|
||||
player.sendMessage(MessageUtil.success("Hologramm §e" + args[2] + " §agelöscht!"));
|
||||
} else {
|
||||
player.sendMessage(MessageUtil.error("Kein Hologramm mit ID §e" + args[2] + "§c gefunden!"));
|
||||
}
|
||||
}
|
||||
case "reload" -> {
|
||||
plugin.getHologramManager().reload();
|
||||
player.sendMessage(MessageUtil.success("Hologramme neu geladen! §7(" + plugin.getHologramManager().getCount() + " gesamt)"));
|
||||
}
|
||||
case "list" -> {
|
||||
player.sendMessage(MessageUtil.header("Hologramme (" + plugin.getHologramManager().getCount() + ")"));
|
||||
if (plugin.getHologramManager().getCount() == 0) {
|
||||
player.sendMessage(MessageUtil.warn("Keine Hologramme vorhanden."));
|
||||
} else {
|
||||
for (String id : plugin.getHologramManager().getHologramIds()) {
|
||||
player.sendMessage("§7 • §e" + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> player.sendMessage(MessageUtil.error("Gültig: set <id> goals|wins | remove | delete <id> | reload | list"));
|
||||
}
|
||||
}
|
||||
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
return true;
|
||||
@@ -229,6 +301,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
case "bluepenaltymax" -> { arena.setBluePenaltyMax(player.getLocation()); player.sendMessage(MessageUtil.success("Blauer Strafraum Max gesetzt: "+ locStr(player.getLocation()))); }
|
||||
case "redpenaltyspot" -> { arena.setRedPenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Roter Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "bluepenaltyspot" -> { arena.setBluePenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Blauer Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "spectatorspawn" -> { arena.setSpectatorSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Zuschauer-Spawn gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "minplayers" -> { if (args.length < 4) return; arena.setMinPlayers(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Min-Spieler: §e" + args[3])); }
|
||||
case "maxplayers" -> { if (args.length < 4) return; arena.setMaxPlayers(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Max-Spieler: §e" + args[3])); }
|
||||
case "duration" -> { if (args.length < 4) return; arena.setGameDuration(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Spieldauer: §e" + args[3] + "s")); }
|
||||
@@ -246,7 +319,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
+ " §8(optional – sonst auto-berechnet)");
|
||||
player.sendMessage("§7 Blauer Strafraum: " + check(arena.getBluePenaltyMin(), arena.getBluePenaltyMax())
|
||||
+ " §8(optional – sonst auto-berechnet)");
|
||||
player.sendMessage("§7 Roter Elfmeter-Punkt: " + check(arena.getRedPenaltySpot()) + " §8(optional – sonst ball-spawn)");
|
||||
player.sendMessage("§7 Zuschauer-Spawn: " + check(arena.getSpectatorSpawn()) + " §8(optional – sonst Spielfeldrand)");
|
||||
player.sendMessage("§7 Blauer Elfmeter-Punkt: " + check(arena.getBluePenaltySpot()) + " §8(optional – sonst ball-spawn)");
|
||||
player.sendMessage("§7 Min. Spieler: §e" + arena.getMinPlayers());
|
||||
player.sendMessage("§7 Max. Spieler: §e" + arena.getMaxPlayers());
|
||||
@@ -320,6 +393,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
s.sendMessage("§e/fb top [goals|wins] §7- Bestenliste");
|
||||
if (s.hasPermission("fussball.admin")) {
|
||||
s.sendMessage("§c§lAdmin: §ccreate / delete / setup / stop / debug");
|
||||
s.sendMessage("§c§lAdmin: §chologram set goals|wins / remove / reload");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,9 +419,17 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (args.length == 1) {
|
||||
list.addAll(List.of("join", "leave", "list", "stats", "top", "spectate"));
|
||||
if (sender.hasPermission("fussball.admin")) list.addAll(List.of("create", "delete", "setup", "stop", "setgk", "debug"));
|
||||
if (sender.hasPermission("fussball.admin")) list.addAll(List.of("create", "delete", "setup", "stop", "setgk", "debug", "hologram"));
|
||||
} else if (args.length == 2 && List.of("join","delete","setup","stop","setgk","debug","spectate").contains(args[0].toLowerCase())) {
|
||||
list.addAll(plugin.getArenaManager().getArenaNames());
|
||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("hologram")) {
|
||||
list.addAll(List.of("set", "remove", "delete", "reload", "list"));
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
||||
list.addAll(plugin.getArenaManager().getArenaNames()); // id-Vorschläge (frei wählbar, aber arena-namen passen)
|
||||
} else if (args.length == 4 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
||||
list.addAll(List.of("goals", "wins"));
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("delete")) {
|
||||
list.addAll(plugin.getHologramManager().getHologramIds());
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("setgk")) {
|
||||
// Spielernamen aus dem aktiven Spiel vorschlagen
|
||||
Game gkGame = plugin.getGameManager().getGame(args[1]);
|
||||
@@ -362,7 +444,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
"redgoalmin","redgoalmax","bluegoalmin","bluegoalmax",
|
||||
"fieldmin","fieldmax",
|
||||
"redpenaltymin","redpenaltymax","bluepenaltymin","bluepenaltymax",
|
||||
"redpenaltyspot","bluepenaltyspot",
|
||||
"redpenaltyspot","bluepenaltyspot","spectatorspawn",
|
||||
"minplayers","maxplayers","duration","info"));
|
||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("top")) {
|
||||
list.addAll(List.of("goals", "wins"));
|
||||
|
||||
@@ -52,6 +52,10 @@ public class Game {
|
||||
private final Map<UUID, Integer> outOfBoundsCountdown = new HashMap<>();
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── AFK-Erkennung ──────────────────────────────────────────────────────
|
||||
private final Map<UUID, Location> lastPosition = new HashMap<>(); // letzte gemessene Position
|
||||
private final Map<UUID, Integer> afkTicks = new HashMap<>(); // Ticks ohne Bewegung
|
||||
|
||||
private UUID lastKicker = null;
|
||||
private UUID secondLastKicker = null; // für Assist-Erkennung
|
||||
private boolean lastKickWasHeader = false; // für Rückpass-Regel (Header erlaubt)
|
||||
@@ -161,15 +165,29 @@ public class Game {
|
||||
public boolean addSpectator(Player player) {
|
||||
if (isInGame(player)) { player.sendMessage(MessageUtil.error("Du bist bereits Spieler!")); return false; }
|
||||
if (isSpectator(player)) { player.sendMessage(MessageUtil.error("Du schaust bereits zu!")); return false; }
|
||||
if (state == GameState.WAITING || state == GameState.STARTING || state == GameState.ENDING) {
|
||||
player.sendMessage(MessageUtil.error("Kein laufendes Spiel zum Zuschauen!")); return false;
|
||||
if (state == GameState.ENDING) {
|
||||
player.sendMessage(MessageUtil.error("Das Spiel ist gerade dabei zu enden!")); return false;
|
||||
}
|
||||
spectators.add(player.getUniqueId());
|
||||
player.setGameMode(GameMode.SPECTATOR);
|
||||
player.teleport(arena.getBallSpawn() != null ? arena.getBallSpawn() : arena.getLobby());
|
||||
player.setGameMode(GameMode.ADVENTURE);
|
||||
|
||||
// Gesetzter Zuschauer-Spawn hat Priorität, danach Fallback über getSpectatorSpawn()
|
||||
Location spectatorLoc = getSpectatorSpawn();
|
||||
if (spectatorLoc != null) player.teleport(spectatorLoc);
|
||||
|
||||
// Zuschauer bleiben sichtbar – die Arena-Grenzen verhindern das Betreten des Feldes.
|
||||
// Inventar leeren und Hunger/Schaden deaktivieren (bereits durch PlayerListener)
|
||||
player.getInventory().clear();
|
||||
player.setHealth(20.0);
|
||||
player.setFoodLevel(20);
|
||||
for (PotionEffect e : player.getActivePotionEffects()) player.removePotionEffect(e.getType());
|
||||
|
||||
scoreboard.give(player);
|
||||
if (bossBar != null) bossBar.addPlayer(player);
|
||||
player.sendMessage(MessageUtil.success("Du schaust jetzt §e" + arena.getName() + " §azu! §7(/fb leave zum Beenden)"));
|
||||
String stateHint = (state == GameState.WAITING || state == GameState.STARTING)
|
||||
? " §7(Spiel startet gleich)" : "";
|
||||
player.sendMessage(MessageUtil.success("Du schaust jetzt §e" + arena.getName() + " §azu!" + stateHint + " §7(/fb leave zum Beenden)"));
|
||||
player.sendMessage(MessageUtil.info("§7Du kannst die Arena nicht betreten. Viel Spaß beim Zuschauen!"));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -177,6 +195,7 @@ public class Game {
|
||||
spectators.remove(player.getUniqueId());
|
||||
if (bossBar != null) bossBar.removePlayer(player);
|
||||
scoreboard.remove(player);
|
||||
|
||||
resetPlayer(player);
|
||||
}
|
||||
|
||||
@@ -225,11 +244,13 @@ public class Game {
|
||||
player.getInventory().setLeggings(armor[2]); player.getInventory().setBoots(armor[3]);
|
||||
}
|
||||
|
||||
/** BUG FIX: Teleportiert zur Lobby statt Welt-Spawn */
|
||||
/** Setzt einen Spieler oder Zuschauer zurück (Lobby, ADVENTURE, Inventar leer) */
|
||||
private void resetPlayer(Player player) {
|
||||
player.getInventory().clear();
|
||||
for (PotionEffect e : player.getActivePotionEffects()) player.removePotionEffect(e.getType());
|
||||
player.setGameMode(GameMode.SURVIVAL);
|
||||
player.setGameMode(GameMode.ADVENTURE); // ADVENTURE – kein SURVIVAL (kein PvP / kein Hunger)
|
||||
player.setHealth(20.0);
|
||||
player.setFoodLevel(20);
|
||||
Location tp = arena.getLobby() != null ? arena.getLobby() : Bukkit.getWorlds().get(0).getSpawnLocation();
|
||||
player.teleport(tp);
|
||||
}
|
||||
@@ -578,6 +599,9 @@ public class Game {
|
||||
if (gameTask != null) { gameTask.cancel(); gameTask = null; }
|
||||
gameTask = new BukkitRunnable() {
|
||||
public void run() {
|
||||
// Zuschauer-Grenzen immer prüfen (unabhängig vom Spielzustand)
|
||||
checkSpectatorBoundaries();
|
||||
|
||||
if (state != GameState.RUNNING && state != GameState.OVERTIME) return;
|
||||
|
||||
// Kopfball-Abklingzeiten herunterzählen
|
||||
@@ -615,6 +639,7 @@ public class Game {
|
||||
checkPlayerBallInteraction();
|
||||
checkPlayerBoundaries();
|
||||
checkHeaderOpportunities();
|
||||
checkAfkPlayers();
|
||||
|
||||
// Freistoß-Abstandsdurchsetzung
|
||||
if (freekickLocation != null) {
|
||||
@@ -849,7 +874,7 @@ public class Game {
|
||||
for (UUID uuid : allPlayers) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p == null) continue;
|
||||
if (ball.getDistanceTo(p) >= 1.5) continue;
|
||||
if (ball.getDistanceTo(p) >= 2.2) continue;
|
||||
if (throwInTeam != null && getTeam(p) != throwInTeam) continue;
|
||||
|
||||
// ── Rückpass-Regel für Torwarte ──────────────────────────────────
|
||||
@@ -1336,9 +1361,199 @@ public class Game {
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// KOPFBALL
|
||||
// AFK-ERKENNUNG
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Erkennt Spieler die zu lange stillstehen und ermahnt / kickt sie.
|
||||
*
|
||||
* Konfiguration (config.yml):
|
||||
* gameplay.afk-warn-seconds: 20 → erste Warnung nach 20s Stillstand
|
||||
* gameplay.afk-kick-seconds: 40 → Disqualifikation nach 40s Stillstand
|
||||
* gameplay.afk-move-threshold: 0.5 → Mindestbewegung in Blöcken pro Sekunde
|
||||
*
|
||||
* Läuft sekündlich im Game-Loop (nur bei RUNNING / OVERTIME).
|
||||
*/
|
||||
private void checkAfkPlayers() {
|
||||
if (state != GameState.RUNNING && state != GameState.OVERTIME) return;
|
||||
int warnSecs = plugin.getConfig().getInt("gameplay.afk-warn-seconds", 20);
|
||||
int kickSecs = plugin.getConfig().getInt("gameplay.afk-kick-seconds", 40);
|
||||
double threshold = plugin.getConfig().getDouble("gameplay.afk-move-threshold", 0.5);
|
||||
|
||||
for (UUID uuid : new ArrayList<>(allPlayers)) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p == null) continue;
|
||||
|
||||
Location current = p.getLocation();
|
||||
Location last = lastPosition.get(uuid);
|
||||
|
||||
// Erste Messung → Position speichern, kein AFK-Zähler
|
||||
if (last == null) {
|
||||
lastPosition.put(uuid, current.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
double dist = current.distanceSquared(last); // Quadrat reicht zum Vergleich
|
||||
|
||||
if (dist >= threshold * threshold) {
|
||||
// Spieler hat sich bewegt → AFK-Counter zurücksetzen
|
||||
afkTicks.remove(uuid);
|
||||
lastPosition.put(uuid, current.clone());
|
||||
} else {
|
||||
// Spieler steht still → Counter erhöhen
|
||||
int ticks = afkTicks.merge(uuid, 1, Integer::sum);
|
||||
lastPosition.put(uuid, current.clone());
|
||||
|
||||
if (ticks == warnSecs) {
|
||||
// ── Erste Warnung ────────────────────────────────────────
|
||||
p.sendTitle("§e§l⚠ AFK?", "§7Bewege dich – du wirst sonst disqualifiziert!", 5, 50, 10);
|
||||
p.sendMessage(MessageUtil.warn("§7Du stehst seit §e" + warnSecs + "s §7still! Bewege dich oder du wirst rausgeworfen!"));
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.5f);
|
||||
broadcastAll(MessageUtil.warn("§e" + p.getName() + " §7scheint AFK zu sein!"));
|
||||
|
||||
} else if (ticks > warnSecs && ticks < kickSecs && (ticks - warnSecs) % 5 == 0) {
|
||||
// ── Erinnerungen alle 5s ─────────────────────────────────
|
||||
int remaining = kickSecs - ticks;
|
||||
p.sendTitle("§c§l⚠ NOCH " + remaining + "s!", "§7Bewege dich oder du wirst disqualifiziert!", 5, 25, 5);
|
||||
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.3f);
|
||||
|
||||
} else if (ticks >= kickSecs) {
|
||||
// ── Disqualifikation ─────────────────────────────────────
|
||||
afkTicks.remove(uuid);
|
||||
lastPosition.remove(uuid);
|
||||
broadcastAll(MessageUtil.warn("§e" + p.getName() + " §7wurde wegen AFK disqualifiziert!"));
|
||||
p.sendTitle("§c§lDISQUALIFIZIERT!", "§7Du warst zu lange AFK!", 10, 80, 20);
|
||||
disqualifyPlayer(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// ZUSCHAUER-GRENZEN
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Zuschauer müssen sich AUSSERHALB des Spielfeldes aufhalten (mind. 2 Blöcke Abstand),
|
||||
* dürfen aber einen Außenpuffer von 15 Blöcken um das Spielfeld nicht verlassen.
|
||||
*
|
||||
* Zu nah am Feld (< 2 Blöcke) → wird nach außen geschoben
|
||||
* Zu weit vom Feld (> 15 Blöcke) → wird zum Spielfeldrand zurückteleportiert
|
||||
* Falsche Welt → sofort zurück
|
||||
*/
|
||||
private void checkSpectatorBoundaries() {
|
||||
if (spectators.isEmpty()) return;
|
||||
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return;
|
||||
|
||||
final double INNER_BUFFER = 2.0; // Mindestabstand zum Spielfeld (außen)
|
||||
final double OUTER_BUFFER = 15.0; // Maximaler Abstand vom Spielfeld
|
||||
|
||||
// Spielfeld-Grenzen
|
||||
double fMinX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||
double fMaxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||
double fMinZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||
double fMaxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||
double fMinY = Math.min(arena.getFieldMin().getY(), arena.getFieldMax().getY());
|
||||
double fMaxY = Math.max(arena.getFieldMin().getY(), arena.getFieldMax().getY());
|
||||
|
||||
// Innere Grenze (Spielfeld + INNER_BUFFER) – Zuschauer NICHT erlaubt
|
||||
double innerMinX = fMinX - INNER_BUFFER; double innerMaxX = fMaxX + INNER_BUFFER;
|
||||
double innerMinZ = fMinZ - INNER_BUFFER; double innerMaxZ = fMaxZ + INNER_BUFFER;
|
||||
|
||||
// Äußere Grenze (Spielfeld + OUTER_BUFFER) – Zuschauer MÜSSEN innerhalb sein
|
||||
double outerMinX = fMinX - OUTER_BUFFER; double outerMaxX = fMaxX + OUTER_BUFFER;
|
||||
double outerMinZ = fMinZ - OUTER_BUFFER; double outerMaxZ = fMaxZ + OUTER_BUFFER;
|
||||
double outerMinY = fMinY - 5; double outerMaxY = fMaxY + 30;
|
||||
|
||||
// Rückteleport-Ziel: Spielfeldrand (+ INNER_BUFFER + 1, damit klar außerhalb)
|
||||
// Wir teleportieren zur Mitte einer Seitenlinie
|
||||
double centerX = (fMinX + fMaxX) / 2.0;
|
||||
double centerZ = (fMinZ + fMaxZ) / 2.0;
|
||||
double baseY = fMinY;
|
||||
// Rand-Spawn: an der langen Seite des Feldes, außen
|
||||
Location spectatorEdge = new Location(
|
||||
arena.getFieldMin().getWorld(),
|
||||
centerX,
|
||||
baseY,
|
||||
fMaxZ + INNER_BUFFER + 1,
|
||||
0f, 0f
|
||||
);
|
||||
// Fallback: Ball-Spawn / Lobby
|
||||
Location fallback = arena.getBallSpawn() != null ? arena.getBallSpawn() : arena.getLobby();
|
||||
|
||||
for (UUID uuid : new ArrayList<>(spectators)) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p == null) continue;
|
||||
|
||||
// ── Falsche Welt ──────────────────────────────────────────────────
|
||||
if (!p.getWorld().equals(arena.getFieldMin().getWorld())) {
|
||||
Location tp = fallback != null ? fallback : spectatorEdge;
|
||||
p.teleport(tp);
|
||||
p.sendTitle("§c⚠ FALSCHE WELT!", "§7Zurück zur Arena teleportiert!", 5, 40, 10);
|
||||
continue;
|
||||
}
|
||||
|
||||
Location loc = p.getLocation();
|
||||
double px = loc.getX(), pz = loc.getZ(), py = loc.getY();
|
||||
|
||||
boolean insideInner = px > innerMinX && px < innerMaxX
|
||||
&& pz > innerMinZ && pz < innerMaxZ;
|
||||
boolean outsideOuter = px < outerMinX || px > outerMaxX
|
||||
|| pz < outerMinZ || pz > outerMaxZ
|
||||
|| py < outerMinY || py > outerMaxY;
|
||||
|
||||
if (insideInner) {
|
||||
// ── Zu nah / auf dem Spielfeld → raus schieben ───────────────
|
||||
// Nächsten Punkt auf dem inneren Rand berechnen und leicht weiter raus
|
||||
double pushX = Math.max(innerMinX, Math.min(px, innerMaxX));
|
||||
double pushZ = Math.max(innerMinZ, Math.min(pz, innerMaxZ));
|
||||
// Richtung nach außen bestimmen
|
||||
double dxMin = Math.abs(px - innerMinX), dxMax = Math.abs(px - innerMaxX);
|
||||
double dzMin = Math.abs(pz - innerMinZ), dzMax = Math.abs(pz - innerMaxZ);
|
||||
double minDist = Math.min(Math.min(dxMin, dxMax), Math.min(dzMin, dzMax));
|
||||
Location push;
|
||||
if (minDist == dxMin) push = new Location(loc.getWorld(), innerMinX - 1, loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
||||
else if (minDist == dxMax) push = new Location(loc.getWorld(), innerMaxX + 1, loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
|
||||
else if (minDist == dzMin) push = new Location(loc.getWorld(), loc.getX(), loc.getY(), innerMinZ - 1, loc.getYaw(), loc.getPitch());
|
||||
else push = new Location(loc.getWorld(), loc.getX(), loc.getY(), innerMaxZ + 1, loc.getYaw(), loc.getPitch());
|
||||
p.teleport(push);
|
||||
p.sendTitle("§c⚠ SPIELFELD!", "§7Zuschauer müssen außerhalb des Feldes bleiben!", 5, 30, 5);
|
||||
|
||||
} else if (outsideOuter) {
|
||||
// ── Zu weit draußen → zum Spielfeldrand zurück ───────────────
|
||||
p.teleport(spectatorEdge);
|
||||
p.sendTitle("§c⚠ ARENAGRENZE!", "§7Zuschauer dürfen die Arena nicht verlassen!", 5, 40, 10);
|
||||
p.sendMessage(MessageUtil.warn("§7Als Zuschauer darfst du die Arena nicht verlassen!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Für PlayerListener: prüft ob Zuschauer außerhalb des erlaubten Bereichs ist */
|
||||
public boolean isSpectatorOutOfBounds(Player player) {
|
||||
if (!isSpectator(player)) return false;
|
||||
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return false;
|
||||
final double OUTER_BUFFER = 15.0;
|
||||
Location loc = player.getLocation();
|
||||
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX()) - OUTER_BUFFER;
|
||||
double maxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX()) + OUTER_BUFFER;
|
||||
double minZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()) - OUTER_BUFFER;
|
||||
double maxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()) + OUTER_BUFFER;
|
||||
return loc.getX() < minX || loc.getX() > maxX || loc.getZ() < minZ || loc.getZ() > maxZ;
|
||||
}
|
||||
|
||||
public Location getSpectatorSpawn() {
|
||||
// Manuell gesetzter Zuschauer-Spawn hat Priorität
|
||||
if (arena.getSpectatorSpawn() != null) return arena.getSpectatorSpawn();
|
||||
// Fallback: Spielfeldrand (Mitte der langen Seite, 3 Blöcke außen)
|
||||
if (arena.getFieldMin() != null && arena.getFieldMax() != null) {
|
||||
double fMaxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||
double centerX = (arena.getFieldMin().getX() + arena.getFieldMax().getX()) / 2.0;
|
||||
double baseY = Math.min(arena.getFieldMin().getY(), arena.getFieldMax().getY());
|
||||
return new Location(arena.getFieldMin().getWorld(), centerX, baseY, fMaxZ + 3, 0f, 0f);
|
||||
}
|
||||
return arena.getBallSpawn() != null ? arena.getBallSpawn() : arena.getLobby();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft jeden Sekunden-Tick ob ein Spieler den Ball köpfen kann.
|
||||
* Bedingungen: Spieler ist in der Luft, Ball befindet sich auf Kopfhöhe,
|
||||
|
||||
220
src/main/java/de/fussball/plugin/hologram/FussballHologram.java
Normal file
220
src/main/java/de/fussball/plugin/hologram/FussballHologram.java
Normal file
@@ -0,0 +1,220 @@
|
||||
package de.fussball.plugin.hologram;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.stats.StatsManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Display;
|
||||
import org.bukkit.entity.Interaction;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.TextDisplay;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Ein einzelnes Fußball-Statistik-Hologramm.
|
||||
*
|
||||
* Basiert auf NexusHologram (NexusLobby):
|
||||
* • Pro Spieler eine eigene TextDisplay-Entity → nur er sieht sie
|
||||
* • Interaction-Entity als Hitbox → Rechtsklick wechselt zwischen Seiten
|
||||
* • Seiten: GOALS (Top-10 Torschützen) und WINS (Top-10 Gewinner)
|
||||
* • Distanz-Check: > 48 Blöcke → Entity entfernen (Bandbreite sparen)
|
||||
*/
|
||||
public class FussballHologram {
|
||||
|
||||
/** Render-Radius: 48 Blöcke (2304 = 48²) */
|
||||
private static final double RENDER_RADIUS_SQ = 2304.0;
|
||||
|
||||
public enum HoloType { GOALS, WINS }
|
||||
|
||||
private final String id;
|
||||
private final Location location;
|
||||
private HoloType type; // mutable – nicht final, damit Admin den Typ ändern kann
|
||||
|
||||
// UUID des Spielers → seine persönliche Entity
|
||||
private final Map<UUID, TextDisplay> playerEntities = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Interaction> playerInteractions = new ConcurrentHashMap<>();
|
||||
/** Aktuell angezeigte Seite (0 = GOALS, 1 = WINS) pro Spieler */
|
||||
private final Map<UUID, Integer> currentPage = new ConcurrentHashMap<>();
|
||||
|
||||
private final Fussball plugin;
|
||||
|
||||
public FussballHologram(String id, Location location, HoloType type, Fussball plugin) {
|
||||
this.id = id;
|
||||
this.location = location.clone();
|
||||
this.type = type;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
// ── Seiten-Wechsel ───────────────────────────────────────────────────────
|
||||
|
||||
/** Rechtsklick → zur nächsten Seite (GOALS ↔ WINS) */
|
||||
public void nextPage(Player player) {
|
||||
// Wir haben 2 Seiten: 0 = GOALS, 1 = WINS
|
||||
int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % 2;
|
||||
currentPage.put(player.getUniqueId(), next);
|
||||
renderForPlayer(player);
|
||||
}
|
||||
|
||||
// ── Render ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Rendert das Hologramm für einen Spieler – oder entfernt es wenn er zu weit weg ist.
|
||||
* Wird alle 5 Ticks vom HologramManager aufgerufen.
|
||||
*/
|
||||
public void renderForPlayer(Player player) {
|
||||
// Falsche Welt oder zu weit → entfernen
|
||||
if (!player.getWorld().equals(location.getWorld())
|
||||
|| player.getLocation().distanceSquared(location) > RENDER_RADIUS_SQ) {
|
||||
removeForPlayer(player);
|
||||
return;
|
||||
}
|
||||
|
||||
// Seite bestimmen
|
||||
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), type == HoloType.WINS ? 1 : 0);
|
||||
HoloType displayType = pageIdx == 1 ? HoloType.WINS : HoloType.GOALS;
|
||||
|
||||
String text = buildText(displayType);
|
||||
|
||||
TextDisplay display = playerEntities.get(player.getUniqueId());
|
||||
|
||||
if (display == null || !display.isValid()) {
|
||||
// ── Neue TextDisplay-Entity spawnen ─────────────────────────────
|
||||
display = location.getWorld().spawn(location, TextDisplay.class, entity -> {
|
||||
entity.setCustomName("fb_holo_" + id + "_" + player.getName());
|
||||
entity.setCustomNameVisible(false);
|
||||
entity.setPersistent(false);
|
||||
entity.setBillboard(Display.Billboard.CENTER);
|
||||
entity.setBackgroundColor(Color.fromARGB(0, 0, 0, 0)); // komplett transparent
|
||||
entity.setDefaultBackground(false); // Standard-Grau-Panel entfernen
|
||||
entity.setText(text);
|
||||
entity.setInvulnerable(true);
|
||||
entity.setSeeThrough(false);
|
||||
});
|
||||
|
||||
// ── Interaction-Entity spawnen (Hitbox für Rechtsklick) ──────────
|
||||
Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> {
|
||||
entity.setInteractionWidth(2.5f);
|
||||
entity.setInteractionHeight(2.5f);
|
||||
entity.setCustomNameVisible(false);
|
||||
entity.setPersistent(false);
|
||||
});
|
||||
|
||||
// ── Nur für diesen Spieler sichtbar machen ───────────────────────
|
||||
TextDisplay finalDisplay = display;
|
||||
Interaction finalInteract = interact;
|
||||
for (Player other : Bukkit.getOnlinePlayers()) {
|
||||
if (!other.getUniqueId().equals(player.getUniqueId())) {
|
||||
other.hideEntity(plugin, finalDisplay);
|
||||
other.hideEntity(plugin, finalInteract);
|
||||
}
|
||||
}
|
||||
|
||||
playerEntities.put(player.getUniqueId(), display);
|
||||
playerInteractions.put(player.getUniqueId(), interact);
|
||||
|
||||
} else {
|
||||
// ── Bestehende Entity nur aktualisieren (kein Re-Spawn) ──────────
|
||||
if (!display.getText().equals(text)) {
|
||||
display.setText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Entfernen ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Entfernt die Entities eines einzelnen Spielers (Disconnect / Worldwechsel / zu weit) */
|
||||
public void removeForPlayer(Player player) {
|
||||
TextDisplay display = playerEntities.remove(player.getUniqueId());
|
||||
if (display != null) display.remove();
|
||||
|
||||
Interaction interact = playerInteractions.remove(player.getUniqueId());
|
||||
if (interact != null) interact.remove();
|
||||
|
||||
currentPage.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/** Entfernt ALLE Entities (Plugin-Disable / Hologramm gelöscht) */
|
||||
public void removeAll() {
|
||||
playerEntities.values().forEach(TextDisplay::remove);
|
||||
playerInteractions.values().forEach(Interaction::remove);
|
||||
playerEntities.clear();
|
||||
playerInteractions.clear();
|
||||
currentPage.clear();
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Prüft, ob die gegebene Entity-UUID eine Interaction-Entity dieses Hologramms ist.
|
||||
* (Wird in HologramManager genutzt um Klick-Events zuzuordnen)
|
||||
*/
|
||||
public boolean isInteractionEntity(UUID entityId) {
|
||||
return playerInteractions.values().stream()
|
||||
.anyMatch(i -> i.getUniqueId().equals(entityId));
|
||||
}
|
||||
|
||||
/** Baut den anzuzeigenden Text aus den aktuellen Top-10-Statistiken */
|
||||
private String buildText(HoloType showType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (showType == HoloType.GOALS) {
|
||||
sb.append("§6§l⚽ TOP 10 TORSCHÜTZEN ⚽\n");
|
||||
sb.append("§8§m══════════════════════§r\n");
|
||||
var list = plugin.getStatsManager().getTopScorers(10);
|
||||
if (list.isEmpty()) {
|
||||
sb.append("§8Noch keine Statistiken vorhanden.");
|
||||
} else {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
StatsManager.PlayerStats s = list.get(i).getValue();
|
||||
sb.append(medal(i + 1))
|
||||
.append(" §0").append(s.name)
|
||||
.append(" §4").append(s.goals).append(" §8Tore");
|
||||
if (i < list.size() - 1) sb.append("\n");
|
||||
}
|
||||
}
|
||||
sb.append("\n§8§m══════════════════════§r");
|
||||
sb.append("\n§8§o[Rechtsklick → Siege anzeigen]");
|
||||
|
||||
} else {
|
||||
sb.append("§2§l🏆 TOP 10 GEWINNER 🏆\n");
|
||||
sb.append("§8§m══════════════════════§r\n");
|
||||
var list = plugin.getStatsManager().getTopWins(10);
|
||||
if (list.isEmpty()) {
|
||||
sb.append("§8Noch keine Statistiken vorhanden.");
|
||||
} else {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
StatsManager.PlayerStats s = list.get(i).getValue();
|
||||
sb.append(medal(i + 1))
|
||||
.append(" §0").append(s.name)
|
||||
.append(" §2").append(s.wins).append(" §8Siege")
|
||||
.append(" §8(").append(String.format("%.0f", s.getWinRate())).append("%)");
|
||||
if (i < list.size() - 1) sb.append("\n");
|
||||
}
|
||||
}
|
||||
sb.append("\n§8§m══════════════════════§r");
|
||||
sb.append("\n§8§o[Rechtsklick → Tore anzeigen]");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String medal(int rank) {
|
||||
return switch (rank) {
|
||||
case 1 -> "§6§l#1"; // Gold bleibt – hebt sich gut ab
|
||||
case 2 -> "§8§l#2"; // Dunkelgrau
|
||||
case 3 -> "§4§l#3"; // Dunkelrot
|
||||
default -> "§8#" + rank;
|
||||
};
|
||||
}
|
||||
|
||||
// ── Getter ───────────────────────────────────────────────────────────────
|
||||
|
||||
public String getId() { return id; }
|
||||
public Location getLocation() { return location.clone(); }
|
||||
public HoloType getType() { return type; }
|
||||
public void setType(HoloType t) { this.type = t; }
|
||||
}
|
||||
293
src/main/java/de/fussball/plugin/hologram/HologramManager.java
Normal file
293
src/main/java/de/fussball/plugin/hologram/HologramManager.java
Normal file
@@ -0,0 +1,293 @@
|
||||
package de.fussball.plugin.hologram;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Interaction;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.TextDisplay;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Verwaltet alle Fußball-Statistik-Hologramme.
|
||||
*
|
||||
* Adaption von HologramModule (NexusLobby):
|
||||
* • Pro Spieler eine eigene TextDisplay-Entity (nur er sieht sie)
|
||||
* • Interaction-Entity als Klick-Hitbox → Seiten wechseln (Tore ↔ Siege)
|
||||
* • Render-Task alle 5 Ticks → Distanzprüfung + Text-Update
|
||||
* • Cleanup bei Join / Quit / Weltenwechsel
|
||||
* • Persistierung in holograms.yml
|
||||
*
|
||||
* Befehle (FussballCommand):
|
||||
* /fb hologram set goals|wins – Hologramm an Spielerposition erstellen
|
||||
* /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
||||
* /fb hologram reload – Alle Hologramme neu laden (nach Restart)
|
||||
* /fb hologram list – Alle Hologramme auflisten
|
||||
*/
|
||||
public class HologramManager implements Listener {
|
||||
|
||||
private final Fussball plugin;
|
||||
private final Map<String, FussballHologram> holograms = new ConcurrentHashMap<>();
|
||||
|
||||
private File holoFile;
|
||||
private FileConfiguration holoConfig;
|
||||
private BukkitTask renderTask;
|
||||
|
||||
public HologramManager(Fussball plugin) {
|
||||
this.plugin = plugin;
|
||||
loadConfig();
|
||||
loadHolograms();
|
||||
startRenderTask();
|
||||
cleanupStrayEntities();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
// ── Konfiguration ────────────────────────────────────────────────────────
|
||||
|
||||
private void loadConfig() {
|
||||
holoFile = new File(plugin.getDataFolder(), "holograms.yml");
|
||||
if (!holoFile.exists()) {
|
||||
try { holoFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
holoConfig = YamlConfiguration.loadConfiguration(holoFile);
|
||||
}
|
||||
|
||||
private void saveConfig() {
|
||||
try {
|
||||
holoConfig.save(holoFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("[Hologram] Konnte holograms.yml nicht speichern: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Laden / Erstellen / Entfernen ────────────────────────────────────────
|
||||
|
||||
private void loadHolograms() {
|
||||
// Vorherige Instanzen sauber entfernen
|
||||
holograms.values().forEach(FussballHologram::removeAll);
|
||||
holograms.clear();
|
||||
|
||||
if (!holoConfig.contains("holograms")) return;
|
||||
|
||||
for (String id : holoConfig.getConfigurationSection("holograms").getKeys(false)) {
|
||||
String path = "holograms." + id;
|
||||
String worldName = holoConfig.getString(path + ".world");
|
||||
if (worldName == null) continue;
|
||||
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world == null) {
|
||||
plugin.getLogger().warning("[Hologram] Welt '" + worldName
|
||||
+ "' nicht gefunden – Hologramm '" + id + "' übersprungen.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Location loc = new Location(
|
||||
world,
|
||||
holoConfig.getDouble(path + ".x"),
|
||||
holoConfig.getDouble(path + ".y"),
|
||||
holoConfig.getDouble(path + ".z")
|
||||
);
|
||||
|
||||
String typeStr = holoConfig.getString(path + ".type", "GOALS");
|
||||
FussballHologram.HoloType type =
|
||||
"WINS".equalsIgnoreCase(typeStr)
|
||||
? FussballHologram.HoloType.WINS
|
||||
: FussballHologram.HoloType.GOALS;
|
||||
|
||||
holograms.put(id, new FussballHologram(id, loc, type, plugin));
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[Hologram] " + holograms.size() + " Hologramme geladen.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues Hologramm und speichert es in holograms.yml.
|
||||
* Falls die ID bereits existiert, wird das alte sauber entfernt.
|
||||
*/
|
||||
public boolean createHologram(String id, Location loc, FussballHologram.HoloType type) {
|
||||
if (holograms.containsKey(id)) removeHologram(id);
|
||||
|
||||
String path = "holograms." + id;
|
||||
holoConfig.set(path + ".world", loc.getWorld().getName());
|
||||
holoConfig.set(path + ".x", loc.getX());
|
||||
holoConfig.set(path + ".y", loc.getY());
|
||||
holoConfig.set(path + ".z", loc.getZ());
|
||||
holoConfig.set(path + ".type", type.name());
|
||||
saveConfig();
|
||||
|
||||
FussballHologram holo = new FussballHologram(id, loc, type, plugin);
|
||||
holograms.put(id, holo);
|
||||
|
||||
// Sofort für alle Online-Spieler rendern
|
||||
for (Player player : Bukkit.getOnlinePlayers()) holo.renderForPlayer(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt ein Hologramm anhand seiner ID.
|
||||
* @return true wenn gefunden und entfernt
|
||||
*/
|
||||
public boolean removeHologram(String id) {
|
||||
FussballHologram holo = holograms.remove(id);
|
||||
if (holo == null) return false;
|
||||
|
||||
// Erst für alle Online-Spieler visuell entfernen
|
||||
for (Player player : Bukkit.getOnlinePlayers()) holo.removeForPlayer(player);
|
||||
// Dann alle Entities serverseitig löschen
|
||||
holo.removeAll();
|
||||
|
||||
holoConfig.set("holograms." + id, null);
|
||||
saveConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt das nächste Hologramm innerhalb von 5 Blöcken zur gegebenen Location.
|
||||
* @return ID des entfernten Hologramms oder null wenn keines gefunden
|
||||
*/
|
||||
public String removeNearest(Location loc) {
|
||||
String nearest = null;
|
||||
double nearestDist = 5.0;
|
||||
|
||||
for (Map.Entry<String, FussballHologram> entry : holograms.entrySet()) {
|
||||
Location holoLoc = entry.getValue().getLocation();
|
||||
if (!holoLoc.getWorld().equals(loc.getWorld())) continue;
|
||||
double dist = holoLoc.distance(loc);
|
||||
if (dist < nearestDist) {
|
||||
nearestDist = dist;
|
||||
nearest = entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
if (nearest == null) return null;
|
||||
removeHologram(nearest);
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/** Alle Hologramme neu laden (z.B. nach /fb hologram reload) */
|
||||
public void reload() {
|
||||
loadConfig();
|
||||
loadHolograms();
|
||||
// Für alle Online-Spieler sofort rendern
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
holograms.values().forEach(h -> h.renderForPlayer(player));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Render-Task ──────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Alle 5 Ticks: Distanzprüfung + Text-Update.
|
||||
* Text wird in FussballHologram nur neu gesetzt wenn er sich geändert hat (==Check).
|
||||
*/
|
||||
private void startRenderTask() {
|
||||
if (renderTask != null) renderTask.cancel();
|
||||
renderTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
holograms.values().forEach(h -> h.renderForPlayer(player));
|
||||
}
|
||||
}, 20L, 5L);
|
||||
}
|
||||
|
||||
// ── Events ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Rechtsklick auf Interaction-Entity → Seite wechseln (Tore ↔ Siege) */
|
||||
@EventHandler
|
||||
public void onInteract(PlayerInteractEntityEvent event) {
|
||||
if (!(event.getRightClicked() instanceof Interaction)) return;
|
||||
|
||||
for (FussballHologram holo : holograms.values()) {
|
||||
if (holo.isInteractionEntity(event.getRightClicked().getUniqueId())) {
|
||||
holo.nextPage(event.getPlayer());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Spieler verlässt den Server → Entities entfernen */
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
|
||||
}
|
||||
|
||||
/** Weltenwechsel → Entities aus der alten Welt entfernen */
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent event) {
|
||||
holograms.values().forEach(h -> h.removeForPlayer(event.getPlayer()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Spieler joint → verbleibende verwaiste fb_holo_*-Entities verstecken,
|
||||
* dann Hologramme für ihn rendern.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
Player p = event.getPlayer();
|
||||
// Crash-Überreste aus der Welt verstecken
|
||||
for (Entity entity : p.getWorld().getEntities()) {
|
||||
String name = entity.getCustomName();
|
||||
if (name == null || !name.startsWith("fb_holo_")) continue;
|
||||
if (!name.endsWith("_" + p.getName())) {
|
||||
p.hideEntity(plugin, entity);
|
||||
}
|
||||
}
|
||||
// Sofort für ihn rendern
|
||||
holograms.values().forEach(h -> h.renderForPlayer(p));
|
||||
}, 10L);
|
||||
}
|
||||
|
||||
// ── Shutdown ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** Sauber herunterfahren (Plugin-Disable) */
|
||||
public void removeAll() {
|
||||
if (renderTask != null) { renderTask.cancel(); renderTask = null; }
|
||||
HandlerList.unregisterAll(this);
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
holograms.values().forEach(h -> h.removeForPlayer(player));
|
||||
}
|
||||
holograms.values().forEach(FussballHologram::removeAll);
|
||||
holograms.clear();
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Entfernt beim Serverstart verbleibende fb_holo_*-Entities aus allen Welten.
|
||||
* (Überreste nach einem Crash – non-persistent Entities sollten eigentlich weg sein,
|
||||
* aber besser einmal zu viel prüfen)
|
||||
*/
|
||||
private void cleanupStrayEntities() {
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
for (Entity entity : world.getEntities()) {
|
||||
String name = entity.getCustomName();
|
||||
if (name != null && name.startsWith("fb_holo_")) {
|
||||
entity.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return Anzahl der registrierten Hologramme */
|
||||
public int getCount() { return holograms.size(); }
|
||||
|
||||
/** @return Set aller Hologramm-IDs (für Tab-Completion) */
|
||||
public Set<String> getHologramIds() { return holograms.keySet(); }
|
||||
}
|
||||
@@ -23,10 +23,8 @@ public class PlayerListener implements Listener {
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game != null) game.removePlayer(player);
|
||||
// Auch als Zuschauer entfernen
|
||||
Game spectatorGame = plugin.getGameManager().getSpectatorGame(player);
|
||||
if (spectatorGame != null) spectatorGame.removeSpectator(player);
|
||||
// Aus Warteschlangen entfernen
|
||||
plugin.getGameManager().removeFromAllQueues(player);
|
||||
}
|
||||
|
||||
@@ -66,24 +64,81 @@ public class PlayerListener implements Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Team-Chat: Nachrichten von Spielern im Spiel werden NUR ans eigene Team gesendet.
|
||||
* Mit "!" am Anfang können Admins global ins Spiel broadcasten.
|
||||
* Zuschauer sehen alle Team-Chats (mit Label).
|
||||
* Zuschauer dürfen die Arena nicht verlassen (Weltenwechsel blockieren).
|
||||
* GM3 erlaubt Portale – das verhindert, dass ein Zuschauer durch ein Portal
|
||||
* aus der Welt heraus teleportiert wird.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGH)
|
||||
public void onPortal(PlayerPortalEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
if (plugin.getGameManager().getSpectatorGame(player) != null) {
|
||||
event.setCancelled(true);
|
||||
player.sendMessage("\u00a7cZuschauer d\u00fcrfen keine Portale benutzen!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zuschauer-Grenzen bei Bewegung prüfen.
|
||||
* Nur bei echtem Block-Wechsel (reduziert Event-Last drastisch).
|
||||
* Der Game-Loop prüft ebenfalls jede Sekunde – dieser Handler greift
|
||||
* als sofortiger Schutz bei schnellem Fliegen.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onMove(PlayerMoveEvent event) {
|
||||
// Nur bei echtem Block-Wechsel auswerten (performance)
|
||||
if (event.getFrom().getBlockX() == event.getTo().getBlockX()
|
||||
&& event.getFrom().getBlockY() == event.getTo().getBlockY()
|
||||
&& event.getFrom().getBlockZ() == event.getTo().getBlockZ()) return;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getSpectatorGame(player);
|
||||
if (game == null) return;
|
||||
|
||||
// Weltenwechsel durch Bewegung abfangen
|
||||
if (!event.getFrom().getWorld().equals(event.getTo().getWorld())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grenzen prüfen – Game.java berechnet die erlaubte Zone
|
||||
if (game.isSpectatorOutOfBounds(player)) {
|
||||
event.setCancelled(true);
|
||||
// Zum Zuschauer-Spawn zurückteleportieren
|
||||
player.teleport(game.getSpectatorSpawn());
|
||||
player.sendTitle("§c\u26a0 ARENAGRENZE!", "§7Zuschauer d\u00fcrfen die Arena nicht verlassen!", 5, 40, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Team-Chat: Nachrichten von Spielern im Spiel werden NUR ans eigene Team gesendet.
|
||||
*
|
||||
* LOWEST-Priorität: Event wird sofort gecancelt, BEVOR Chat-Formatter-Plugins
|
||||
* (EssentialsChat, CMI, ...) die Nachricht mit ihrem Prefix versenden können.
|
||||
* Das verhindert Doppelnachrichten wie "[Rot] msg" + "[Owner] msg".
|
||||
*
|
||||
* Da AsyncPlayerChatEvent asynchron läuft, wird die eigentliche Team-Nachricht
|
||||
* per runTask auf den Haupt-Thread verlagert.
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
|
||||
// Sofort canceln – noch bevor andere Plugins das Event verarbeiten
|
||||
event.setCancelled(true);
|
||||
event.getRecipients().clear();
|
||||
|
||||
String message = event.getMessage();
|
||||
|
||||
// Nachricht auf dem Haupt-Thread versenden (Bukkit-API ist nicht thread-safe)
|
||||
org.bukkit.Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
// Admin-Global-Broadcast
|
||||
if (message.startsWith("!") && player.hasPermission("fussball.admin")) {
|
||||
game.broadcastAll("§6[Global] §f" + player.getName() + "§7: " + message.substring(1).trim());
|
||||
return;
|
||||
}
|
||||
game.sendTeamMessage(player, message);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,24 +23,33 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Fußball-Join-Schilder
|
||||
* Fußball-Schilder (Join + Zuschauer)
|
||||
*
|
||||
* Format beim Beschriften (braucht fussball.admin):
|
||||
* JOIN-Schild (braucht fussball.admin):
|
||||
* Zeile 1: [Fussball]
|
||||
* Zeile 2: <ArenaName>
|
||||
*
|
||||
* Schilder werden in signs.yml gespeichert und überleben Server-Neustarts.
|
||||
* Aktualisierung erfolgt automatisch bei Spieler-Join/Leave und Spielstart/-ende.
|
||||
* ZUSCHAUER-Schild (braucht fussball.admin):
|
||||
* Zeile 1: [FussballSpec]
|
||||
* Zeile 2: <ArenaName>
|
||||
*
|
||||
* Beide Schild-Typen werden in signs.yml gespeichert.
|
||||
* Der Typ wird als Prefix im Key gespeichert: "join:" oder "spec:".
|
||||
*/
|
||||
public class SignListener implements Listener {
|
||||
|
||||
private static final String TAG = "[Fussball]";
|
||||
private static final String TAG_FORMATTED = "§8[§e⚽§8]";
|
||||
// Join-Schild Tags
|
||||
private static final String TAG_JOIN = "[Fussball]";
|
||||
private static final String TAG_JOIN_FMT = "§8[§e⚽§8]";
|
||||
|
||||
// Zuschauer-Schild Tags
|
||||
private static final String TAG_SPEC = "[FussballSpec]";
|
||||
private static final String TAG_SPEC_FMT = "§8[§b👁§8]";
|
||||
|
||||
private final Fussball plugin;
|
||||
|
||||
// Location → ArenaName
|
||||
private final Map<String, String> signs = new HashMap<>(); // key = "world;x;y;z"
|
||||
// locKey → "join:<ArenaName>" oder "spec:<ArenaName>"
|
||||
private final Map<String, String> signs = new HashMap<>();
|
||||
|
||||
private final File signFile;
|
||||
private FileConfiguration signConfig;
|
||||
@@ -61,8 +70,12 @@ public class SignListener implements Listener {
|
||||
signs.clear();
|
||||
if (signConfig.contains("signs")) {
|
||||
for (String key : signConfig.getConfigurationSection("signs").getKeys(false)) {
|
||||
String arenaName = signConfig.getString("signs." + key);
|
||||
signs.put(key, arenaName);
|
||||
String value = signConfig.getString("signs." + key);
|
||||
// Legacy-Migration: alte Einträge ohne Prefix → "join:"
|
||||
if (value != null && !value.startsWith("join:") && !value.startsWith("spec:")) {
|
||||
value = "join:" + value;
|
||||
}
|
||||
signs.put(key, value);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("[Fussball] " + signs.size() + " Schilder geladen.");
|
||||
@@ -82,13 +95,16 @@ public class SignListener implements Listener {
|
||||
|
||||
// ── Events ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Schild beschriften → Fußball-Schild erstellen */
|
||||
@EventHandler
|
||||
public void onSignChange(SignChangeEvent event) {
|
||||
String line0 = event.getLine(0);
|
||||
if (line0 == null || !line0.equalsIgnoreCase(TAG)) return;
|
||||
|
||||
if (line0 == null) return;
|
||||
Player player = event.getPlayer();
|
||||
|
||||
boolean isJoin = line0.equalsIgnoreCase(TAG_JOIN);
|
||||
boolean isSpec = line0.equalsIgnoreCase(TAG_SPEC);
|
||||
if (!isJoin && !isSpec) return;
|
||||
|
||||
if (!player.hasPermission("fussball.admin")) {
|
||||
player.sendMessage(MessageUtil.error("Keine Berechtigung für Fußball-Schilder!"));
|
||||
event.setCancelled(true);
|
||||
@@ -109,31 +125,40 @@ public class SignListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
event.setLine(0, TAG_FORMATTED);
|
||||
String key = locKey(event.getBlock().getLocation());
|
||||
|
||||
if (isJoin) {
|
||||
event.setLine(0, TAG_JOIN_FMT);
|
||||
event.setLine(1, "§e" + arena.getName());
|
||||
event.setLine(2, buildStatusLine(arena));
|
||||
event.setLine(3, "§7Klick zum Joinen");
|
||||
|
||||
String key = locKey(event.getBlock().getLocation());
|
||||
signs.put(key, arena.getName());
|
||||
event.setLine(3, "§7Joinen");
|
||||
signs.put(key, "join:" + arena.getName());
|
||||
player.sendMessage(MessageUtil.success("Join-Schild für §e" + arena.getName() + " §aerstellt!"));
|
||||
} else {
|
||||
event.setLine(0, TAG_SPEC_FMT);
|
||||
event.setLine(1, "§b" + arena.getName());
|
||||
event.setLine(2, buildStatusLine(arena));
|
||||
event.setLine(3, "§7Zuschauen");
|
||||
signs.put(key, "spec:" + arena.getName());
|
||||
player.sendMessage(MessageUtil.success("Zuschauer-Schild für §e" + arena.getName() + " §aerstellt!"));
|
||||
}
|
||||
saveSigns();
|
||||
|
||||
player.sendMessage(MessageUtil.success("Fußball-Schild für §e" + arena.getName() + " §aerstellt!"));
|
||||
}
|
||||
|
||||
/** Rechtsklick → Spieler joinen */
|
||||
@EventHandler
|
||||
public void onInteract(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null || !(block.getState() instanceof Sign sign)) return;
|
||||
if (block == null || !(block.getState() instanceof Sign)) return;
|
||||
|
||||
String key = locKey(block.getLocation());
|
||||
if (!signs.containsKey(key)) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
Player player = event.getPlayer();
|
||||
String arenaName = signs.get(key);
|
||||
String value = signs.get(key);
|
||||
boolean isSpec = value.startsWith("spec:");
|
||||
String arenaName = value.substring(value.indexOf(':') + 1);
|
||||
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null) {
|
||||
@@ -142,6 +167,21 @@ public class SignListener implements Listener {
|
||||
saveSigns();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSpec) {
|
||||
// ── ZUSCHAUER-SCHILD ────────────────────────────────────────────
|
||||
if (plugin.getGameManager().isInAnyGame(player)) {
|
||||
player.sendMessage(MessageUtil.error("Verlasse zuerst dein aktuelles Spiel!"));
|
||||
return;
|
||||
}
|
||||
Game game = plugin.getGameManager().getGame(arenaName);
|
||||
if (game == null) {
|
||||
player.sendMessage(MessageUtil.error("Kein laufendes Spiel in §e" + arenaName + "§c!"));
|
||||
return;
|
||||
}
|
||||
game.addSpectator(player);
|
||||
} else {
|
||||
// ── JOIN-SCHILD ─────────────────────────────────────────────────
|
||||
if (!arena.isSetupComplete()) {
|
||||
player.sendMessage(MessageUtil.error("Diese Arena ist noch nicht fertig eingerichtet!"));
|
||||
return;
|
||||
@@ -150,12 +190,11 @@ public class SignListener implements Listener {
|
||||
player.sendMessage(MessageUtil.error("Du bist bereits in einem Spiel!"));
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getGameManager().createGame(arena).addPlayer(player);
|
||||
refreshSignsForArena(arenaName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Schild abbauen → aus Liste entfernen */
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
String key = locKey(event.getBlock().getLocation());
|
||||
@@ -168,10 +207,13 @@ public class SignListener implements Listener {
|
||||
|
||||
// ── Öffentliche Methode: von Game.java aufrufen ──────────────────────────
|
||||
|
||||
/** Aktualisiert alle Schilder einer Arena. Wird von Game.java aufgerufen. */
|
||||
public void refreshSignsForArena(String arenaName) {
|
||||
for (Map.Entry<String, String> entry : signs.entrySet()) {
|
||||
if (!entry.getValue().equalsIgnoreCase(arenaName)) continue;
|
||||
String value = entry.getValue();
|
||||
String entryArena = value.substring(value.indexOf(':') + 1);
|
||||
if (!entryArena.equalsIgnoreCase(arenaName)) continue;
|
||||
|
||||
boolean isSpec = value.startsWith("spec:");
|
||||
Location loc = keyToLocation(entry.getKey());
|
||||
if (loc == null) continue;
|
||||
Block block = loc.getBlock();
|
||||
@@ -180,10 +222,17 @@ public class SignListener implements Listener {
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null) continue;
|
||||
|
||||
sign.setLine(0, TAG_FORMATTED);
|
||||
if (isSpec) {
|
||||
sign.setLine(0, TAG_SPEC_FMT);
|
||||
sign.setLine(1, "§b" + arena.getName());
|
||||
sign.setLine(2, buildStatusLine(arena));
|
||||
sign.setLine(3, "§7Zuschauen");
|
||||
} else {
|
||||
sign.setLine(0, TAG_JOIN_FMT);
|
||||
sign.setLine(1, "§e" + arena.getName());
|
||||
sign.setLine(2, buildStatusLine(arena));
|
||||
sign.setLine(3, "§7Klick zum Joinen");
|
||||
sign.setLine(3, "§7Joinen");
|
||||
}
|
||||
sign.update();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user