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"); }
|
||||||
|
}
|
||||||
119
src/main/resources/config.yml
Normal file
119
src/main/resources/config.yml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# Fußball-Plugin Konfiguration
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
min-players: 2
|
||||||
|
max-players: 10
|
||||||
|
game-duration: 300 # Sekunden (5 Min × 2 Halbzeiten)
|
||||||
|
|
||||||
|
ball:
|
||||||
|
kick-power: 1.1
|
||||||
|
sprint-kick-power: 1.8
|
||||||
|
kick-vertical: 0.3
|
||||||
|
charged-min-power: 1.3
|
||||||
|
charged-max-power: 3.8
|
||||||
|
|
||||||
|
gameplay:
|
||||||
|
offside-enabled: true # Abseits an/aus
|
||||||
|
foul-detection-enabled: true # Foul-Erkennung an/aus
|
||||||
|
freekick-distance: 5.0 # Mindestabstand Gegner beim Freistoß (Blöcke)
|
||||||
|
freekick-duration: 600 # Ticks bis Freistoß automatisch freigegeben wird
|
||||||
|
goalkeeper-hold-range: 2.5 # Wie nah der TW am Ball sein muss um ihn zu halten
|
||||||
|
goalkeeper-throw-power: 1.8 # Wurfstärke des TW
|
||||||
|
out-of-bounds-tolerance: 2.0 # Toleranz außerhalb Spielfeld (Blöcke)
|
||||||
|
out-of-bounds-countdown: 5 # Sekunden bis Disqualifikation
|
||||||
|
|
||||||
|
# ── Nachrichten (alle editierbar) ──────────────────
|
||||||
|
# Verfügbare Platzhalter je nach Kontext:
|
||||||
|
# {player} = Spielername
|
||||||
|
# {team} = Teamname
|
||||||
|
# {score} = Spielstand (z.B. "2 : 1")
|
||||||
|
# {time} = Minute/Zeit
|
||||||
|
# {reason} = Grund
|
||||||
|
# {n} = Zahl
|
||||||
|
|
||||||
|
messages:
|
||||||
|
prefix: "§8[§e⚽§8] §r"
|
||||||
|
|
||||||
|
# Spielbeginn / Halbzeit / Ende
|
||||||
|
game-start: "§a§lANPFIFF! ⚽"
|
||||||
|
game-start-sub: "§7Viel Erfolg!"
|
||||||
|
second-half: "§a§l▶ 2. HALBZEIT! ◀"
|
||||||
|
second-half-sub: "§7Die Seiten wurden getauscht!"
|
||||||
|
halftime: "§e§lHALBZEIT! ⏸"
|
||||||
|
halftime-score: "§7Spielstand: §c{score}"
|
||||||
|
halftime-resume: "§e⏱ 2. Halbzeit in §e{n} §6Sek!"
|
||||||
|
overtime: "§6§lVERLÄNGERUNG! ⚡"
|
||||||
|
overtime-sub: "§710 Minuten extra!"
|
||||||
|
penalty-start: "§c§lELFMETERSCHIEßEN! ⚽"
|
||||||
|
penalty-sub: "§75 Schüsse pro Team"
|
||||||
|
game-end-win: "§6§lGEWONNEN! 🏆"
|
||||||
|
game-end-win-sub: "§7Herzlichen Glückwunsch!"
|
||||||
|
game-end-lose: "§c§lVERLOREN!"
|
||||||
|
game-end-lose-sub: "§7Gutes Spiel!"
|
||||||
|
game-end-draw: "§7UNENTSCHIEDEN"
|
||||||
|
result: "§7Endergebnis: §c{score}"
|
||||||
|
|
||||||
|
# Tor
|
||||||
|
goal-banner: "§e§l⚽ T - O - R ⚽"
|
||||||
|
goal-team: "{team} §7hat ein Tor erzielt!"
|
||||||
|
goal-scorer: "§7Torschütze: §e{player}"
|
||||||
|
goal-score: "§7Spielstand: §c{score}"
|
||||||
|
goal-title: "§e⚽ TOR!"
|
||||||
|
goal-continue: "§aWeiter geht's!"
|
||||||
|
|
||||||
|
# Warn-Nachrichten
|
||||||
|
time-1min: "§e⏱ Noch §61 Minute§e!"
|
||||||
|
time-30sec: "§e⏱ Noch §630 Sekunden§e!"
|
||||||
|
|
||||||
|
# Abseits
|
||||||
|
offside: "§e⚠ §lABSEITS! §7— {player} stand im Abseits!"
|
||||||
|
offside-title: "§e⚠ ABSEITS!"
|
||||||
|
offside-sub: "§7Freistoß für {team}"
|
||||||
|
|
||||||
|
# Foul & Karten
|
||||||
|
foul: "§c⚠ §lFOUL! §7{player} hat gefoult!"
|
||||||
|
yellow-card: "§e🟨 §lGELBE KARTE §7— {player} §8({reason})"
|
||||||
|
yellow-card-2: "§e🟨→§c🟥 §lGELB-ROT §7— {player} §8(2. Gelbe Karte)"
|
||||||
|
red-card: "§c🟥 §lROTE KARTE §7— {player} §8({reason})"
|
||||||
|
red-card-title: "§c§lROTE KARTE!"
|
||||||
|
red-card-sub: "§7Du wurdest vom Platz gestellt!"
|
||||||
|
|
||||||
|
# Freistoß
|
||||||
|
freekick: "§e⚽ §lFREISTOSS §7für {team}!"
|
||||||
|
freekick-hint: "§7Gegner müssen §e{n} Blöcke §7Abstand halten."
|
||||||
|
freekick-push: "§cAbstand halten! Mindestens {n} Blöcke vom Ball!"
|
||||||
|
|
||||||
|
# Torwart
|
||||||
|
goalkeeper-assigned: "§6Du bist §lTorwart§6! Rechtsklick = Ball halten, Rechtsklick wieder = werfen."
|
||||||
|
goalkeeper-hold: "§6TW §f{player} §7hält den Ball!"
|
||||||
|
goalkeeper-throw: "§6TW §f{player} §7wirft den Ball!"
|
||||||
|
goalkeeper-no-hold: "§cDu kannst den Ball nur in deiner eigenen Hälfte halten!"
|
||||||
|
|
||||||
|
# Aus / Einwurf / Ecke / Abstoß
|
||||||
|
out-side: "§e⚽ §7Ball im Aus! §7Einwurf für {team}§7!"
|
||||||
|
out-corner: "§e⚽ §7Ball im Aus! §7Ecke für {team}§7!"
|
||||||
|
out-goal-kick: "§e⚽ §7Ball im Aus! §7Abstoß für {team}§7!"
|
||||||
|
|
||||||
|
# Feldgrenze-Warnung
|
||||||
|
boundary-warn: "§c⚠ §lSPIELFELDGRENZE! §7Kehre in §e{n} Sek §7zurück!"
|
||||||
|
boundary-return: "§aWieder im Spielfeld!"
|
||||||
|
boundary-disq: "§c⚠ §e{player} §cwurde disqualifiziert! (Spielfeldgrenze)"
|
||||||
|
boundary-disq-self: "§cDu wurdest disqualifiziert, weil du zu lange außerhalb warst!"
|
||||||
|
|
||||||
|
# Spieler beitreten / verlassen
|
||||||
|
player-join: "§e{player} §7ist beigetreten! §8({n}/{max})"
|
||||||
|
player-leave: "§e{player} §7hat das Spiel verlassen!"
|
||||||
|
team-red: "§cRotes Team"
|
||||||
|
team-blue: "§9Blaues Team"
|
||||||
|
|
||||||
|
# Matchbericht
|
||||||
|
report-header: "§e§l━━━━━━ MATCHBERICHT ━━━━━━"
|
||||||
|
report-footer: "§e§l━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
report-goals: "§7Tore:"
|
||||||
|
report-cards: "§7Karten:"
|
||||||
|
report-fouls: "§7Fouls:"
|
||||||
|
report-offside: "§7Abseits:"
|
||||||
|
report-mvp: "§6⭐ MVP: §e{player} §7({n} Tore)"
|
||||||
|
report-no-events: "§8Keine Ereignisse."
|
||||||
23
src/main/resources/plugin.yml
Normal file
23
src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Fussball
|
||||||
|
version: 2.0.0
|
||||||
|
main: de.fussball.plugin.Fussball
|
||||||
|
api-version: 1.21
|
||||||
|
author: M_Viper
|
||||||
|
description: Ein vollständiges Fußball-Minigame Plugin für Minecraft
|
||||||
|
|
||||||
|
softdepend:
|
||||||
|
- PlaceholderAPI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
fussball.admin:
|
||||||
|
description: Zugriff auf alle Admin-Befehle
|
||||||
|
default: op
|
||||||
|
fussball.play:
|
||||||
|
description: Spiele spielen
|
||||||
|
default: true
|
||||||
|
|
||||||
|
commands:
|
||||||
|
fussball:
|
||||||
|
description: Hauptbefehl des Fußball-Plugins
|
||||||
|
usage: /fussball <join|leave|spectate|list|stats|top|create|delete|setup|stop|debug>
|
||||||
|
aliases: [fb, soccer]
|
||||||
Reference in New Issue
Block a user