Files
Fussball/src/main/java/de/fussball/plugin/arena/Arena.java
2026-02-27 15:11:33 +01:00

373 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
// Strafräume optional manuell gesetzt; sonst auto-berechnet aus Tor + config
private Location redPenaltyMin, redPenaltyMax, bluePenaltyMin, bluePenaltyMax;
// Elfmeter-Punkte (optional sonst wird ballSpawn genutzt)
private Location redPenaltySpot, bluePenaltySpot;
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); }
// ── Strafraum ────────────────────────────────────────────────────────────
/**
* Prüft ob eine Position im roten Strafraum liegt.
* Wenn manuell gesetzte Punkte vorhanden sind, werden diese verwendet.
* Sonst wird der Strafraum automatisch aus den Tor-Koordinaten + config berechnet.
*/
public boolean isInRedPenaltyArea(Location loc) {
if (redPenaltyMin != null && redPenaltyMax != null) {
return isInRegion2D(loc, redPenaltyMin, redPenaltyMax);
}
return isAutoCalculatedPenaltyArea(loc, redGoalMin, redGoalMax, true);
}
public boolean isInBluePenaltyArea(Location loc) {
if (bluePenaltyMin != null && bluePenaltyMax != null) {
return isInRegion2D(loc, bluePenaltyMin, bluePenaltyMax);
}
return isAutoCalculatedPenaltyArea(loc, blueGoalMin, blueGoalMax, false);
}
/**
* Automatisch berechneter Strafraum.
* Erweitert das Tor um 'penalty-area-margin' seitwärts und 'penalty-area-depth'
* in Richtung Spielfeldmitte.
*/
private boolean isAutoCalculatedPenaltyArea(Location loc, Location gMin, Location gMax, boolean isRedGoal) {
if (gMin == null || gMax == null || loc == null) return false;
Fussball plugin = Fussball.getInstance();
double depth = plugin != null ? plugin.getConfig().getDouble("gameplay.penalty-area-depth", 16) : 16;
double margin = plugin != null ? plugin.getConfig().getDouble("gameplay.penalty-area-margin", 6) : 6;
double minX = Math.min(gMin.getX(), gMax.getX()) - margin;
double maxX = Math.max(gMin.getX(), gMax.getX()) + margin;
double minZ = Math.min(gMin.getZ(), gMax.getZ()) - margin;
double maxZ = Math.max(gMin.getZ(), gMax.getZ()) + margin;
org.bukkit.util.Vector dir = getFieldDirection();
if (dir != null) {
double redAxis = getRedGoalAxisValue();
double blueAxis = getBlueGoalAxisValue();
// Strafraum erstreckt sich VOM Tor in Richtung Feldmitte
if (Math.abs(dir.getZ()) > Math.abs(dir.getX())) {
// Feld läuft entlang Z-Achse
if (isRedGoal) {
if (redAxis < blueAxis) maxZ = Math.max(maxZ, maxZ + depth);
else minZ = Math.min(minZ, minZ - depth);
} else {
if (blueAxis > redAxis) minZ = Math.min(minZ, minZ - depth);
else maxZ = Math.max(maxZ, maxZ + depth);
}
} else {
// Feld läuft entlang X-Achse
if (isRedGoal) {
if (redAxis < blueAxis) maxX = Math.max(maxX, maxX + depth);
else minX = Math.min(minX, minX - depth);
} else {
if (blueAxis > redAxis) minX = Math.min(minX, minX - depth);
else maxX = Math.max(maxX, maxX + depth);
}
}
} else {
// Keine Richtungsinformation → gleichmäßig ausdehnen
minX -= depth / 2; maxX += depth / 2;
minZ -= depth / 2; maxZ += depth / 2;
}
return loc.getX() >= minX && loc.getX() <= maxX && loc.getZ() >= minZ && loc.getZ() <= maxZ;
}
private boolean isInRegion2D(Location loc, Location min, Location max) {
if (min == null || max == null || loc == null) return false;
return loc.getX() >= Math.min(min.getX(), max.getX()) && loc.getX() <= Math.max(min.getX(), max.getX())
&& loc.getZ() >= Math.min(min.getZ(), max.getZ()) && loc.getZ() <= Math.max(min.getZ(), max.getZ());
}
/**
* 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));
if (redPenaltyMin != null) map.put("redPenaltyMin", serLoc(redPenaltyMin));
if (redPenaltyMax != null) map.put("redPenaltyMax", serLoc(redPenaltyMax));
if (bluePenaltyMin != null) map.put("bluePenaltyMin", serLoc(bluePenaltyMin));
if (bluePenaltyMax != null) map.put("bluePenaltyMax", serLoc(bluePenaltyMax));
if (redPenaltySpot != null) map.put("redPenaltySpot", serLoc(redPenaltySpot));
if (bluePenaltySpot != null) map.put("bluePenaltySpot", serLoc(bluePenaltySpot));
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"));
if (map.containsKey("redPenaltyMin")) a.redPenaltyMin = desLoc(map.get("redPenaltyMin"));
if (map.containsKey("redPenaltyMax")) a.redPenaltyMax = desLoc(map.get("redPenaltyMax"));
if (map.containsKey("bluePenaltyMin")) a.bluePenaltyMin = desLoc(map.get("bluePenaltyMin"));
if (map.containsKey("bluePenaltyMax")) a.bluePenaltyMax = desLoc(map.get("bluePenaltyMax"));
if (map.containsKey("redPenaltySpot")) a.redPenaltySpot = desLoc(map.get("redPenaltySpot"));
if (map.containsKey("bluePenaltySpot")) a.bluePenaltySpot = desLoc(map.get("bluePenaltySpot"));
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 Location getRedPenaltyMin() { return redPenaltyMin; }
public void setRedPenaltyMin(Location l) { this.redPenaltyMin = l; }
public Location getRedPenaltyMax() { return redPenaltyMax; }
public void setRedPenaltyMax(Location l) { this.redPenaltyMax = l; }
public Location getBluePenaltyMin() { return bluePenaltyMin; }
public void setBluePenaltyMin(Location l) { this.bluePenaltyMin = l; }
public Location getBluePenaltyMax() { return bluePenaltyMax; }
public void setBluePenaltyMax(Location l) { this.bluePenaltyMax = l; }
// ── Elfmeter-Punkte ──────────────────────────────────────────────────────
public Location getRedPenaltySpot() { return redPenaltySpot; }
public void setRedPenaltySpot(Location l) { this.redPenaltySpot = l; }
public Location getBluePenaltySpot() { return bluePenaltySpot; }
public void setBluePenaltySpot(Location l) { this.bluePenaltySpot = l; }
/**
* Gibt den Elfmeter-Punkt für ein Team zurück.
* Falls nicht gesetzt, wird ballSpawn als Fallback verwendet.
*/
public Location getPenaltySpot(de.fussball.plugin.game.Team team) {
if (team == de.fussball.plugin.game.Team.RED) {
return redPenaltySpot != null ? redPenaltySpot : ballSpawn;
} else {
return bluePenaltySpot != null ? bluePenaltySpot : ballSpawn;
}
}
public boolean hasManualPenaltyAreas() { return redPenaltyMin != null && redPenaltyMax != null
&& bluePenaltyMin != null && bluePenaltyMax != null; }
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; }
}