Update from Git Manager GUI
This commit is contained in:
74
src/main/java/de/fussball/plugin/Fussball.java
Normal file
74
src/main/java/de/fussball/plugin/Fussball.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package de.fussball.plugin;
|
||||
|
||||
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.listeners.*;
|
||||
import de.fussball.plugin.placeholders.FussballPlaceholders;
|
||||
import de.fussball.plugin.stats.StatsManager;
|
||||
import de.fussball.plugin.utils.Messages;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class Fussball extends JavaPlugin {
|
||||
|
||||
private static Fussball instance;
|
||||
private ArenaManager arenaManager;
|
||||
private GameManager gameManager;
|
||||
private StatsManager statsManager;
|
||||
private SignListener signListener;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
instance = this;
|
||||
ConfigurationSerialization.registerClass(Arena.class);
|
||||
saveDefaultConfig();
|
||||
|
||||
// Manager initialisieren
|
||||
arenaManager = new ArenaManager(this);
|
||||
gameManager = new GameManager(this);
|
||||
statsManager = new StatsManager(this);
|
||||
signListener = new SignListener(this);
|
||||
Messages.init(this);
|
||||
|
||||
registerCommands();
|
||||
registerListeners();
|
||||
|
||||
// PlaceholderAPI-Integration (optional – nur wenn PAPI installiert ist)
|
||||
if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
|
||||
new FussballPlaceholders(this).register();
|
||||
getLogger().info("[Fussball] PlaceholderAPI-Integration aktiviert!");
|
||||
} else {
|
||||
getLogger().info("[Fussball] PlaceholderAPI nicht gefunden – Platzhalter deaktiviert.");
|
||||
}
|
||||
|
||||
getLogger().info("⚽ Fußball-Plugin v" + getDescription().getVersion() + " gestartet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (gameManager != null) gameManager.stopAllGames();
|
||||
if (statsManager != null) statsManager.save();
|
||||
getLogger().info("⚽ Fußball-Plugin gestoppt!");
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
FussballCommand cmd = new FussballCommand(this);
|
||||
getCommand("fussball").setExecutor(cmd);
|
||||
getCommand("fussball").setTabCompleter(cmd);
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
getServer().getPluginManager().registerEvents(new BallListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new BlockListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(signListener, this);
|
||||
}
|
||||
|
||||
public static Fussball getInstance() { return instance; }
|
||||
public ArenaManager getArenaManager() { return arenaManager; }
|
||||
public GameManager getGameManager() { return gameManager; }
|
||||
public StatsManager getStatsManager() { return statsManager; }
|
||||
public SignListener getSignListener() { return signListener; }
|
||||
}
|
||||
148
src/main/java/de/fussball/plugin/StatsManager.java
Normal file
148
src/main/java/de/fussball/plugin/StatsManager.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package de.fussball.plugin.stats;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Verwaltet persistente Spielerstatistiken in stats.yml.
|
||||
* Gespeichert werden: Tore, Schüsse, Siege, Niederlagen, Unentschieden, gespielte Spiele.
|
||||
*/
|
||||
public class StatsManager {
|
||||
|
||||
private final Fussball plugin;
|
||||
private final File statsFile;
|
||||
private FileConfiguration statsConfig;
|
||||
|
||||
// In-Memory-Cache für schnellen Zugriff
|
||||
private final Map<UUID, PlayerStats> cache = new HashMap<>();
|
||||
|
||||
public StatsManager(Fussball plugin) {
|
||||
this.plugin = plugin;
|
||||
this.statsFile = new File(plugin.getDataFolder(), "stats.yml");
|
||||
load();
|
||||
}
|
||||
|
||||
// ── Persistenz ──────────────────────────────────────────────────────────
|
||||
|
||||
private void load() {
|
||||
if (!statsFile.exists()) {
|
||||
try { statsFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
statsConfig = YamlConfiguration.loadConfiguration(statsFile);
|
||||
cache.clear();
|
||||
if (statsConfig.contains("players")) {
|
||||
for (String uuidStr : statsConfig.getConfigurationSection("players").getKeys(false)) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(uuidStr);
|
||||
String path = "players." + uuidStr;
|
||||
PlayerStats stats = new PlayerStats(
|
||||
statsConfig.getString(path + ".name", "Unbekannt"),
|
||||
statsConfig.getInt(path + ".goals", 0),
|
||||
statsConfig.getInt(path + ".kicks", 0),
|
||||
statsConfig.getInt(path + ".wins", 0),
|
||||
statsConfig.getInt(path + ".losses", 0),
|
||||
statsConfig.getInt(path + ".draws", 0),
|
||||
statsConfig.getInt(path + ".games", 0)
|
||||
);
|
||||
cache.put(uuid, stats);
|
||||
} catch (IllegalArgumentException ignored) {}
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("[Fussball] Statistiken geladen: " + cache.size() + " Spieler.");
|
||||
}
|
||||
|
||||
public void save() {
|
||||
statsConfig.set("players", null);
|
||||
for (Map.Entry<UUID, PlayerStats> entry : cache.entrySet()) {
|
||||
String path = "players." + entry.getKey();
|
||||
PlayerStats s = entry.getValue();
|
||||
statsConfig.set(path + ".name", s.name);
|
||||
statsConfig.set(path + ".goals", s.goals);
|
||||
statsConfig.set(path + ".kicks", s.kicks);
|
||||
statsConfig.set(path + ".wins", s.wins);
|
||||
statsConfig.set(path + ".losses", s.losses);
|
||||
statsConfig.set(path + ".draws", s.draws);
|
||||
statsConfig.set(path + ".games", s.games);
|
||||
}
|
||||
try { statsConfig.save(statsFile); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
// ── Datenzugriff ────────────────────────────────────────────────────────
|
||||
|
||||
public PlayerStats getStats(UUID uuid) {
|
||||
return cache.computeIfAbsent(uuid, k -> new PlayerStats("Unbekannt", 0, 0, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
public void addGoal(UUID uuid, String name) {
|
||||
PlayerStats s = getStats(uuid);
|
||||
s.name = name;
|
||||
s.goals++;
|
||||
save();
|
||||
}
|
||||
|
||||
public void addKick(UUID uuid, String name) {
|
||||
PlayerStats s = getStats(uuid);
|
||||
s.name = name;
|
||||
s.kicks++;
|
||||
// Kein sofortiges Speichern bei jedem Kick – Spiel-Ende reicht
|
||||
}
|
||||
|
||||
public void addGameResult(UUID uuid, String name, GameResult result) {
|
||||
PlayerStats s = getStats(uuid);
|
||||
s.name = name;
|
||||
s.games++;
|
||||
switch (result) {
|
||||
case WIN -> s.wins++;
|
||||
case LOSS -> s.losses++;
|
||||
case DRAW -> s.draws++;
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
public void flushKicks(Map<UUID, Integer> kicks, Map<UUID, String> names) {
|
||||
for (Map.Entry<UUID, Integer> entry : kicks.entrySet()) {
|
||||
PlayerStats s = getStats(entry.getKey());
|
||||
if (names.containsKey(entry.getKey())) s.name = names.get(entry.getKey());
|
||||
s.kicks += entry.getValue();
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
/** Gibt die Top-N-Torschützen zurück, sortiert nach Toren */
|
||||
public List<Map.Entry<UUID, PlayerStats>> getTopScorers(int limit) {
|
||||
List<Map.Entry<UUID, PlayerStats>> list = new ArrayList<>(cache.entrySet());
|
||||
list.sort((a, b) -> b.getValue().goals - a.getValue().goals);
|
||||
return list.subList(0, Math.min(limit, list.size()));
|
||||
}
|
||||
|
||||
/** Gibt die Top-N-Spieler nach Siegen zurück */
|
||||
public List<Map.Entry<UUID, PlayerStats>> getTopWins(int limit) {
|
||||
List<Map.Entry<UUID, PlayerStats>> list = new ArrayList<>(cache.entrySet());
|
||||
list.sort((a, b) -> b.getValue().wins - a.getValue().wins);
|
||||
return list.subList(0, Math.min(limit, list.size()));
|
||||
}
|
||||
|
||||
// ── Innere Klassen ───────────────────────────────────────────────────────
|
||||
|
||||
public static class PlayerStats {
|
||||
public String name;
|
||||
public int goals, kicks, wins, losses, draws, games;
|
||||
|
||||
public PlayerStats(String name, int goals, int kicks, int wins, int losses, int draws, int games) {
|
||||
this.name = name; this.goals = goals; this.kicks = kicks;
|
||||
this.wins = wins; this.losses = losses; this.draws = draws; this.games = games;
|
||||
}
|
||||
|
||||
public double getWinRate() {
|
||||
if (games == 0) return 0.0;
|
||||
return (double) wins / games * 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GameResult { WIN, LOSS, DRAW }
|
||||
}
|
||||
253
src/main/java/de/fussball/plugin/arena/Arena.java
Normal file
253
src/main/java/de/fussball/plugin/arena/Arena.java
Normal file
@@ -0,0 +1,253 @@
|
||||
package de.fussball.plugin.arena;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
import java.util.*;
|
||||
|
||||
public class Arena implements ConfigurationSerializable {
|
||||
|
||||
private final String name;
|
||||
private Location center, redSpawn, blueSpawn, ballSpawn;
|
||||
private Location redGoalMin, redGoalMax, blueGoalMin, blueGoalMax, lobby;
|
||||
private Location fieldMin, fieldMax;
|
||||
private int minPlayers, maxPlayers, gameDuration;
|
||||
|
||||
/**
|
||||
* Neue Arena erstellt – Standardwerte werden aus der config.yml gelesen.
|
||||
* Bereits gespeicherte Arenen laden ihre eigenen Werte über deserialize().
|
||||
*/
|
||||
public Arena(String name) {
|
||||
this.name = name;
|
||||
Fussball plugin = Fussball.getInstance();
|
||||
if (plugin != null) {
|
||||
this.minPlayers = plugin.getConfig().getInt("defaults.min-players", 2);
|
||||
this.maxPlayers = plugin.getConfig().getInt("defaults.max-players", 10);
|
||||
this.gameDuration = plugin.getConfig().getInt("defaults.game-duration", 300);
|
||||
} else {
|
||||
// Fallback falls getInstance() noch nicht verfügbar (z.B. Deserialisierung)
|
||||
this.minPlayers = 2;
|
||||
this.maxPlayers = 10;
|
||||
this.gameDuration = 300;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSetupComplete() {
|
||||
return center != null && redSpawn != null && blueSpawn != null && ballSpawn != null
|
||||
&& redGoalMin != null && redGoalMax != null
|
||||
&& blueGoalMin != null && blueGoalMax != null && lobby != null;
|
||||
}
|
||||
|
||||
public boolean isInRedGoal(Location loc) { return isInRegion(loc, redGoalMin, redGoalMax); }
|
||||
public boolean isInBlueGoal(Location loc) { return isInRegion(loc, blueGoalMin, blueGoalMax); }
|
||||
|
||||
/**
|
||||
* BUG FIX: Nur X und Z prüfen (Y ignorieren).
|
||||
* Vorher führte die Y-Prüfung dazu, dass der Ball beim Anstoß sofort
|
||||
* als Aus erkannt wurde, weil der ArmorStand über dem Boden schwebt.
|
||||
*/
|
||||
public boolean isInField(Location loc) {
|
||||
if (fieldMin == null || fieldMax == null) return true;
|
||||
return isInField2D(loc);
|
||||
}
|
||||
|
||||
private boolean isInField2D(Location loc) {
|
||||
if (fieldMin == null || fieldMax == null || loc == null) return true;
|
||||
double minX = Math.min(fieldMin.getX(), fieldMax.getX());
|
||||
double maxX = Math.max(fieldMin.getX(), fieldMax.getX());
|
||||
double minZ = Math.min(fieldMin.getZ(), fieldMax.getZ());
|
||||
double maxZ = Math.max(fieldMin.getZ(), fieldMax.getZ());
|
||||
return loc.getX() >= minX && loc.getX() <= maxX
|
||||
&& loc.getZ() >= minZ && loc.getZ() <= maxZ;
|
||||
}
|
||||
|
||||
/** Auf welcher Seite hat der Ball das Feld verlassen? (nur XZ, kein Y) */
|
||||
public String getOutSide(Location loc) {
|
||||
if (fieldMin == null || fieldMax == null) return null;
|
||||
if (isInField2D(loc)) return null;
|
||||
|
||||
double minX = Math.min(fieldMin.getX(), fieldMax.getX());
|
||||
double maxX = Math.max(fieldMin.getX(), fieldMax.getX());
|
||||
double minZ = Math.min(fieldMin.getZ(), fieldMax.getZ());
|
||||
double maxZ = Math.max(fieldMin.getZ(), fieldMax.getZ());
|
||||
double lenX = maxX - minX;
|
||||
double lenZ = maxZ - minZ;
|
||||
|
||||
if (lenZ >= lenX) {
|
||||
if (loc.getZ() < minZ) return "redEnd";
|
||||
if (loc.getZ() > maxZ) return "blueEnd";
|
||||
return "side";
|
||||
} else {
|
||||
if (loc.getX() < minX) return "redEnd";
|
||||
if (loc.getX() > maxX) return "blueEnd";
|
||||
return "side";
|
||||
}
|
||||
}
|
||||
|
||||
public Location clampToField(Location loc) {
|
||||
if (fieldMin == null || fieldMax == null) return loc;
|
||||
double x = Math.max(Math.min(fieldMin.getX(), fieldMax.getX()),
|
||||
Math.min(loc.getX(), Math.max(fieldMin.getX(), fieldMax.getX())));
|
||||
double y = loc.getY();
|
||||
double z = Math.max(Math.min(fieldMin.getZ(), fieldMax.getZ()),
|
||||
Math.min(loc.getZ(), Math.max(fieldMin.getZ(), fieldMax.getZ())));
|
||||
return new Location(loc.getWorld(), x, y, z);
|
||||
}
|
||||
|
||||
// Tor-Erkennung: volle 3D-Prüfung (Y ist für das Tor wichtig!)
|
||||
private boolean isInRegion(Location loc, Location min, Location max) {
|
||||
if (min == null || max == null || loc == null) return false;
|
||||
if (loc.getWorld() == null || min.getWorld() == null) return false;
|
||||
if (!loc.getWorld().equals(min.getWorld())) return false;
|
||||
return loc.getX() >= Math.min(min.getX(), max.getX()) && loc.getX() <= Math.max(min.getX(), max.getX())
|
||||
&& loc.getY() >= Math.min(min.getY(), max.getY()) && loc.getY() <= Math.max(min.getY(), max.getY())
|
||||
&& loc.getZ() >= Math.min(min.getZ(), max.getZ()) && loc.getZ() <= Math.max(min.getZ(), max.getZ());
|
||||
}
|
||||
|
||||
// ── Spielfeld-Achse (für Abseits-Berechnung) ────────────────────────────
|
||||
|
||||
/**
|
||||
* Gibt den Einheitsvektor zurück der vom roten Tor zum blauen Tor zeigt.
|
||||
* Funktioniert unabhängig davon ob das Feld in X- oder Z-Richtung ausgerichtet ist.
|
||||
*/
|
||||
public org.bukkit.util.Vector getFieldDirection() {
|
||||
if (redGoalMin == null || redGoalMax == null || blueGoalMin == null || blueGoalMax == null) return null;
|
||||
double rX = (redGoalMin.getX() + redGoalMax.getX()) / 2.0;
|
||||
double rZ = (redGoalMin.getZ() + redGoalMax.getZ()) / 2.0;
|
||||
double bX = (blueGoalMin.getX() + blueGoalMax.getX()) / 2.0;
|
||||
double bZ = (blueGoalMin.getZ() + blueGoalMax.getZ()) / 2.0;
|
||||
org.bukkit.util.Vector dir = new org.bukkit.util.Vector(bX - rX, 0, bZ - rZ);
|
||||
double len = dir.length();
|
||||
if (len < 0.001) return null;
|
||||
return dir.multiply(1.0 / len);
|
||||
}
|
||||
|
||||
/** Projektionswert einer Location auf die Spielfeld-Achse */
|
||||
public double getAxisValue(org.bukkit.Location loc) {
|
||||
org.bukkit.util.Vector dir = getFieldDirection();
|
||||
if (dir == null || loc == null) return 0;
|
||||
return loc.getX() * dir.getX() + loc.getZ() * dir.getZ();
|
||||
}
|
||||
|
||||
public double getRedGoalAxisValue() {
|
||||
if (redGoalMin == null || redGoalMax == null) return 0;
|
||||
org.bukkit.util.Vector dir = getFieldDirection();
|
||||
if (dir == null) return 0;
|
||||
double cx = (redGoalMin.getX() + redGoalMax.getX()) / 2.0;
|
||||
double cz = (redGoalMin.getZ() + redGoalMax.getZ()) / 2.0;
|
||||
return cx * dir.getX() + cz * dir.getZ();
|
||||
}
|
||||
|
||||
public double getBlueGoalAxisValue() {
|
||||
if (blueGoalMin == null || blueGoalMax == null) return 0;
|
||||
org.bukkit.util.Vector dir = getFieldDirection();
|
||||
if (dir == null) return 0;
|
||||
double cx = (blueGoalMin.getX() + blueGoalMax.getX()) / 2.0;
|
||||
double cz = (blueGoalMin.getZ() + blueGoalMax.getZ()) / 2.0;
|
||||
return cx * dir.getX() + cz * dir.getZ();
|
||||
}
|
||||
|
||||
public double getCenterAxisValue() {
|
||||
return (getRedGoalAxisValue() + getBlueGoalAxisValue()) / 2.0;
|
||||
}
|
||||
|
||||
// ── Serialisierung ───────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("name", name);
|
||||
map.put("minPlayers", minPlayers);
|
||||
map.put("maxPlayers", maxPlayers);
|
||||
map.put("gameDuration", gameDuration);
|
||||
if (lobby != null) map.put("lobby", serLoc(lobby));
|
||||
if (center != null) map.put("center", serLoc(center));
|
||||
if (redSpawn != null) map.put("redSpawn", serLoc(redSpawn));
|
||||
if (blueSpawn != null) map.put("blueSpawn", serLoc(blueSpawn));
|
||||
if (ballSpawn != null) map.put("ballSpawn", serLoc(ballSpawn));
|
||||
if (redGoalMin != null) map.put("redGoalMin", serLoc(redGoalMin));
|
||||
if (redGoalMax != null) map.put("redGoalMax", serLoc(redGoalMax));
|
||||
if (blueGoalMin != null) map.put("blueGoalMin", serLoc(blueGoalMin));
|
||||
if (blueGoalMax != null) map.put("blueGoalMax", serLoc(blueGoalMax));
|
||||
if (fieldMin != null) map.put("fieldMin", serLoc(fieldMin));
|
||||
if (fieldMax != null) map.put("fieldMax", serLoc(fieldMax));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beim Laden gespeicherter Arenen werden die eigenen Werte aus der YAML-Datei
|
||||
* gelesen – NICHT aus der config.yml. So kann jede Arena eigene Werte haben.
|
||||
*/
|
||||
public static Arena deserialize(Map<String, Object> map) {
|
||||
Arena a = new Arena((String) map.get("name"));
|
||||
// Überschreibe Konstruktor-Defaults mit den gespeicherten Werten
|
||||
a.minPlayers = getInt(map, "minPlayers", a.minPlayers);
|
||||
a.maxPlayers = getInt(map, "maxPlayers", a.maxPlayers);
|
||||
a.gameDuration = getInt(map, "gameDuration", a.gameDuration);
|
||||
if (map.containsKey("lobby")) a.lobby = desLoc(map.get("lobby"));
|
||||
if (map.containsKey("center")) a.center = desLoc(map.get("center"));
|
||||
if (map.containsKey("redSpawn")) a.redSpawn = desLoc(map.get("redSpawn"));
|
||||
if (map.containsKey("blueSpawn")) a.blueSpawn = desLoc(map.get("blueSpawn"));
|
||||
if (map.containsKey("ballSpawn")) a.ballSpawn = desLoc(map.get("ballSpawn"));
|
||||
if (map.containsKey("redGoalMin")) a.redGoalMin = desLoc(map.get("redGoalMin"));
|
||||
if (map.containsKey("redGoalMax")) a.redGoalMax = desLoc(map.get("redGoalMax"));
|
||||
if (map.containsKey("blueGoalMin")) a.blueGoalMin = desLoc(map.get("blueGoalMin"));
|
||||
if (map.containsKey("blueGoalMax")) a.blueGoalMax = desLoc(map.get("blueGoalMax"));
|
||||
if (map.containsKey("fieldMin")) a.fieldMin = desLoc(map.get("fieldMin"));
|
||||
if (map.containsKey("fieldMax")) a.fieldMax = desLoc(map.get("fieldMax"));
|
||||
return a;
|
||||
}
|
||||
|
||||
private static String serLoc(Location l) {
|
||||
return l.getWorld().getName() + ";" + l.getX() + ";" + l.getY() + ";" + l.getZ() + ";" + l.getYaw() + ";" + l.getPitch();
|
||||
}
|
||||
|
||||
private static Location desLoc(Object obj) {
|
||||
if (obj == null) return null;
|
||||
try {
|
||||
String[] p = obj.toString().split(";");
|
||||
World world = Bukkit.getWorld(p[0]);
|
||||
if (world == null) return null;
|
||||
return new Location(world, Double.parseDouble(p[1]), Double.parseDouble(p[2]), Double.parseDouble(p[3]),
|
||||
p.length > 4 ? Float.parseFloat(p[4]) : 0f, p.length > 5 ? Float.parseFloat(p[5]) : 0f);
|
||||
} catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private static int getInt(Map<String, Object> map, String key, int def) {
|
||||
Object v = map.get(key); return v instanceof Number ? ((Number) v).intValue() : def;
|
||||
}
|
||||
|
||||
// ── Getter / Setter ──────────────────────────────────────────────────────
|
||||
|
||||
public String getName() { return name; }
|
||||
public Location getCenter() { return center; }
|
||||
public void setCenter(Location l) { this.center = l; }
|
||||
public Location getRedSpawn() { return redSpawn; }
|
||||
public void setRedSpawn(Location l) { this.redSpawn = l; }
|
||||
public Location getBlueSpawn() { return blueSpawn; }
|
||||
public void setBlueSpawn(Location l) { this.blueSpawn = l; }
|
||||
public Location getBallSpawn() { return ballSpawn; }
|
||||
public void setBallSpawn(Location l) { this.ballSpawn = l; }
|
||||
public Location getRedGoalMin() { return redGoalMin; }
|
||||
public void setRedGoalMin(Location l) { this.redGoalMin = l; }
|
||||
public Location getRedGoalMax() { return redGoalMax; }
|
||||
public void setRedGoalMax(Location l) { this.redGoalMax = l; }
|
||||
public Location getBlueGoalMin() { return blueGoalMin; }
|
||||
public void setBlueGoalMin(Location l) { this.blueGoalMin = l; }
|
||||
public Location getBlueGoalMax() { return blueGoalMax; }
|
||||
public void setBlueGoalMax(Location l) { this.blueGoalMax = l; }
|
||||
public Location getLobby() { return lobby; }
|
||||
public void setLobby(Location l) { this.lobby = l; }
|
||||
public Location getFieldMin() { return fieldMin; }
|
||||
public void setFieldMin(Location l) { this.fieldMin = l; }
|
||||
public Location getFieldMax() { return fieldMax; }
|
||||
public void setFieldMax(Location l) { this.fieldMax = l; }
|
||||
public int getMinPlayers() { return minPlayers; }
|
||||
public void setMinPlayers(int n) { this.minPlayers = n; }
|
||||
public int getMaxPlayers() { return maxPlayers; }
|
||||
public void setMaxPlayers(int n) { this.maxPlayers = n; }
|
||||
public int getGameDuration() { return gameDuration; }
|
||||
public void setGameDuration(int n) { this.gameDuration = n; }
|
||||
}
|
||||
65
src/main/java/de/fussball/plugin/arena/ArenaManager.java
Normal file
65
src/main/java/de/fussball/plugin/arena/ArenaManager.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package de.fussball.plugin.arena;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class ArenaManager {
|
||||
|
||||
private final Fussball plugin;
|
||||
private final Map<String, Arena> arenas = new HashMap<>();
|
||||
private final File arenaFile;
|
||||
private FileConfiguration arenaConfig;
|
||||
|
||||
public ArenaManager(Fussball plugin) {
|
||||
this.plugin = plugin;
|
||||
this.arenaFile = new File(plugin.getDataFolder(), "arenas.yml");
|
||||
loadArenas();
|
||||
}
|
||||
|
||||
public void loadArenas() {
|
||||
if (!arenaFile.exists()) {
|
||||
try { arenaFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
arenaConfig = YamlConfiguration.loadConfiguration(arenaFile);
|
||||
arenas.clear();
|
||||
if (arenaConfig.contains("arenas")) {
|
||||
for (String key : arenaConfig.getConfigurationSection("arenas").getKeys(false)) {
|
||||
Object obj = arenaConfig.get("arenas." + key);
|
||||
if (obj instanceof Arena) arenas.put(key.toLowerCase(), (Arena) obj);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("Arenen geladen: " + arenas.size());
|
||||
}
|
||||
|
||||
public void saveArenas() {
|
||||
arenaConfig.set("arenas", null);
|
||||
for (Map.Entry<String, Arena> e : arenas.entrySet())
|
||||
arenaConfig.set("arenas." + e.getKey(), e.getValue());
|
||||
try { arenaConfig.save(arenaFile); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
public Arena createArena(String name) {
|
||||
Arena arena = new Arena(name);
|
||||
arenas.put(name.toLowerCase(), arena);
|
||||
saveArenas();
|
||||
return arena;
|
||||
}
|
||||
|
||||
public boolean deleteArena(String name) {
|
||||
if (arenas.remove(name.toLowerCase()) != null) { saveArenas(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
public Arena getArena(String name) { return arenas.get(name.toLowerCase()); }
|
||||
public boolean arenaExists(String name) { return arenas.containsKey(name.toLowerCase()); }
|
||||
public Collection<Arena> getAllArenas() { return arenas.values(); }
|
||||
public List<String> getArenaNames() { return new ArrayList<>(arenas.keySet()); }
|
||||
|
||||
public void saveArena(Arena arena) {
|
||||
arenas.put(arena.getName().toLowerCase(), arena);
|
||||
saveArenas();
|
||||
}
|
||||
}
|
||||
333
src/main/java/de/fussball/plugin/commands/FussballCommand.java
Normal file
333
src/main/java/de/fussball/plugin/commands/FussballCommand.java
Normal file
@@ -0,0 +1,333 @@
|
||||
package de.fussball.plugin.commands;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
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.stats.StatsManager;
|
||||
import de.fussball.plugin.utils.MessageUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.*;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class FussballCommand implements CommandExecutor, TabCompleter {
|
||||
|
||||
private final Fussball plugin;
|
||||
public FussballCommand(Fussball plugin) { this.plugin = plugin; }
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) { sendHelp(sender); return true; }
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
|
||||
// ── Spieler-Befehle ──────────────────────────────────────────────
|
||||
|
||||
case "join" -> {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||
if (args.length < 2) { player.sendMessage(MessageUtil.error("Benutze: /fb join <arena>")); return true; }
|
||||
Arena arena = plugin.getArenaManager().getArena(args[1]);
|
||||
if (arena == null) { player.sendMessage(MessageUtil.error("Arena §e" + args[1] + " §cnicht gefunden!")); return true; }
|
||||
if (!arena.isSetupComplete()) { player.sendMessage(MessageUtil.error("Arena nicht vollständig eingerichtet!")); return true; }
|
||||
if (plugin.getGameManager().isInAnyGame(player)) { player.sendMessage(MessageUtil.error("Du bist bereits in einem Spiel! Tippe §e/fb leave§c.")); return true; }
|
||||
plugin.getGameManager().createGame(arena).addPlayer(player);
|
||||
}
|
||||
|
||||
case "leave" -> {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||
// Aus Spiel
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game != null) { game.removePlayer(player); player.sendMessage(MessageUtil.success("Du hast das Spiel verlassen!")); return true; }
|
||||
// Aus Zuschauer
|
||||
Game specGame = plugin.getGameManager().getSpectatorGame(player);
|
||||
if (specGame != null) { specGame.removeSpectator(player); player.sendMessage(MessageUtil.success("Du hast das Zuschauen beendet!")); return true; }
|
||||
// Aus Warteschlange
|
||||
plugin.getGameManager().removeFromAllQueues(player);
|
||||
player.sendMessage(MessageUtil.warn("Du bist in keinem Spiel und in keiner Warteschlange."));
|
||||
}
|
||||
|
||||
case "spectate", "spec" -> {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||
if (args.length < 2) { player.sendMessage(MessageUtil.error("Benutze: /fb spectate <arena>")); return true; }
|
||||
Arena arena = plugin.getArenaManager().getArena(args[1]);
|
||||
if (arena == null) { player.sendMessage(MessageUtil.error("Arena nicht gefunden!")); return true; }
|
||||
if (plugin.getGameManager().isInAnyGame(player)) { player.sendMessage(MessageUtil.error("Verlasse zuerst dein aktuelles Spiel!")); return true; }
|
||||
Game game = plugin.getGameManager().getGame(arena.getName());
|
||||
if (game == null) { player.sendMessage(MessageUtil.error("Kein laufendes Spiel in dieser Arena!")); return true; }
|
||||
game.addSpectator(player);
|
||||
}
|
||||
|
||||
case "list" -> {
|
||||
sender.sendMessage(MessageUtil.header("⚽ Verfügbare Arenen"));
|
||||
Collection<Arena> arenas = plugin.getArenaManager().getAllArenas();
|
||||
if (arenas.isEmpty()) { sender.sendMessage(MessageUtil.warn("Keine Arenen vorhanden.")); return true; }
|
||||
for (Arena a : arenas) {
|
||||
Game g = plugin.getGameManager().getGame(a.getName());
|
||||
int queueSize = plugin.getGameManager().getQueueSize(a.getName());
|
||||
String status = g != null ? statusDot(g.getState()) : "§a●";
|
||||
String players = g != null ? g.getAllPlayers().size() + "/" + a.getMaxPlayers() : "0/" + a.getMaxPlayers();
|
||||
String setup = a.isSetupComplete() ? "§a✔" : "§c✗";
|
||||
String queue = queueSize > 0 ? " §8(§e" + queueSize + " §8warten)" : "";
|
||||
sender.sendMessage("§7 " + status + " §e" + a.getName() + " §7[" + players + "] " + setup + queue);
|
||||
}
|
||||
}
|
||||
|
||||
case "stats" -> {
|
||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||
Player target = args.length >= 2 ? Bukkit.getPlayer(args[1]) : player;
|
||||
if (target == null) { player.sendMessage(MessageUtil.error("Spieler §e" + args[1] + " §cnicht gefunden!")); return true; }
|
||||
StatsManager.PlayerStats s = plugin.getStatsManager().getStats(target.getUniqueId());
|
||||
player.sendMessage(MessageUtil.header("Statistiken: " + target.getName()));
|
||||
player.sendMessage("§7 ⚽ Tore: §e" + s.goals);
|
||||
player.sendMessage("§7 👟 Schüsse: §e" + s.kicks);
|
||||
player.sendMessage("§7 🏆 Siege: §a" + s.wins);
|
||||
player.sendMessage("§7 ❌ Niederlagen: §c" + s.losses);
|
||||
player.sendMessage("§7 ➖ Unentschieden: §7" + s.draws);
|
||||
player.sendMessage("§7 📊 Gespielte Spiele: §e" + s.games);
|
||||
player.sendMessage("§7 📈 Siegquote: §e" + String.format("%.1f", s.getWinRate()) + "§7%");
|
||||
// In-Game-Statistik anhängen wenn aktiv
|
||||
Game inGame = plugin.getGameManager().getPlayerGame(target);
|
||||
if (inGame != null) {
|
||||
player.sendMessage("§8--- Aktuelles Spiel ---");
|
||||
player.sendMessage("§7 Tore heute: §e" + inGame.getGoals().getOrDefault(target.getUniqueId(), 0));
|
||||
player.sendMessage("§7 Schüsse heute: §e" + inGame.getKicks().getOrDefault(target.getUniqueId(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Admin-Befehle ────────────────────────────────────────────────
|
||||
|
||||
case "create" -> {
|
||||
if (!sender.hasPermission("fussball.admin")) { sender.sendMessage(MessageUtil.error("Keine Berechtigung!")); return true; }
|
||||
if (args.length < 2) { sender.sendMessage(MessageUtil.error("Benutze: /fb create <name>")); return true; }
|
||||
if (plugin.getArenaManager().arenaExists(args[1])) { sender.sendMessage(MessageUtil.error("Arena §e" + args[1] + " §cexistiert bereits!")); return true; }
|
||||
plugin.getArenaManager().createArena(args[1]);
|
||||
sender.sendMessage(MessageUtil.success("Arena §e" + args[1] + " §aerstellt! Richte sie mit §e/fb setup §aein."));
|
||||
}
|
||||
|
||||
case "delete" -> {
|
||||
if (!sender.hasPermission("fussball.admin")) { sender.sendMessage(MessageUtil.error("Keine Berechtigung!")); return true; }
|
||||
if (args.length < 2) { sender.sendMessage(MessageUtil.error("Benutze: /fb delete <arena>")); return true; }
|
||||
sender.sendMessage(plugin.getArenaManager().deleteArena(args[1])
|
||||
? MessageUtil.success("Arena §e" + args[1] + " §agelöscht!") : MessageUtil.error("Arena nicht gefunden!"));
|
||||
}
|
||||
|
||||
case "setup" -> {
|
||||
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 < 3) { sendSetupHelp(player); return true; }
|
||||
handleSetup(player, args);
|
||||
}
|
||||
|
||||
case "stop" -> {
|
||||
if (!sender.hasPermission("fussball.admin")) { sender.sendMessage(MessageUtil.error("Keine Berechtigung!")); return true; }
|
||||
if (args.length < 2) { sender.sendMessage(MessageUtil.error("Benutze: /fb stop <arena>")); return true; }
|
||||
Game game = plugin.getGameManager().getGame(args[1]);
|
||||
if (game == null) { sender.sendMessage(MessageUtil.error("Kein aktives Spiel in §e" + args[1] + "§c!")); return true; }
|
||||
game.endGame(null);
|
||||
sender.sendMessage(MessageUtil.success("Spiel in §e" + args[1] + " §aberendet."));
|
||||
}
|
||||
|
||||
case "top" -> {
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(MessageUtil.error("Benutze: /fussball top goals|wins|kicks"));
|
||||
return true;
|
||||
}
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "goals" -> {
|
||||
sender.sendMessage(MessageUtil.header("🏆 Top Torschützen"));
|
||||
var list = plugin.getStatsManager().getTopScorers(10);
|
||||
if (list.isEmpty()) { sender.sendMessage(MessageUtil.warn("Noch keine Daten.")); break; }
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
var e = list.get(i);
|
||||
sender.sendMessage("§e#" + (i+1) + " §f" + e.getValue().name
|
||||
+ " §7— §e" + e.getValue().goals + " §7Tore"
|
||||
+ " §8(§7" + e.getValue().games + " Spiele§8)");
|
||||
}
|
||||
}
|
||||
case "wins" -> {
|
||||
sender.sendMessage(MessageUtil.header("🏆 Top Gewinner"));
|
||||
var list = plugin.getStatsManager().getTopWins(10);
|
||||
if (list.isEmpty()) { sender.sendMessage(MessageUtil.warn("Noch keine Daten.")); break; }
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
var e = list.get(i);
|
||||
sender.sendMessage("§e#" + (i+1) + " §f" + e.getValue().name
|
||||
+ " §7— §a" + e.getValue().wins + " §7Siege"
|
||||
+ " §8(§7" + String.format("%.0f", e.getValue().getWinRate()) + "% WR§8)");
|
||||
}
|
||||
}
|
||||
case "kicks" -> {
|
||||
sender.sendMessage(MessageUtil.header("🏆 Top Schützen (Schüsse)"));
|
||||
var list = plugin.getStatsManager().getTopScorers(10);
|
||||
// Nutze getTopScorers und zeige kicks
|
||||
var kickList = new java.util.ArrayList<>(plugin.getStatsManager().getTopScorers(100));
|
||||
kickList.sort((a, b) -> b.getValue().kicks - a.getValue().kicks);
|
||||
for (int i = 0; i < Math.min(10, kickList.size()); i++) {
|
||||
var e = kickList.get(i);
|
||||
sender.sendMessage("§e#" + (i+1) + " §f" + e.getValue().name
|
||||
+ " §7— §e" + e.getValue().kicks + " §7Schüsse");
|
||||
}
|
||||
}
|
||||
default -> sender.sendMessage(MessageUtil.error("Gültig: goals | wins | kicks"));
|
||||
}
|
||||
}
|
||||
|
||||
case "debug" -> {
|
||||
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) { sender.sendMessage(MessageUtil.error("Benutze: /fb debug <arena>")); return true; }
|
||||
Arena arena = plugin.getArenaManager().getArena(args[1]);
|
||||
if (arena == null) { sender.sendMessage(MessageUtil.error("Arena nicht gefunden!")); return true; }
|
||||
handleDebug(player, arena);
|
||||
}
|
||||
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Setup-Handler ────────────────────────────────────────────────────────
|
||||
|
||||
private void handleSetup(Player player, String[] args) {
|
||||
Arena arena = plugin.getArenaManager().getArena(args[1]);
|
||||
if (arena == null) { player.sendMessage(MessageUtil.error("Arena §e" + args[1] + " §cnicht gefunden!")); return; }
|
||||
|
||||
switch (args[2].toLowerCase()) {
|
||||
case "lobby" -> { arena.setLobby(player.getLocation()); player.sendMessage(MessageUtil.success("Lobby gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "redspawn" -> { arena.setRedSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Roter Spawn gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "bluespawn" -> { arena.setBlueSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Blauer Spawn gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "ballspawn" -> { arena.setBallSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Ball-Spawn gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "center" -> { arena.setCenter(player.getLocation()); player.sendMessage(MessageUtil.success("Mittelpunkt gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "redgoalmin" -> { arena.setRedGoalMin(player.getLocation()); player.sendMessage(MessageUtil.success("Rotes Tor Min gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "redgoalmax" -> { arena.setRedGoalMax(player.getLocation()); player.sendMessage(MessageUtil.success("Rotes Tor Max gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "bluegoalmin" -> { arena.setBlueGoalMin(player.getLocation()); player.sendMessage(MessageUtil.success("Blaues Tor Min gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "bluegoalmax" -> { arena.setBlueGoalMax(player.getLocation()); player.sendMessage(MessageUtil.success("Blaues Tor Max gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "fieldmin" -> { arena.setFieldMin(player.getLocation()); player.sendMessage(MessageUtil.success("Spielfeld Min gesetzt: " + locStr(player.getLocation()))); }
|
||||
case "fieldmax" -> { arena.setFieldMax(player.getLocation()); player.sendMessage(MessageUtil.success("Spielfeld Max 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")); }
|
||||
case "info" -> {
|
||||
player.sendMessage(MessageUtil.header("Arena: " + arena.getName()));
|
||||
player.sendMessage("§7 Lobby: " + check(arena.getLobby()));
|
||||
player.sendMessage("§7 Roter Spawn: " + check(arena.getRedSpawn()));
|
||||
player.sendMessage("§7 Blauer Spawn: " + check(arena.getBlueSpawn()));
|
||||
player.sendMessage("§7 Ball-Spawn: " + check(arena.getBallSpawn()));
|
||||
player.sendMessage("§7 Mittelpunkt: " + check(arena.getCenter()));
|
||||
player.sendMessage("§7 Rotes Tor: " + check(arena.getRedGoalMin(), arena.getRedGoalMax()));
|
||||
player.sendMessage("§7 Blaues Tor: " + check(arena.getBlueGoalMin(), arena.getBlueGoalMax()));
|
||||
player.sendMessage("§7 Spielfeld: " + check(arena.getFieldMin(), arena.getFieldMax()) + " §8(optional)");
|
||||
player.sendMessage("§7 Min. Spieler: §e" + arena.getMinPlayers());
|
||||
player.sendMessage("§7 Max. Spieler: §e" + arena.getMaxPlayers());
|
||||
player.sendMessage("§7 Spieldauer: §e" + arena.getGameDuration() + "s");
|
||||
player.sendMessage("§7 Setup komplett: " + (arena.isSetupComplete() ? "§a✔ JA" : "§c✗ NEIN"));
|
||||
return;
|
||||
}
|
||||
default -> { sendSetupHelp(player); return; }
|
||||
}
|
||||
plugin.getArenaManager().saveArena(arena);
|
||||
}
|
||||
|
||||
// ── Debug-Handler ────────────────────────────────────────────────────────
|
||||
|
||||
private void handleDebug(Player player, Arena arena) {
|
||||
player.sendMessage(MessageUtil.header("DEBUG: " + arena.getName()));
|
||||
printRegion(player, "§cROTES TOR", arena.getRedGoalMin(), arena.getRedGoalMax());
|
||||
printRegion(player, "§9BLAUES TOR", arena.getBlueGoalMin(), arena.getBlueGoalMax());
|
||||
printRegion(player, "§aSPIELFELD", arena.getFieldMin(), arena.getFieldMax());
|
||||
|
||||
Game game = plugin.getGameManager().getGame(arena.getName());
|
||||
if (game != null && game.getBall() != null && game.getBall().isActive()) {
|
||||
Ball ball = game.getBall();
|
||||
Location bl = ball.getEntity().getLocation();
|
||||
Location bh = bl.clone().add(0, 1.4, 0);
|
||||
player.sendMessage("§e--- BALL ---");
|
||||
player.sendMessage("§7Fuß: §f" + locStr(bl));
|
||||
player.sendMessage("§7Kopf: §f" + locStr(bh));
|
||||
player.sendMessage("§7RotesTor: Fuß=" + yn(arena.isInRedGoal(bl)) + " Kopf=" + yn(arena.isInRedGoal(bh)));
|
||||
player.sendMessage("§7BlauesTor: Fuß=" + yn(arena.isInBlueGoal(bl)) + " Kopf=" + yn(arena.isInBlueGoal(bh)));
|
||||
player.sendMessage("§7Im Feld: " + yn(arena.isInField(bl)));
|
||||
player.sendMessage("§7Aus-Seite: §f" + (arena.getOutSide(bl) != null ? arena.getOutSide(bl) : "keine"));
|
||||
} else {
|
||||
player.sendMessage("§7(Kein aktiver Ball)");
|
||||
}
|
||||
player.sendMessage("§e--- DEINE POSITION ---");
|
||||
player.sendMessage("§f" + locStr(player.getLocation()));
|
||||
player.sendMessage("§7RotesTor: " + yn(arena.isInRedGoal(player.getLocation())));
|
||||
player.sendMessage("§7BlauesTor: " + yn(arena.isInBlueGoal(player.getLocation())));
|
||||
player.sendMessage("§7Im Feld: " + yn(arena.isInField(player.getLocation())));
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
private String statusDot(GameState s) {
|
||||
return switch (s) {
|
||||
case WAITING -> "§a●";
|
||||
case STARTING -> "§e●";
|
||||
case RUNNING, GOAL, HALFTIME, OVERTIME -> "§c●";
|
||||
case PENALTY -> "§d●";
|
||||
case ENDING -> "§7●";
|
||||
};
|
||||
}
|
||||
|
||||
private void printRegion(Player p, String label, Location min, Location max) {
|
||||
p.sendMessage("§e--- " + label + " §e---");
|
||||
if (min == null || max == null) { p.sendMessage("§cNICHT GESETZT"); return; }
|
||||
p.sendMessage("§7Min: §f" + locStr(min) + " §7Max: §f" + locStr(max));
|
||||
p.sendMessage("§7X: §f" + fmt(Math.min(min.getX(), max.getX())) + "§7─§f" + fmt(Math.max(min.getX(), max.getX())));
|
||||
p.sendMessage("§7Y: §f" + fmt(Math.min(min.getY(), max.getY())) + "§7─§f" + fmt(Math.max(min.getY(), max.getY())));
|
||||
p.sendMessage("§7Z: §f" + fmt(Math.min(min.getZ(), max.getZ())) + "§7─§f" + fmt(Math.max(min.getZ(), max.getZ())));
|
||||
}
|
||||
|
||||
private void sendHelp(CommandSender s) {
|
||||
s.sendMessage(MessageUtil.header("⚽ Fußball Plugin"));
|
||||
s.sendMessage("§e/fb join <arena> §7- Spiel beitreten");
|
||||
s.sendMessage("§e/fb leave §7- Spiel / Zuschauer verlassen");
|
||||
s.sendMessage("§e/fb spectate <arena> §7- Spiel zuschauen");
|
||||
s.sendMessage("§e/fb list §7- Arenen anzeigen");
|
||||
s.sendMessage("§e/fb stats [spieler] §7- Statistiken anzeigen");
|
||||
s.sendMessage("§e/fb top [goals|wins] §7- Bestenliste");
|
||||
if (s.hasPermission("fussball.admin")) {
|
||||
s.sendMessage("§c§lAdmin: §ccreate / delete / setup / stop / debug");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSetupHelp(Player p) {
|
||||
p.sendMessage(MessageUtil.header("Setup-Optionen"));
|
||||
p.sendMessage("§e/fb setup <arena> lobby|redspawn|bluespawn|ballspawn|center");
|
||||
p.sendMessage("§e/fb setup <arena> redgoalmin|redgoalmax|bluegoalmin|bluegoalmax");
|
||||
p.sendMessage("§e/fb setup <arena> fieldmin|fieldmax §8(optional – Aus-Erkennung)");
|
||||
p.sendMessage("§e/fb setup <arena> minplayers <n>|maxplayers <n>|duration <s>|info");
|
||||
}
|
||||
|
||||
private String check(Location l) { return l != null ? "§a✔ " + locStr(l) : "§c✗ nicht gesetzt"; }
|
||||
private String check(Location a, Location b) { return (a != null && b != null) ? "§a✔ gesetzt" : "§c✗ nicht gesetzt"; }
|
||||
private String yn(boolean b) { return b ? "§aJA" : "§cNEIN"; }
|
||||
private String locStr(Location l) { return fmt(l.getX()) + " / " + fmt(l.getY()) + " / " + fmt(l.getZ()); }
|
||||
private String fmt(double d) { return String.format("%.1f", d); }
|
||||
|
||||
// ── Tab-Completion ───────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
|
||||
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", "debug"));
|
||||
} else if (args.length == 2 && List.of("join","delete","setup","stop","debug","spectate").contains(args[0].toLowerCase())) {
|
||||
list.addAll(plugin.getArenaManager().getArenaNames());
|
||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("setup")) {
|
||||
list.addAll(List.of("lobby","redspawn","bluespawn","ballspawn","center",
|
||||
"redgoalmin","redgoalmax","bluegoalmin","bluegoalmax",
|
||||
"fieldmin","fieldmax","minplayers","maxplayers","duration","info"));
|
||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("top")) {
|
||||
list.addAll(List.of("goals", "wins"));
|
||||
}
|
||||
String input = args[args.length - 1].toLowerCase();
|
||||
list.removeIf(s -> !s.toLowerCase().startsWith(input));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
256
src/main/java/de/fussball/plugin/game/Ball.java
Normal file
256
src/main/java/de/fussball/plugin/game/Ball.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package de.fussball.plugin.game;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.utils.MessageUtil;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.*;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Ball {
|
||||
|
||||
private static final String BALL_TEXTURE_URL =
|
||||
"https://textures.minecraft.net/texture/451f8cfcfb85d77945dc6a3618414093e70436b46d2577b28c727f1329b7265e";
|
||||
|
||||
// Reibung: jeder Tick wird horizontale Geschwindigkeit um diesen Faktor reduziert
|
||||
private static final double FRICTION = 0.82;
|
||||
// Unter dieser Geschwindigkeit → Ball stoppt komplett
|
||||
private static final double MIN_VELOCITY = 0.04;
|
||||
|
||||
private final Game game;
|
||||
private final Fussball plugin;
|
||||
private ArmorStand entity;
|
||||
private final Location spawnLocation;
|
||||
private boolean active = false;
|
||||
|
||||
// ── Torwart-Halten ───────────────────────────────────────────────────────
|
||||
private boolean heldByGoalkeeper = false;
|
||||
private Player holdingPlayer = null;
|
||||
|
||||
public Ball(Game game, Fussball plugin, Location spawnLocation) {
|
||||
this.game = game;
|
||||
this.plugin = plugin;
|
||||
this.spawnLocation = spawnLocation.clone();
|
||||
}
|
||||
|
||||
// ── Config-Helfer ────────────────────────────────────────────────────────
|
||||
|
||||
private double cfg(String path, double def) {
|
||||
return plugin.getConfig().getDouble(path, def);
|
||||
}
|
||||
|
||||
// ── Spawnen ──────────────────────────────────────────────────────────────
|
||||
|
||||
public void spawn() {
|
||||
if (spawnLocation == null || spawnLocation.getWorld() == null) {
|
||||
plugin.getLogger().severe("[Fussball] Ball-Spawn Location ist null!"); return;
|
||||
}
|
||||
if (entity != null && !entity.isDead()) entity.remove();
|
||||
|
||||
spawnLocation.getWorld().loadChunk(spawnLocation.getBlockX() >> 4, spawnLocation.getBlockZ() >> 4);
|
||||
|
||||
entity = spawnLocation.getWorld().spawn(
|
||||
spawnLocation, ArmorStand.class, CreatureSpawnEvent.SpawnReason.CUSTOM, false,
|
||||
stand -> {
|
||||
stand.setVisible(false);
|
||||
stand.setGravity(true);
|
||||
stand.setCollidable(false);
|
||||
stand.setInvulnerable(true);
|
||||
stand.setPersistent(false);
|
||||
stand.setSilent(true);
|
||||
stand.setSmall(true);
|
||||
stand.setArms(false);
|
||||
stand.setBasePlate(false);
|
||||
stand.setCustomName("§e⚽ Fußball");
|
||||
stand.setCustomNameVisible(true);
|
||||
stand.getEquipment().setHelmet(createBallItem());
|
||||
}
|
||||
);
|
||||
|
||||
if (entity == null) {
|
||||
plugin.getLogger().severe("[Fussball] ArmorStand konnte nicht gespawnt werden!"); return;
|
||||
}
|
||||
active = true;
|
||||
}
|
||||
|
||||
private ItemStack createBallItem() {
|
||||
ItemStack skull = new ItemStack(Material.PLAYER_HEAD);
|
||||
SkullMeta meta = (SkullMeta) skull.getItemMeta();
|
||||
if (meta == null) return skull;
|
||||
meta.setDisplayName("§e⚽ Fußball");
|
||||
try {
|
||||
PlayerProfile profile = Bukkit.createPlayerProfile(
|
||||
UUID.nameUUIDFromBytes("FussballBall".getBytes()), "FussballBall");
|
||||
PlayerTextures textures = profile.getTextures();
|
||||
textures.setSkin(new URL(BALL_TEXTURE_URL));
|
||||
profile.setTextures(textures);
|
||||
meta.setOwnerProfile(profile);
|
||||
} catch (MalformedURLException e) {
|
||||
plugin.getLogger().warning("[Fussball] Ball-Textur URL ungültig: " + e.getMessage());
|
||||
}
|
||||
skull.setItemMeta(meta);
|
||||
return skull;
|
||||
}
|
||||
|
||||
// ── Schuss ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Normaler Schuss (Rechtsklick) */
|
||||
public void kick(Player player) {
|
||||
if (entity == null || entity.isDead() || !active) return;
|
||||
Location ballLoc = entity.getLocation();
|
||||
Vector dir = getKickDirection(player);
|
||||
|
||||
double kickVertical = cfg("ball.kick-vertical", 0.3);
|
||||
double kickPower = cfg("ball.kick-power", 1.1);
|
||||
double sprintKickPower = cfg("ball.sprint-kick-power", 1.8);
|
||||
|
||||
dir.setY(kickVertical);
|
||||
dir.multiply(player.isSprinting() ? sprintKickPower : kickPower);
|
||||
applyKick(dir, ballLoc, 1.8f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aufgeladener Schuss (Shift gedrückt halten → loslassen)
|
||||
* power = 0.0 (kurz gehalten) bis 1.0 (voll aufgeladen, ~1.5s)
|
||||
*/
|
||||
public void chargedKick(Player player, double power) {
|
||||
if (entity == null || entity.isDead() || !active) return;
|
||||
Location ballLoc = entity.getLocation();
|
||||
Vector dir = getKickDirection(player);
|
||||
|
||||
double minPower = cfg("ball.charged-min-power", 1.3);
|
||||
double maxPower = cfg("ball.charged-max-power", 3.8);
|
||||
double actualMultiplier = minPower + (maxPower - minPower) * power;
|
||||
|
||||
dir.setY(0.25 + power * 0.25); // mehr Loft bei vollem Schuss
|
||||
dir.multiply(actualMultiplier);
|
||||
float pitch = 1.0f + (float) (power * 0.8f);
|
||||
applyKick(dir, ballLoc, pitch);
|
||||
|
||||
// Feuer-Partikel ab 70% Ladung
|
||||
if (power > 0.7) {
|
||||
ballLoc.getWorld().spawnParticle(Particle.FLAME, ballLoc, 12, 0.2, 0.2, 0.2, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector getKickDirection(Player player) {
|
||||
Vector dir = entity.getLocation().toVector().subtract(player.getLocation().toVector());
|
||||
if (dir.lengthSquared() < 0.0001) dir = player.getLocation().getDirection();
|
||||
dir.normalize();
|
||||
return dir;
|
||||
}
|
||||
|
||||
private void applyKick(Vector dir, Location ballLoc, float soundPitch) {
|
||||
entity.setVelocity(dir);
|
||||
ballLoc.getWorld().playSound(ballLoc, Sound.ENTITY_GENERIC_SMALL_FALL, 1.0f, soundPitch);
|
||||
ballLoc.getWorld().spawnParticle(Particle.POOF, ballLoc, 5, 0.1, 0.1, 0.1, 0.05);
|
||||
}
|
||||
|
||||
// ── Physik ───────────────────────────────────────────────────────────────
|
||||
|
||||
public void applyFriction() {
|
||||
if (heldByGoalkeeper) return; // Kein Reibung wenn der TW den Ball hält
|
||||
if (entity == null || entity.isDead() || !active) return;
|
||||
Vector vel = entity.getVelocity();
|
||||
double hx = vel.getX() * FRICTION;
|
||||
double hz = vel.getZ() * FRICTION;
|
||||
if (Math.abs(hx) < MIN_VELOCITY) hx = 0;
|
||||
if (Math.abs(hz) < MIN_VELOCITY) hz = 0;
|
||||
entity.setVelocity(new Vector(hx, vel.getY(), hz));
|
||||
}
|
||||
|
||||
// ── Torwart-Mechanik ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Torwart greift den Ball – Ball schwebt vor ihm und folgt ihm.
|
||||
* Prüft vorher, ob er im erlaubten Bereich ist.
|
||||
*/
|
||||
public void holdBall(Player goalkeeper) {
|
||||
if (entity == null || entity.isDead() || !active) return;
|
||||
|
||||
// NEUE REGEL: Prüfen, ob Spieler im 2-Block-Radius am Tor ist
|
||||
if (!game.isAllowedToHoldBall(goalkeeper)) {
|
||||
goalkeeper.sendMessage(MessageUtil.error("Du kannst den Ball nur innerhalb von 2 Blöcken am Tor halten!"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.heldByGoalkeeper = true;
|
||||
this.holdingPlayer = goalkeeper;
|
||||
entity.setGravity(false);
|
||||
entity.setVelocity(new Vector(0, 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Muss jeden Tick aufgerufen werden damit der Ball dem TW folgt.
|
||||
*/
|
||||
public void updateHeldPosition() {
|
||||
if (!heldByGoalkeeper || holdingPlayer == null || entity == null || entity.isDead()) return;
|
||||
Location hold = holdingPlayer.getLocation().clone();
|
||||
hold.add(holdingPlayer.getLocation().getDirection().normalize().multiply(0.8));
|
||||
hold.setY(hold.getY() + 1.0);
|
||||
entity.teleport(hold);
|
||||
entity.setVelocity(new Vector(0, 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Torwart lässt den Ball los ohne zu werfen (z.B. fallen lassen).
|
||||
*/
|
||||
public void releaseBall() {
|
||||
this.heldByGoalkeeper = false;
|
||||
this.holdingPlayer = null;
|
||||
if (entity != null && !entity.isDead()) {
|
||||
entity.setGravity(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Torwart wirft den Ball mit gegebener Stärke (0.5 – 2.5).
|
||||
*/
|
||||
public void throwBall(Player goalkeeper, double power) {
|
||||
releaseBall();
|
||||
if (entity == null || entity.isDead()) return;
|
||||
Vector dir = goalkeeper.getLocation().getDirection();
|
||||
dir.setY(dir.getY() + 0.25);
|
||||
dir.multiply(power);
|
||||
entity.setVelocity(dir);
|
||||
Location loc = entity.getLocation();
|
||||
loc.getWorld().playSound(loc, Sound.ENTITY_SNOWBALL_THROW, 1f, 1.1f);
|
||||
loc.getWorld().spawnParticle(Particle.POOF, loc, 5, 0.1, 0.1, 0.1, 0.04);
|
||||
}
|
||||
|
||||
public boolean isHeld() { return heldByGoalkeeper; }
|
||||
public Player getHoldingPlayer() { return holdingPlayer; }
|
||||
|
||||
// ── Util ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public void returnToCenter() {
|
||||
if (entity != null && !entity.isDead()) {
|
||||
entity.teleport(spawnLocation);
|
||||
entity.setVelocity(new Vector(0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
if (entity != null && !entity.isDead()) entity.remove();
|
||||
entity = null;
|
||||
active = false;
|
||||
}
|
||||
|
||||
public ArmorStand getEntity() { return entity; }
|
||||
public boolean isActive() { return active; }
|
||||
public Location getSpawnLocation() { return spawnLocation; }
|
||||
|
||||
public double getDistanceTo(Player player) {
|
||||
if (entity == null || entity.isDead()) return Double.MAX_VALUE;
|
||||
if (!entity.getWorld().equals(player.getWorld())) return Double.MAX_VALUE;
|
||||
return entity.getLocation().distance(player.getLocation());
|
||||
}
|
||||
}
|
||||
1424
src/main/java/de/fussball/plugin/game/Game.java
Normal file
1424
src/main/java/de/fussball/plugin/game/Game.java
Normal file
File diff suppressed because it is too large
Load Diff
133
src/main/java/de/fussball/plugin/game/GameManager.java
Normal file
133
src/main/java/de/fussball/plugin/game/GameManager.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package de.fussball.plugin.game;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.arena.Arena;
|
||||
import de.fussball.plugin.utils.MessageUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class GameManager {
|
||||
|
||||
private final Fussball plugin;
|
||||
private final Map<String, Game> games = new HashMap<>();
|
||||
// Warteschlange pro Arena: FIFO-Queue mit Spieler-UUIDs
|
||||
private final Map<String, Queue<UUID>> queues = new HashMap<>();
|
||||
|
||||
public GameManager(Fussball plugin) { this.plugin = plugin; }
|
||||
|
||||
// ── Spiel-Verwaltung ────────────────────────────────────────────────────
|
||||
|
||||
public Game createGame(Arena arena) {
|
||||
String key = arena.getName().toLowerCase();
|
||||
if (games.containsKey(key)) return games.get(key);
|
||||
Game game = new Game(plugin, arena);
|
||||
games.put(key, game);
|
||||
return game;
|
||||
}
|
||||
|
||||
public Game getGame(String arenaName) { return games.get(arenaName.toLowerCase()); }
|
||||
public void removeGame(String arenaName) {
|
||||
games.remove(arenaName.toLowerCase());
|
||||
// Warteschlange abarbeiten – nächster Spieler darf joinen
|
||||
processQueue(arenaName);
|
||||
}
|
||||
|
||||
public Collection<Game> getAllGames() { return games.values(); }
|
||||
|
||||
public Game getPlayerGame(Player player) {
|
||||
for (Game game : games.values()) if (game.isInGame(player)) return game;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInGame(Player player) { return getPlayerGame(player) != null; }
|
||||
|
||||
/** Gibt zurück ob der Spieler als Zuschauer in einem Spiel sitzt */
|
||||
public Game getSpectatorGame(Player player) {
|
||||
for (Game game : games.values()) if (game.isSpectator(player)) return game;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isInAnyGame(Player player) {
|
||||
return isInGame(player) || getSpectatorGame(player) != null;
|
||||
}
|
||||
|
||||
public void stopAllGames() {
|
||||
for (Game game : new ArrayList<>(games.values())) game.endGame(null);
|
||||
games.clear();
|
||||
queues.clear();
|
||||
}
|
||||
|
||||
// ── Warteschlange ───────────────────────────────────────────────────────
|
||||
|
||||
/** Spieler zur Warteschlange einer Arena hinzufügen */
|
||||
public void addToQueue(String arenaName, Player player) {
|
||||
String key = arenaName.toLowerCase();
|
||||
queues.computeIfAbsent(key, k -> new LinkedList<>());
|
||||
Queue<UUID> queue = queues.get(key);
|
||||
if (queue.contains(player.getUniqueId())) {
|
||||
player.sendMessage(MessageUtil.warn("Du bist bereits in der Warteschlange für §e" + arenaName + "§e!"));
|
||||
return;
|
||||
}
|
||||
queue.add(player.getUniqueId());
|
||||
int pos = getQueuePosition(arenaName, player);
|
||||
player.sendMessage(MessageUtil.info("Du bist in der Warteschlange für §e" + arenaName + " §7(Position §e" + pos + "§7)"));
|
||||
}
|
||||
|
||||
/** Spieler aus der Warteschlange entfernen */
|
||||
public void removeFromQueue(String arenaName, Player player) {
|
||||
Queue<UUID> queue = queues.get(arenaName.toLowerCase());
|
||||
if (queue != null) queue.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/** Entfernt einen Spieler aus ALLEN Warteschlangen */
|
||||
public void removeFromAllQueues(Player player) {
|
||||
for (Queue<UUID> q : queues.values()) q.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public int getQueuePosition(String arenaName, Player player) {
|
||||
Queue<UUID> queue = queues.get(arenaName.toLowerCase());
|
||||
if (queue == null) return -1;
|
||||
int pos = 1;
|
||||
for (UUID uuid : queue) {
|
||||
if (uuid.equals(player.getUniqueId())) return pos;
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getQueueSize(String arenaName) {
|
||||
Queue<UUID> queue = queues.get(arenaName.toLowerCase());
|
||||
return queue == null ? 0 : queue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wenn ein Spiel endet oder Platz frei wird → nächsten Spieler aus der
|
||||
* Warteschlange in das (neue) Spiel einladen.
|
||||
*/
|
||||
private void processQueue(String arenaName) {
|
||||
String key = arenaName.toLowerCase();
|
||||
Queue<UUID> queue = queues.get(key);
|
||||
if (queue == null || queue.isEmpty()) return;
|
||||
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null || !arena.isSetupComplete()) return;
|
||||
|
||||
// Warte kurz, bis das alte Spiel vollständig aufgeräumt ist
|
||||
org.bukkit.scheduler.BukkitRunnable task = new org.bukkit.scheduler.BukkitRunnable() {
|
||||
public void run() {
|
||||
Queue<UUID> q = queues.get(key);
|
||||
if (q == null || q.isEmpty()) return;
|
||||
UUID next = q.poll();
|
||||
if (next == null) return;
|
||||
Player p = Bukkit.getPlayer(next);
|
||||
if (p == null || !p.isOnline()) { run(); return; } // überspringe Offline-Spieler
|
||||
if (isInAnyGame(p)) return;
|
||||
p.sendMessage(MessageUtil.success("§e⚽ Dein Platz in §e" + arenaName + " §aist frei! Du wirst hinzugefügt..."));
|
||||
createGame(arena).addPlayer(p);
|
||||
}
|
||||
};
|
||||
task.runTaskLater(plugin, 120L); // 6s nach Spielende
|
||||
}
|
||||
}
|
||||
12
src/main/java/de/fussball/plugin/game/GameState.java
Normal file
12
src/main/java/de/fussball/plugin/game/GameState.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package de.fussball.plugin.game;
|
||||
|
||||
public enum GameState {
|
||||
WAITING, // Warte auf Spieler
|
||||
STARTING, // Countdown läuft
|
||||
RUNNING, // Spiel läuft
|
||||
GOAL, // Tor-Pause
|
||||
HALFTIME, // Halbzeit-Pause
|
||||
OVERTIME, // Verlängerung
|
||||
PENALTY, // Elfmeterschießen
|
||||
ENDING // Spiel beendet
|
||||
}
|
||||
22
src/main/java/de/fussball/plugin/game/Team.java
Normal file
22
src/main/java/de/fussball/plugin/game/Team.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package de.fussball.plugin.game;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
public enum Team {
|
||||
RED("Rot", ChatColor.RED, "§c"),
|
||||
BLUE("Blau", ChatColor.BLUE, "§9");
|
||||
|
||||
private final String displayName;
|
||||
private final ChatColor color;
|
||||
private final String colorCode;
|
||||
|
||||
Team(String displayName, ChatColor color, String colorCode) {
|
||||
this.displayName = displayName;
|
||||
this.color = color;
|
||||
this.colorCode = colorCode;
|
||||
}
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public ChatColor getColor() { return color; }
|
||||
public String getColorCode() { return colorCode; }
|
||||
public Team getOpponent() { return this == RED ? BLUE : RED; }
|
||||
}
|
||||
222
src/main/java/de/fussball/plugin/listeners/BallListener.java
Normal file
222
src/main/java/de/fussball/plugin/listeners/BallListener.java
Normal file
@@ -0,0 +1,222 @@
|
||||
package de.fussball.plugin.listeners;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.game.Ball;
|
||||
import de.fussball.plugin.game.Game;
|
||||
import de.fussball.plugin.game.GameState;
|
||||
import de.fussball.plugin.game.Team;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import org.bukkit.entity.*;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||||
import org.bukkit.event.player.PlayerToggleSneakEvent;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Verwaltet Ball-Interaktionen:
|
||||
* • Normaler Schuss – Rechtsklick auf Ball
|
||||
* • Aufgeladener Schuss – Shift halten → loslassen
|
||||
* • Torwart – Rechtsklick = halten, nochmal Rechtsklick/Shift = werfen
|
||||
* • Foul-Erkennung – Spieler trifft gegnerischen Spieler
|
||||
*/
|
||||
public class BallListener implements Listener {
|
||||
|
||||
private final Fussball plugin;
|
||||
// UUID → Timestamp Lade-Beginn (ms)
|
||||
private final Map<UUID, Long> chargeMap = new HashMap<>();
|
||||
|
||||
public BallListener(Fussball plugin) { this.plugin = plugin; }
|
||||
|
||||
// ── Rechtsklick auf Ball ─────────────────────────────────────────────────
|
||||
|
||||
@EventHandler
|
||||
public void onInteract(PlayerInteractAtEntityEvent event) {
|
||||
if (!(event.getRightClicked() instanceof ArmorStand stand)) return;
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
Ball ball = game.getBall();
|
||||
if (ball == null || !stand.equals(ball.getEntity())) return;
|
||||
event.setCancelled(true);
|
||||
if (game.getState() != GameState.RUNNING && game.getState() != GameState.OVERTIME) return;
|
||||
|
||||
// ── Torwart hält Ball bereits → werfen ──────────────────────────────
|
||||
if (ball.isHeld() && player.equals(ball.getHoldingPlayer())) {
|
||||
double power = plugin.getConfig().getDouble("gameplay.goalkeeper-throw-power", 1.8);
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.throwBall(player, power);
|
||||
game.clearThrowIn();
|
||||
game.broadcastAll(plugin.getConfig().getString("messages.goalkeeper-throw",
|
||||
"§6TW §f{player} §7wirft den Ball!").replace("{player}", player.getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Torwart greift Ball (noch nicht haltend) ────────────────────────
|
||||
if (game.isGoalkeeper(player) && !player.isSneaking()) {
|
||||
if (!plugin.getConfig().getBoolean("gameplay.foul-detection-enabled", true)) {
|
||||
// Falls Torwart-Mechanik in Config deaktiviert → normaler Schuss
|
||||
} else if (ball.getDistanceTo(player) <= plugin.getConfig().getDouble("gameplay.goalkeeper-hold-range", 2.5)) {
|
||||
if (game.isInOwnHalf(player)) {
|
||||
if (game.getThrowInTeam() == null || game.getThrowInTeam() == game.getTeam(player)) {
|
||||
game.clearThrowIn();
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.holdBall(player);
|
||||
game.broadcastAll(plugin.getConfig().getString("messages.goalkeeper-hold",
|
||||
"§6TW §f{player} §7hält den Ball!").replace("{player}", player.getName()));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
player.sendMessage(plugin.getConfig().getString("messages.goalkeeper-no-hold",
|
||||
"§cDu kannst den Ball nur in deiner eigenen Hälfte halten!"));
|
||||
// Fällt durch zum normalen Schuss
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Normaler Schuss (kein Shift) ────────────────────────────────────
|
||||
if (player.isSneaking()) return;
|
||||
if (game.getThrowInTeam() != null && game.getThrowInTeam() != game.getTeam(player)) {
|
||||
player.sendMessage("§cDu bist nicht dran! Warte auf den Einwurf des anderen Teams.");
|
||||
return;
|
||||
}
|
||||
game.clearThrowIn();
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.kick(player);
|
||||
}
|
||||
|
||||
// ── Linksklick / Schaden am Ball ────────────────────────────────────────
|
||||
|
||||
@EventHandler
|
||||
public void onDamage(EntityDamageByEntityEvent event) {
|
||||
if (!(event.getEntity() instanceof ArmorStand)) return;
|
||||
if (!(event.getDamager() instanceof Player player)) return;
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
Ball ball = game.getBall();
|
||||
if (ball == null || !ball.getEntity().equals(event.getEntity())) return;
|
||||
event.setCancelled(true);
|
||||
if (game.getState() != GameState.RUNNING && game.getState() != GameState.OVERTIME) return;
|
||||
if (player.isSneaking()) return;
|
||||
if (game.getThrowInTeam() != null && game.getThrowInTeam() != game.getTeam(player)) {
|
||||
player.sendMessage("§cDu bist nicht dran!");
|
||||
return;
|
||||
}
|
||||
game.clearThrowIn();
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.kick(player);
|
||||
}
|
||||
|
||||
// ── Foul-Erkennung (Spieler trifft gegnerischen Spieler) ─────────────────
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onPlayerHitPlayer(EntityDamageByEntityEvent event) {
|
||||
if (!(event.getEntity() instanceof Player victim)) return;
|
||||
Player damager;
|
||||
if (event.getDamager() instanceof Player p) {
|
||||
damager = p;
|
||||
} else if (event.getDamager() instanceof Projectile proj && proj.getShooter() instanceof Player p) {
|
||||
damager = p;
|
||||
} else return;
|
||||
|
||||
if (!plugin.getConfig().getBoolean("gameplay.foul-detection-enabled", true)) return;
|
||||
|
||||
Game game = plugin.getGameManager().getPlayerGame(damager);
|
||||
if (game == null || !game.isInGame(victim)) return;
|
||||
if (game.getState() != GameState.RUNNING && game.getState() != GameState.OVERTIME) return;
|
||||
|
||||
Team damagerTeam = game.getTeam(damager);
|
||||
Team victimTeam = game.getTeam(victim);
|
||||
if (damagerTeam == null || victimTeam == null || damagerTeam == victimTeam) return;
|
||||
|
||||
// Schaden canceln (kein PvP im Fußball)
|
||||
event.setCancelled(true);
|
||||
|
||||
// Foul registrieren
|
||||
double damage = event.getFinalDamage();
|
||||
boolean directRedCard = damage >= 8.0; // Sehr harter Schlag → direkt Rot
|
||||
game.handleFoul(damager, victim, victim.getLocation(), directRedCard);
|
||||
}
|
||||
|
||||
// ── Aufgeladener Schuss (Shift-System) ──────────────────────────────────
|
||||
|
||||
@EventHandler
|
||||
public void onSneak(PlayerToggleSneakEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
if (game.getState() != GameState.RUNNING && game.getState() != GameState.OVERTIME) {
|
||||
chargeMap.remove(player.getUniqueId()); return;
|
||||
}
|
||||
|
||||
Ball ball = game.getBall();
|
||||
|
||||
// ── Torwart hält Ball → Shift loslassen = werfen ────────────────────
|
||||
if (!event.isSneaking() && ball != null && ball.isHeld() && player.equals(ball.getHoldingPlayer())) {
|
||||
double power = plugin.getConfig().getDouble("gameplay.goalkeeper-throw-power", 1.8);
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.throwBall(player, power);
|
||||
game.clearThrowIn();
|
||||
game.broadcastAll(plugin.getConfig().getString("messages.goalkeeper-throw",
|
||||
"§6TW §f{player} §7wirft den Ball!").replace("{player}", player.getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isSneaking()) {
|
||||
// ── Shift gedrückt: Laden beginnen ──────────────────────────────
|
||||
if (ball == null || ball.getDistanceTo(player) > 2.5) return;
|
||||
chargeMap.put(player.getUniqueId(), System.currentTimeMillis());
|
||||
startChargeDisplay(player, game);
|
||||
} else {
|
||||
// ── Shift losgelassen: Schuss abfeuern ──────────────────────────
|
||||
Long startTime = chargeMap.remove(player.getUniqueId());
|
||||
if (startTime == null) return;
|
||||
if (ball == null || ball.getDistanceTo(player) > 2.5) return;
|
||||
if (game.getThrowInTeam() != null && game.getThrowInTeam() != game.getTeam(player)) {
|
||||
player.sendMessage("§cDu bist nicht dran!");
|
||||
return;
|
||||
}
|
||||
game.clearThrowIn();
|
||||
long held = System.currentTimeMillis() - startTime;
|
||||
double power = Math.min(held / 1500.0, 1.0);
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.chargedKick(player, power);
|
||||
}
|
||||
}
|
||||
|
||||
private void startChargeDisplay(Player player, Game game) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!player.isSneaking() || !chargeMap.containsKey(player.getUniqueId())) { cancel(); return; }
|
||||
if (game.getState() != GameState.RUNNING && game.getState() != GameState.OVERTIME) {
|
||||
chargeMap.remove(player.getUniqueId()); cancel(); return;
|
||||
}
|
||||
Ball ball = game.getBall();
|
||||
if (ball == null || ball.getDistanceTo(player) > 2.5) {
|
||||
chargeMap.remove(player.getUniqueId()); cancel(); return;
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - chargeMap.get(player.getUniqueId());
|
||||
double power = Math.min(elapsed / 1500.0, 1.0);
|
||||
int filled = (int) Math.round(power * 10);
|
||||
String color = power < 0.4 ? "§a" : power < 0.8 ? "§e" : "§c";
|
||||
String bar = color + "█".repeat(filled) + "§8" + "█".repeat(10 - filled);
|
||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
|
||||
TextComponent.fromLegacyText("§e⚽ Schuss-Power: " + bar + " §f" + (int)(power*100) + "%"));
|
||||
if (power >= 1.0) {
|
||||
chargeMap.remove(player.getUniqueId());
|
||||
game.setLastKicker(player.getUniqueId());
|
||||
ball.chargedKick(player, 1.0);
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}.runTaskTimer(plugin, 1L, 2L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.fussball.plugin.listeners;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
|
||||
public class BlockListener implements Listener {
|
||||
private final Fussball plugin;
|
||||
public BlockListener(Fussball plugin) { this.plugin = plugin; }
|
||||
|
||||
@EventHandler
|
||||
public void onBreak(BlockBreakEvent event) {
|
||||
if (plugin.getGameManager().isInGame(event.getPlayer())) event.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlace(BlockPlaceEvent event) {
|
||||
if (plugin.getGameManager().isInGame(event.getPlayer())) event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package de.fussball.plugin.listeners;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.game.Game;
|
||||
import de.fussball.plugin.game.Team;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final Fussball plugin;
|
||||
public PlayerListener(Fussball plugin) { this.plugin = plugin; }
|
||||
|
||||
/** Spieler disconnected → aus dem Spiel entfernen */
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
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);
|
||||
}
|
||||
|
||||
/** Spieler stirbt → heilen und zurückteleportieren statt Tod */
|
||||
@EventHandler
|
||||
public void onDamage(EntityDamageEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) return;
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
if (player.getHealth() - event.getFinalDamage() <= 0) {
|
||||
event.setCancelled(true);
|
||||
player.setHealth(20.0);
|
||||
Team team = game.getTeam(player);
|
||||
if (team == Team.RED) player.teleport(game.getArena().getRedSpawn());
|
||||
else if (team == Team.BLUE) player.teleport(game.getArena().getBlueSpawn());
|
||||
}
|
||||
}
|
||||
|
||||
/** Hunger deaktivieren */
|
||||
@EventHandler
|
||||
public void onFood(FoodLevelChangeEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) return;
|
||||
if (plugin.getGameManager().isInAnyGame(player)) event.setCancelled(true);
|
||||
}
|
||||
|
||||
/** Items droppen verhindern */
|
||||
@EventHandler
|
||||
public void onDrop(PlayerDropItemEvent event) {
|
||||
if (plugin.getGameManager().isInAnyGame(event.getPlayer())) event.setCancelled(true);
|
||||
}
|
||||
|
||||
/** Items aufheben verhindern */
|
||||
@EventHandler
|
||||
public void onPickup(EntityPickupItemEvent event) {
|
||||
if (!(event.getEntity() instanceof Player player)) return;
|
||||
if (plugin.getGameManager().isInAnyGame(player)) event.setCancelled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onChat(AsyncPlayerChatEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||
if (game == null) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
String message = event.getMessage();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
216
src/main/java/de/fussball/plugin/listeners/SignListener.java
Normal file
216
src/main/java/de/fussball/plugin/listeners/SignListener.java
Normal file
@@ -0,0 +1,216 @@
|
||||
package de.fussball.plugin.listeners;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.arena.Arena;
|
||||
import de.fussball.plugin.game.Game;
|
||||
import de.fussball.plugin.utils.MessageUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Fußball-Join-Schilder
|
||||
*
|
||||
* Format beim Beschriften (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.
|
||||
*/
|
||||
public class SignListener implements Listener {
|
||||
|
||||
private static final String TAG = "[Fussball]";
|
||||
private static final String TAG_FORMATTED = "§8[§e⚽§8]";
|
||||
|
||||
private final Fussball plugin;
|
||||
|
||||
// Location → ArenaName
|
||||
private final Map<String, String> signs = new HashMap<>(); // key = "world;x;y;z"
|
||||
|
||||
private final File signFile;
|
||||
private FileConfiguration signConfig;
|
||||
|
||||
public SignListener(Fussball plugin) {
|
||||
this.plugin = plugin;
|
||||
this.signFile = new File(plugin.getDataFolder(), "signs.yml");
|
||||
loadSigns();
|
||||
}
|
||||
|
||||
// ── Persistenz ──────────────────────────────────────────────────────────
|
||||
|
||||
private void loadSigns() {
|
||||
if (!signFile.exists()) {
|
||||
try { signFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
signConfig = YamlConfiguration.loadConfiguration(signFile);
|
||||
signs.clear();
|
||||
if (signConfig.contains("signs")) {
|
||||
for (String key : signConfig.getConfigurationSection("signs").getKeys(false)) {
|
||||
String arenaName = signConfig.getString("signs." + key);
|
||||
signs.put(key, arenaName);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("[Fussball] " + signs.size() + " Schilder geladen.");
|
||||
}
|
||||
|
||||
private void saveSigns() {
|
||||
signConfig.set("signs", null);
|
||||
for (Map.Entry<String, String> e : signs.entrySet()) {
|
||||
signConfig.set("signs." + e.getKey(), e.getValue());
|
||||
}
|
||||
try { signConfig.save(signFile); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
private String locKey(Location l) {
|
||||
return l.getWorld().getName() + ";" + l.getBlockX() + ";" + l.getBlockY() + ";" + l.getBlockZ();
|
||||
}
|
||||
|
||||
// ── 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;
|
||||
|
||||
Player player = event.getPlayer();
|
||||
if (!player.hasPermission("fussball.admin")) {
|
||||
player.sendMessage(MessageUtil.error("Keine Berechtigung für Fußball-Schilder!"));
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
String arenaName = event.getLine(1);
|
||||
if (arenaName == null || arenaName.isEmpty()) {
|
||||
player.sendMessage(MessageUtil.error("Zeile 2 muss den Arena-Namen enthalten!"));
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null) {
|
||||
player.sendMessage(MessageUtil.error("Arena §e" + arenaName + " §cnicht gefunden!"));
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
event.setLine(0, TAG_FORMATTED);
|
||||
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());
|
||||
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;
|
||||
|
||||
String key = locKey(block.getLocation());
|
||||
if (!signs.containsKey(key)) return;
|
||||
|
||||
event.setCancelled(true);
|
||||
Player player = event.getPlayer();
|
||||
String arenaName = signs.get(key);
|
||||
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null) {
|
||||
player.sendMessage(MessageUtil.error("Arena nicht gefunden!"));
|
||||
signs.remove(key);
|
||||
saveSigns();
|
||||
return;
|
||||
}
|
||||
if (!arena.isSetupComplete()) {
|
||||
player.sendMessage(MessageUtil.error("Diese Arena ist noch nicht fertig eingerichtet!"));
|
||||
return;
|
||||
}
|
||||
if (plugin.getGameManager().isInGame(player)) {
|
||||
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());
|
||||
if (signs.containsKey(key)) {
|
||||
signs.remove(key);
|
||||
saveSigns();
|
||||
event.getPlayer().sendMessage(MessageUtil.info("Fußball-Schild entfernt."));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ö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;
|
||||
Location loc = keyToLocation(entry.getKey());
|
||||
if (loc == null) continue;
|
||||
Block block = loc.getBlock();
|
||||
if (!(block.getState() instanceof Sign sign)) continue;
|
||||
|
||||
Arena arena = plugin.getArenaManager().getArena(arenaName);
|
||||
if (arena == null) continue;
|
||||
|
||||
sign.setLine(0, TAG_FORMATTED);
|
||||
sign.setLine(1, "§e" + arena.getName());
|
||||
sign.setLine(2, buildStatusLine(arena));
|
||||
sign.setLine(3, "§7Klick zum Joinen");
|
||||
sign.update();
|
||||
}
|
||||
}
|
||||
|
||||
private String buildStatusLine(Arena arena) {
|
||||
Game game = plugin.getGameManager().getGame(arena.getName());
|
||||
if (game == null) return "§a● §70/" + arena.getMaxPlayers();
|
||||
int players = game.getAllPlayers().size();
|
||||
String dot = switch (game.getState()) {
|
||||
case WAITING -> "§a●";
|
||||
case STARTING -> "§e●";
|
||||
case RUNNING, GOAL -> "§c●";
|
||||
case HALFTIME, OVERTIME,
|
||||
PENALTY -> "§6●";
|
||||
case ENDING -> "§7●";
|
||||
};
|
||||
return dot + " §7" + players + "/" + arena.getMaxPlayers();
|
||||
}
|
||||
|
||||
private Location keyToLocation(String key) {
|
||||
try {
|
||||
String[] p = key.split(";");
|
||||
org.bukkit.World world = Bukkit.getWorld(p[0]);
|
||||
if (world == null) return null;
|
||||
return new Location(world, Integer.parseInt(p[1]), Integer.parseInt(p[2]), Integer.parseInt(p[3]));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.fussball.plugin.placeholders;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
import de.fussball.plugin.stats.StatsManager;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* PlaceholderAPI-Erweiterung für das Fußball-Plugin.
|
||||
*
|
||||
* Verfügbare Platzhalter:
|
||||
* %fussball_goals% - Tore des Spielers (gesamt)
|
||||
* %fussball_kicks% - Schüsse des Spielers (gesamt)
|
||||
* %fussball_wins% - Siege des Spielers
|
||||
* %fussball_losses% - Niederlagen des Spielers
|
||||
* %fussball_draws% - Unentschieden des Spielers
|
||||
* %fussball_games% - Gespielte Spiele des Spielers
|
||||
* %fussball_winrate% - Siegquote in Prozent (z.B. "67.5")
|
||||
* %fussball_ingame% - "true" / "false" – ob Spieler gerade im Spiel ist
|
||||
* %fussball_arena% - Name der aktuellen Arena (oder "–")
|
||||
* %fussball_score% - Aktueller Spielstand (z.B. "2 : 1") oder "–"
|
||||
*/
|
||||
public class FussballPlaceholders extends PlaceholderExpansion {
|
||||
|
||||
private final Fussball plugin;
|
||||
|
||||
public FussballPlaceholders(Fussball plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() { return "fussball"; }
|
||||
|
||||
@Override
|
||||
public String getAuthor() { return "M_Viper"; }
|
||||
|
||||
@Override
|
||||
public String getVersion() { return plugin.getDescription().getVersion(); }
|
||||
|
||||
@Override
|
||||
public boolean persist() { return true; }
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, String identifier) {
|
||||
if (player == null) return "";
|
||||
StatsManager stats = plugin.getStatsManager();
|
||||
StatsManager.PlayerStats s = stats.getStats(player.getUniqueId());
|
||||
|
||||
return switch (identifier.toLowerCase()) {
|
||||
case "goals" -> String.valueOf(s.goals);
|
||||
case "kicks" -> String.valueOf(s.kicks);
|
||||
case "wins" -> String.valueOf(s.wins);
|
||||
case "losses" -> String.valueOf(s.losses);
|
||||
case "draws" -> String.valueOf(s.draws);
|
||||
case "games" -> String.valueOf(s.games);
|
||||
case "winrate" -> String.format("%.1f", s.getWinRate());
|
||||
case "ingame" -> String.valueOf(plugin.getGameManager().isInGame(player));
|
||||
case "arena" -> {
|
||||
var game = plugin.getGameManager().getPlayerGame(player);
|
||||
yield game != null ? game.getArena().getName() : "–";
|
||||
}
|
||||
case "score" -> {
|
||||
var game = plugin.getGameManager().getPlayerGame(player);
|
||||
yield game != null ? game.getRedScore() + " : " + game.getBlueScore() : "–";
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package de.fussball.plugin.scoreboard;
|
||||
|
||||
import de.fussball.plugin.game.Game;
|
||||
import de.fussball.plugin.game.GameState;
|
||||
import de.fussball.plugin.game.Team;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class FussballScoreboard {
|
||||
|
||||
private final Game game;
|
||||
private final Map<UUID, Scoreboard> boards = new HashMap<>();
|
||||
|
||||
public FussballScoreboard(Game game) { this.game = game; }
|
||||
|
||||
// ── Scoreboard geben / entfernen ─────────────────────────────────────────
|
||||
|
||||
public void give(Player player) {
|
||||
Scoreboard board = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
Objective obj = board.registerNewObjective("fussball", "dummy", "§e§l⚽ FUSSBALL");
|
||||
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
|
||||
// Team-Namensschilder registrieren
|
||||
registerTeamTags(board);
|
||||
boards.put(player.getUniqueId(), board);
|
||||
player.setScoreboard(board);
|
||||
update(player, board);
|
||||
}
|
||||
|
||||
public void remove(Player player) {
|
||||
boards.remove(player.getUniqueId());
|
||||
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
|
||||
}
|
||||
|
||||
public void updateAll() {
|
||||
for (UUID uuid : game.getAllPlayers()) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p != null && boards.containsKey(uuid)) {
|
||||
updateTeamTags(boards.get(uuid)); // Team-Tags aktuell halten
|
||||
update(p, boards.get(uuid));
|
||||
}
|
||||
}
|
||||
// Auch Zuschauer-Scoreboards updaten
|
||||
for (UUID uuid : game.getSpectators()) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p != null && boards.containsKey(uuid)) update(p, boards.get(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Anzeige ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void update(Player player, Scoreboard board) {
|
||||
Objective obj = board.getObjective("fussball");
|
||||
if (obj == null) return;
|
||||
for (String entry : board.getEntries()) board.resetScores(entry);
|
||||
|
||||
int l = 14;
|
||||
set(obj, "§r", l--);
|
||||
set(obj, "§c▶ ROT §f" + game.getRedScore() + " §7: §f" + game.getBlueScore() + " §9BLAU ◀", l--);
|
||||
set(obj, "§r§r", l--);
|
||||
|
||||
// Zeit / Status-Zeile
|
||||
switch (game.getState()) {
|
||||
case RUNNING, GOAL -> {
|
||||
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
||||
String half = game.isSecondHalf() ? "§72. HZ" : "§71. HZ";
|
||||
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " " + half, l--);
|
||||
}
|
||||
case HALFTIME -> set(obj, "§6⏸ HALBZEIT", l--);
|
||||
case OVERTIME -> {
|
||||
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
||||
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " §6VL", l--);
|
||||
}
|
||||
case PENALTY -> set(obj, "§c⚽ ELFMETER R" + game.getPenaltyRedGoals() + ":B" + game.getPenaltyBlueGoals(), l--);
|
||||
case STARTING -> set(obj, "§e⏱ §6Startet gleich...", l--);
|
||||
default -> set(obj, "§7⏳ Warte auf Spieler...", l--);
|
||||
}
|
||||
|
||||
set(obj, "§r§r§r", l--);
|
||||
|
||||
// Team
|
||||
Team team = game.getTeam(player);
|
||||
if (team != null) {
|
||||
String c = team == Team.RED ? "§c" : "§9";
|
||||
set(obj, "§7Dein Team: " + c + team.getDisplayName(), l--);
|
||||
} else if (game.isSpectator(player)) {
|
||||
set(obj, "§7Rolle: §8Zuschauer", l--);
|
||||
}
|
||||
|
||||
set(obj, "§7Spieler: §f" + game.getAllPlayers().size() + "/" + game.getArena().getMaxPlayers(), l--);
|
||||
set(obj, "§r§r§r§r", l--);
|
||||
set(obj, "§7Arena: §e" + game.getArena().getName(), l--);
|
||||
set(obj, "§r§r§r§r§r", l--);
|
||||
set(obj, "§6§lFußball-Plugin", l);
|
||||
}
|
||||
|
||||
// ── Team-Namensschilder (farbige Namen über dem Kopf) ────────────────────
|
||||
|
||||
/** Registriert die Scoreboard-Teams auf dem übergebenen Board (1x bei give()) */
|
||||
private void registerTeamTags(Scoreboard board) {
|
||||
if (board.getTeam("fb_red") == null) {
|
||||
org.bukkit.scoreboard.Team red = board.registerNewTeam("fb_red");
|
||||
red.setPrefix("§c"); red.setColor(org.bukkit.ChatColor.RED);
|
||||
}
|
||||
if (board.getTeam("fb_blue") == null) {
|
||||
org.bukkit.scoreboard.Team blue = board.registerNewTeam("fb_blue");
|
||||
blue.setPrefix("§9"); blue.setColor(org.bukkit.ChatColor.BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
/** Aktualisiert die Spieler-Einträge in den Teams (wenn jemand joint/geht) */
|
||||
public void updateTeamTags(Scoreboard board) {
|
||||
org.bukkit.scoreboard.Team red = board.getTeam("fb_red");
|
||||
org.bukkit.scoreboard.Team blue = board.getTeam("fb_blue");
|
||||
if (red == null || blue == null) return;
|
||||
|
||||
// Alle vorherigen Einträge leeren
|
||||
for (String e : new HashSet<>(red.getEntries())) red.removeEntry(e);
|
||||
for (String e : new HashSet<>(blue.getEntries())) blue.removeEntry(e);
|
||||
|
||||
// Neu befüllen
|
||||
for (UUID uuid : game.getRedTeam()) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p != null) red.addEntry(p.getName());
|
||||
}
|
||||
for (UUID uuid : game.getBlueTeam()) {
|
||||
Player p = Bukkit.getPlayer(uuid);
|
||||
if (p != null) blue.addEntry(p.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void set(Objective obj, String text, int value) {
|
||||
obj.getScore(text).setScore(value);
|
||||
}
|
||||
}
|
||||
31
src/main/java/de/fussball/plugin/utils/MessageUtil.java
Normal file
31
src/main/java/de/fussball/plugin/utils/MessageUtil.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package de.fussball.plugin.utils;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
public class MessageUtil {
|
||||
|
||||
public static final String PREFIX = ChatColor.DARK_GRAY + "[" + ChatColor.YELLOW + "⚽" + ChatColor.DARK_GRAY + "] " + ChatColor.GRAY;
|
||||
public static final String ERROR_PREFIX = ChatColor.DARK_GRAY + "[" + ChatColor.RED + "✖" + ChatColor.DARK_GRAY + "] " + ChatColor.RED;
|
||||
public static final String SUCCESS_PREFIX = ChatColor.DARK_GRAY + "[" + ChatColor.GREEN + "✔" + ChatColor.DARK_GRAY + "] " + ChatColor.GREEN;
|
||||
public static final String WARN_PREFIX = ChatColor.DARK_GRAY + "[" + ChatColor.YELLOW + "!" + ChatColor.DARK_GRAY + "] " + ChatColor.YELLOW;
|
||||
|
||||
public static String error(String msg) {
|
||||
return ERROR_PREFIX + msg;
|
||||
}
|
||||
|
||||
public static String success(String msg) {
|
||||
return SUCCESS_PREFIX + msg;
|
||||
}
|
||||
|
||||
public static String warn(String msg) {
|
||||
return WARN_PREFIX + msg;
|
||||
}
|
||||
|
||||
public static String info(String msg) {
|
||||
return PREFIX + msg;
|
||||
}
|
||||
|
||||
public static String header(String title) {
|
||||
return ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "-----------------" + ChatColor.RESET + " " + ChatColor.YELLOW + title + ChatColor.GRAY + " " + ChatColor.STRIKETHROUGH + "-----------------";
|
||||
}
|
||||
}
|
||||
31
src/main/java/de/fussball/plugin/utils/Messages.java
Normal file
31
src/main/java/de/fussball/plugin/utils/Messages.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package de.fussball.plugin.utils;
|
||||
|
||||
import de.fussball.plugin.Fussball;
|
||||
|
||||
/**
|
||||
* Liest alle Broadcast-Texte aus config.yml.
|
||||
* Platzhalter: {player}, {team}, {score}, {time}, {reason}, {n}
|
||||
*/
|
||||
public class Messages {
|
||||
|
||||
private static Fussball plugin;
|
||||
|
||||
public static void init(Fussball p) { plugin = p; }
|
||||
|
||||
/** Liest eine Nachricht aus config.yml → messages.<key> */
|
||||
public static String get(String key) {
|
||||
if (plugin == null) return "§c[MSG:" + key + "]";
|
||||
return plugin.getConfig().getString("messages." + key, "§c[MSG:" + key + "]");
|
||||
}
|
||||
|
||||
/** Liest Nachricht und ersetzt Platzhalter: replace("player", "Hans", "team", "Rot", ...) */
|
||||
public static String get(String key, String... pairs) {
|
||||
String msg = get(key);
|
||||
for (int i = 0; i + 1 < pairs.length; i += 2) {
|
||||
msg = msg.replace("{" + pairs[i] + "}", pairs[i + 1]);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static String prefix() { return get("prefix"); }
|
||||
}
|
||||
Reference in New Issue
Block a user