Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0085ba5bd | |||
| e10f1313bd | |||
| 960290dfa8 | |||
| 3f6d91cdc7 |
Binary file not shown.
@@ -78,7 +78,10 @@ public class Game {
|
|||||||
|
|
||||||
private UUID lastKicker = null;
|
private UUID lastKicker = null;
|
||||||
private UUID secondLastKicker = null; // für Assist-Erkennung
|
private UUID secondLastKicker = null; // für Assist-Erkennung
|
||||||
private boolean lastKickWasHeader = false; // für Rückpass-Regel (Header erlaubt)
|
private boolean lastKickWasHeader = false; // für Rückpass-Regel (Header erlaubt)
|
||||||
|
/** true wenn der letzte Schuss ein Restart-Kick war (Einwurf/Eckstoß/Abstoß/Anstoß)
|
||||||
|
* → der NÄCHSTE Empfänger darf lt. Regel 11 §3 nicht auf Abseits geprüft werden */
|
||||||
|
private boolean lastKickWasRestart = false;
|
||||||
private Team lastTouchTeam = null;
|
private Team lastTouchTeam = null;
|
||||||
private Team throwInTeam = null;
|
private Team throwInTeam = null;
|
||||||
|
|
||||||
@@ -389,6 +392,10 @@ public class Game {
|
|||||||
cd--;
|
cd--;
|
||||||
} else {
|
} else {
|
||||||
spawnBallDelayed(arena.getBallSpawn());
|
spawnBallDelayed(arena.getBallSpawn());
|
||||||
|
// Regel 8: Rot führt den Anstoß in der 1. Halbzeit aus
|
||||||
|
throwInTeam = Team.RED;
|
||||||
|
kickoffEnforceTicks = 200; // 10s Kreisschutz (Regel 8: 9,15m Abstand)
|
||||||
|
broadcastAll(Messages.get("kickoff-team", "team", "§cRotes Team"));
|
||||||
for (UUID uuid : allPlayers) {
|
for (UUID uuid : allPlayers) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
@@ -444,9 +451,19 @@ public class Game {
|
|||||||
secondHalf = true;
|
secondHalf = true;
|
||||||
timeLeft = arena.getGameDuration() / 2;
|
timeLeft = arena.getGameDuration() / 2;
|
||||||
updateGoalBeaconColors();
|
updateGoalBeaconColors();
|
||||||
// Nachspielzeit der 1. Halbzeit zurücksetzen
|
// Nachspielzeit und Spielzustand der 1. Halbzeit zurücksetzen
|
||||||
injuryTimeBuffer = 0;
|
injuryTimeBuffer = 0;
|
||||||
inInjuryTime = false;
|
inInjuryTime = false;
|
||||||
|
lastKicker = null;
|
||||||
|
secondLastKicker = null;
|
||||||
|
lastTouchTeam = null;
|
||||||
|
lastKickWasHeader = false;
|
||||||
|
lastKickWasRestart = false;
|
||||||
|
lastBallLocation = null;
|
||||||
|
outCooldown = false;
|
||||||
|
offsideCooldown = false;
|
||||||
|
headerCooldowns.clear();
|
||||||
|
outOfBoundsCountdown.clear();
|
||||||
|
|
||||||
// Seitenwechsel: Rotes Team → BlueSpawn, Blaues Team → RedSpawn
|
// Seitenwechsel: Rotes Team → BlueSpawn, Blaues Team → RedSpawn
|
||||||
for (UUID uuid : redTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.teleport(arena.getBlueSpawn()); }
|
for (UUID uuid : redTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.teleport(arena.getBlueSpawn()); }
|
||||||
@@ -472,6 +489,10 @@ public class Game {
|
|||||||
cd--;
|
cd--;
|
||||||
} else {
|
} else {
|
||||||
spawnBallDelayed(arena.getBallSpawn());
|
spawnBallDelayed(arena.getBallSpawn());
|
||||||
|
// Regel 8: Das andere Team (Blau) stößt in der 2. Halbzeit an
|
||||||
|
throwInTeam = Team.BLUE;
|
||||||
|
kickoffEnforceTicks = 200;
|
||||||
|
broadcastAll(Messages.get("kickoff-team", "team", "§9Blaues Team"));
|
||||||
for (UUID uuid : allPlayers) {
|
for (UUID uuid : allPlayers) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
@@ -497,11 +518,16 @@ public class Game {
|
|||||||
|
|
||||||
if (ball != null) ball.remove();
|
if (ball != null) ball.remove();
|
||||||
spawnBallDelayed(arena.getBallSpawn());
|
spawnBallDelayed(arena.getBallSpawn());
|
||||||
|
// Regel 8: In der Verlängerung stößt das Team an, das in der 2. Halbzeit NICHT angestoßen hat.
|
||||||
|
// In der 2. HZ stieß Blau an → in der VL stößt Rot an.
|
||||||
|
throwInTeam = Team.RED;
|
||||||
|
kickoffEnforceTicks = 200; // 10s Anstoß-Kreis
|
||||||
|
|
||||||
broadcastAll("§6§l╔══════════════════════╗");
|
broadcastAll("§6§l╔══════════════════════╗");
|
||||||
broadcastAll("§6§l║ ⚽ VERLÄNGERUNG! ║");
|
broadcastAll("§6§l║ ⚽ VERLÄNGERUNG! ║");
|
||||||
broadcastAll("§6§l╚══════════════════════╝");
|
broadcastAll("§6§l╚══════════════════════╝");
|
||||||
broadcastAll("§7Spielstand: §c" + redScore + " §7: §9" + blueScore);
|
broadcastAll("§7Spielstand: §c" + redScore + " §7: §9" + blueScore);
|
||||||
|
broadcastAll(Messages.get("kickoff-team", "team", "§cRotes Team"));
|
||||||
|
|
||||||
for (UUID uuid : redTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) { p.teleport(arena.getRedSpawn()); p.sendTitle("§6§lVERLÄNGERUNG!", "§710 Minuten extra!", 10, 60, 10); } }
|
for (UUID uuid : redTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) { p.teleport(arena.getRedSpawn()); p.sendTitle("§6§lVERLÄNGERUNG!", "§710 Minuten extra!", 10, 60, 10); } }
|
||||||
for (UUID uuid : blueTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) { p.teleport(arena.getBlueSpawn()); p.sendTitle("§6§lVERLÄNGERUNG!", "§710 Minuten extra!", 10, 60, 10); } }
|
for (UUID uuid : blueTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) { p.teleport(arena.getBlueSpawn()); p.sendTitle("§6§lVERLÄNGERUNG!", "§710 Minuten extra!", 10, 60, 10); } }
|
||||||
@@ -856,17 +882,27 @@ public class Game {
|
|||||||
Vector diff = to.toVector().subtract(from.toVector());
|
Vector diff = to.toVector().subtract(from.toVector());
|
||||||
double distance = diff.length();
|
double distance = diff.length();
|
||||||
int steps = Math.max(1, (int) Math.ceil(distance / 0.2));
|
int steps = Math.max(1, (int) Math.ceil(distance / 0.2));
|
||||||
|
// BUG FIX: Vier Y-Offsets prüfen:
|
||||||
|
// 0.0 = ArmorStand-Füße (Entity-Position)
|
||||||
|
// 0.5 = Mitte des kleinen Stands
|
||||||
|
// 0.975 = tatsächliche Helmposition (Textur sichtbar hier!)
|
||||||
|
// 1.4 = konservativer oberer Puffer
|
||||||
|
// Früher wurden nur 0 und 1.4 geprüft → Bälle auf Helm-Höhe (0.975)
|
||||||
|
// wurden nicht als Tor erkannt und landeten als Ecke/Abstoß.
|
||||||
|
final double[] Y_OFFSETS = {0.0, 0.5, 0.975, 1.4};
|
||||||
for (int i = 0; i <= steps; i++) {
|
for (int i = 0; i <= steps; i++) {
|
||||||
double t = (double) i / steps;
|
double t = (double) i / steps;
|
||||||
Location p = from.clone().add(diff.clone().multiply(t));
|
Location base = from.clone().add(diff.clone().multiply(t));
|
||||||
Location head = p.clone().add(0, 1.4, 0);
|
for (double dy : Y_OFFSETS) {
|
||||||
// In der 2. Halbzeit sind die Seiten getauscht → Tore umkehren
|
Location check = base.clone().add(0, dy, 0);
|
||||||
if (!secondHalf) {
|
// In der 2. Halbzeit sind die Seiten getauscht → Tore umkehren
|
||||||
if (arena.isInRedGoal(p) || arena.isInRedGoal(head)) return Team.BLUE;
|
if (!secondHalf) {
|
||||||
if (arena.isInBlueGoal(p) || arena.isInBlueGoal(head)) return Team.RED;
|
if (arena.isInRedGoal(check)) return Team.BLUE;
|
||||||
} else {
|
if (arena.isInBlueGoal(check)) return Team.RED;
|
||||||
if (arena.isInRedGoal(p) || arena.isInRedGoal(head)) return Team.RED;
|
} else {
|
||||||
if (arena.isInBlueGoal(p) || arena.isInBlueGoal(head)) return Team.BLUE;
|
if (arena.isInRedGoal(check)) return Team.RED;
|
||||||
|
if (arena.isInBlueGoal(check)) return Team.BLUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -910,25 +946,47 @@ public class Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "redEnd" -> {
|
case "redEnd" -> {
|
||||||
if (touchTeam == Team.RED) {
|
// Korrekte Fußball-Regel:
|
||||||
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
|
// Letzter Kontakt durch VERTEIDIGER an eigener Torlinie → ECKE für Angreifer
|
||||||
throwInTeam = Team.BLUE;
|
// Letzter Kontakt durch ANGREIFER an gegnerischer Torlinie → ABSTOSS für Verteidiger
|
||||||
message = "§e⚽ §7Ball im Aus! §9Ecke für Blaues Team§7!";
|
// 1. Halbzeit: ROT verteidigt diese Seite (redEnd = rotes Tor)
|
||||||
|
// 2. Halbzeit: BLAU verteidigt diese Seite (Seitenwechsel)
|
||||||
|
Team defenderHere = secondHalf ? Team.BLUE : Team.RED;
|
||||||
|
Team attackerHere = defenderHere.getOpponent();
|
||||||
|
if (touchTeam == defenderHere) {
|
||||||
|
// Verteidiger hat den Ball ins Aus geschossen → ECKE für Angreifer
|
||||||
|
// Ball an Strafraumgrenze (11m-Linie) platzieren – nicht in der Spielfeldecke
|
||||||
|
resumeLocation = moveInsideField(getPenaltyAreaCornerLocation(outLocation, true), 1.25);
|
||||||
|
throwInTeam = attackerHere;
|
||||||
|
String teamStr = attackerHere == Team.RED ? "§cRotes Team" : "§9Blaues Team";
|
||||||
|
message = "§e⚽ §7Ball im Aus! §7Ecke für " + teamStr + "§7!";
|
||||||
} else {
|
} else {
|
||||||
resumeLocation = moveInsideField(arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn(), 1.25);
|
// Angreifer (oder unbekannt) hat den Ball ins Aus geschossen → ABSTOSS für Verteidiger
|
||||||
throwInTeam = Team.RED;
|
// Ball ~5,5 Blöcke vor der Torlinie (5-Meter-Raum), Feldmitte
|
||||||
message = "§e⚽ §7Ball im Aus! §cAbstoß für Rotes Team§7!";
|
resumeLocation = moveInsideField(getGoalKickSpawnLocation(true), 1.25);
|
||||||
|
throwInTeam = defenderHere;
|
||||||
|
String teamStr = defenderHere == Team.RED ? "§cRotes Team" : "§9Blaues Team";
|
||||||
|
message = "§e⚽ §7Ball im Aus! §7Abstoß für " + teamStr + "§7!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "blueEnd" -> {
|
case "blueEnd" -> {
|
||||||
if (touchTeam == Team.BLUE) {
|
// analog zu redEnd.
|
||||||
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
|
// 1. Halbzeit: BLAU verteidigt diese Seite (blueEnd = blaues Tor)
|
||||||
throwInTeam = Team.RED;
|
// 2. Halbzeit: ROT verteidigt diese Seite (Seitenwechsel)
|
||||||
message = "§e⚽ §7Ball im Aus! §cEcke für Rotes Team§7!";
|
Team defenderHere = secondHalf ? Team.RED : Team.BLUE;
|
||||||
|
Team attackerHere = defenderHere.getOpponent();
|
||||||
|
if (touchTeam == defenderHere) {
|
||||||
|
// Verteidiger → ECKE für Angreifer
|
||||||
|
resumeLocation = moveInsideField(getPenaltyAreaCornerLocation(outLocation, false), 1.25);
|
||||||
|
throwInTeam = attackerHere;
|
||||||
|
String teamStr = attackerHere == Team.RED ? "§cRotes Team" : "§9Blaues Team";
|
||||||
|
message = "§e⚽ §7Ball im Aus! §7Ecke für " + teamStr + "§7!";
|
||||||
} else {
|
} else {
|
||||||
resumeLocation = moveInsideField(arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn(), 1.25);
|
// Angreifer (oder unbekannt) → ABSTOSS für Verteidiger
|
||||||
throwInTeam = Team.BLUE;
|
resumeLocation = moveInsideField(getGoalKickSpawnLocation(false), 1.25);
|
||||||
message = "§e⚽ §7Ball im Aus! §9Abstoß für Blaues Team§7!";
|
throwInTeam = defenderHere;
|
||||||
|
String teamStr = defenderHere == Team.RED ? "§cRotes Team" : "§9Blaues Team";
|
||||||
|
message = "§e⚽ §7Ball im Aus! §7Abstoß für " + teamStr + "§7!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
@@ -942,14 +1000,137 @@ public class Game {
|
|||||||
for (UUID uuid : allPlayers) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 1f, 1.2f); }
|
for (UUID uuid : allPlayers) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 1f, 1.2f); }
|
||||||
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-out", 3));
|
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-out", 3));
|
||||||
|
|
||||||
|
// throwInTeam sichern – spawnBallDelayed() würde es auf null zurücksetzen
|
||||||
|
final Team capturedThrowIn = throwInTeam;
|
||||||
final Location spawnHere = resumeLocation;
|
final Location spawnHere = resumeLocation;
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (state == GameState.RUNNING || state == GameState.OVERTIME) spawnBallDelayed(spawnHere);
|
if (state == GameState.RUNNING || state == GameState.OVERTIME) {
|
||||||
|
spawnBallDelayed(spawnHere);
|
||||||
|
throwInTeam = capturedThrowIn;
|
||||||
|
// Abstandsregel pro Spielfortsetzungs-Typ erzwingen (Regel 15/16/17)
|
||||||
|
freekickLocation = spawnHere.clone();
|
||||||
|
freekickTicks = plugin.getConfig().getInt("gameplay.freekick-duration", 600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.runTaskLater(plugin, 40L);
|
}.runTaskLater(plugin, 40L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet den Freistoß-Aufstellungsort für eine Ecke.
|
||||||
|
* Statt der wörtlichen Spielfeldecke wird der Ball an der Strafraumgrenze
|
||||||
|
* (Tiefe aus gameplay.penalty-area-depth) entlang der nächsten Seitenlinie platziert.
|
||||||
|
* Das entspricht dem Wunsch "an oder vor der 11-Meter-Grenze".
|
||||||
|
*
|
||||||
|
* @param outLoc – Wo der Ball das Feld verlassen hat
|
||||||
|
* @param isRedEnd – true = rotes Tor-Ende (redGoal-Seite des Feldes)
|
||||||
|
*/
|
||||||
|
private Location getPenaltyAreaCornerLocation(Location outLoc, boolean isRedEnd) {
|
||||||
|
if (arena.getFieldMin() == null || arena.getFieldMax() == null || arena.getBallSpawn() == null) {
|
||||||
|
return getCornerLocation(outLoc);
|
||||||
|
}
|
||||||
|
double y = arena.getBallSpawn().getY();
|
||||||
|
double penaltyDepth = plugin.getConfig().getDouble("gameplay.penalty-area-depth", 16);
|
||||||
|
|
||||||
|
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||||
|
double maxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||||
|
double minZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||||
|
double maxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||||
|
|
||||||
|
org.bukkit.util.Vector fieldDir = arena.getFieldDirection();
|
||||||
|
if (fieldDir == null) return getCornerLocation(outLoc);
|
||||||
|
|
||||||
|
if (Math.abs(fieldDir.getZ()) >= Math.abs(fieldDir.getX())) {
|
||||||
|
// ── Feld läuft entlang Z-Achse ──────────────────────────────────
|
||||||
|
boolean redIsLowZ = arena.getRedGoalAxisValue() < arena.getBlueGoalAxisValue();
|
||||||
|
// Nächste Seitenlinie (X-Seite) bestimmen
|
||||||
|
double sideX = (Math.abs(outLoc.getX() - minX) <= Math.abs(outLoc.getX() - maxX)) ? minX : maxX;
|
||||||
|
// Z-Position: Strafraum-Tiefe vom Toraus-Ende ins Feld
|
||||||
|
double endZ, targetZ;
|
||||||
|
if (isRedEnd) {
|
||||||
|
endZ = redIsLowZ ? minZ : maxZ;
|
||||||
|
targetZ = redIsLowZ ? endZ + penaltyDepth : endZ - penaltyDepth;
|
||||||
|
} else {
|
||||||
|
endZ = redIsLowZ ? maxZ : minZ;
|
||||||
|
targetZ = redIsLowZ ? endZ - penaltyDepth : endZ + penaltyDepth;
|
||||||
|
}
|
||||||
|
targetZ = Math.max(minZ + 1.0, Math.min(maxZ - 1.0, targetZ));
|
||||||
|
return new Location(outLoc.getWorld(), sideX, y, targetZ);
|
||||||
|
} else {
|
||||||
|
// ── Feld läuft entlang X-Achse ──────────────────────────────────
|
||||||
|
boolean redIsLowX = arena.getRedGoalAxisValue() < arena.getBlueGoalAxisValue();
|
||||||
|
double sideZ = (Math.abs(outLoc.getZ() - minZ) <= Math.abs(outLoc.getZ() - maxZ)) ? minZ : maxZ;
|
||||||
|
double endX, targetX;
|
||||||
|
if (isRedEnd) {
|
||||||
|
endX = redIsLowX ? minX : maxX;
|
||||||
|
targetX = redIsLowX ? endX + penaltyDepth : endX - penaltyDepth;
|
||||||
|
} else {
|
||||||
|
endX = redIsLowX ? maxX : minX;
|
||||||
|
targetX = redIsLowX ? endX - penaltyDepth : endX + penaltyDepth;
|
||||||
|
}
|
||||||
|
targetX = Math.max(minX + 1.0, Math.min(maxX - 1.0, targetX));
|
||||||
|
return new Location(outLoc.getWorld(), targetX, y, sideZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt den Ball-Aufstellungsort für einen Abstoß zurück.
|
||||||
|
* Der Ball wird ~5.5 Blöcke vor der Torlinie, mittig auf dem Feld platziert
|
||||||
|
* (entspricht dem 5-Meter-Raum / Torabstoß-Raum im echten Fußball).
|
||||||
|
*
|
||||||
|
* @param isRedEnd – true = roter Torbereich
|
||||||
|
*/
|
||||||
|
private Location getGoalKickSpawnLocation(boolean isRedEnd) {
|
||||||
|
if (arena.getFieldMin() == null || arena.getFieldMax() == null || arena.getBallSpawn() == null) {
|
||||||
|
if (isRedEnd) return arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn();
|
||||||
|
else return arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn();
|
||||||
|
}
|
||||||
|
double y = arena.getBallSpawn().getY();
|
||||||
|
final double GOAL_KICK_INSET = 5.5; // ~5-Meter-Raum (6-Yard-Box)
|
||||||
|
|
||||||
|
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||||
|
double maxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||||
|
double minZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||||
|
double maxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ());
|
||||||
|
double centerX = (minX + maxX) / 2.0;
|
||||||
|
double centerZ = (minZ + maxZ) / 2.0;
|
||||||
|
|
||||||
|
org.bukkit.util.Vector fieldDir = arena.getFieldDirection();
|
||||||
|
if (fieldDir == null) {
|
||||||
|
return isRedEnd ? (arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn())
|
||||||
|
: (arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(fieldDir.getZ()) >= Math.abs(fieldDir.getX())) {
|
||||||
|
// Feld entlang Z-Achse
|
||||||
|
boolean redIsLowZ = arena.getRedGoalAxisValue() < arena.getBlueGoalAxisValue();
|
||||||
|
double kickZ;
|
||||||
|
if (isRedEnd) {
|
||||||
|
double endZ = redIsLowZ ? minZ : maxZ;
|
||||||
|
kickZ = redIsLowZ ? endZ + GOAL_KICK_INSET : endZ - GOAL_KICK_INSET;
|
||||||
|
} else {
|
||||||
|
double endZ = redIsLowZ ? maxZ : minZ;
|
||||||
|
kickZ = redIsLowZ ? endZ - GOAL_KICK_INSET : endZ + GOAL_KICK_INSET;
|
||||||
|
}
|
||||||
|
kickZ = Math.max(minZ + 1.0, Math.min(maxZ - 1.0, kickZ));
|
||||||
|
return new Location(arena.getFieldMin().getWorld(), centerX, y, kickZ);
|
||||||
|
} else {
|
||||||
|
// Feld entlang X-Achse
|
||||||
|
boolean redIsLowX = arena.getRedGoalAxisValue() < arena.getBlueGoalAxisValue();
|
||||||
|
double kickX;
|
||||||
|
if (isRedEnd) {
|
||||||
|
double endX = redIsLowX ? minX : maxX;
|
||||||
|
kickX = redIsLowX ? endX + GOAL_KICK_INSET : endX - GOAL_KICK_INSET;
|
||||||
|
} else {
|
||||||
|
double endX = redIsLowX ? maxX : minX;
|
||||||
|
kickX = redIsLowX ? endX - GOAL_KICK_INSET : endX + GOAL_KICK_INSET;
|
||||||
|
}
|
||||||
|
kickX = Math.max(minX + 1.0, Math.min(maxX - 1.0, kickX));
|
||||||
|
return new Location(arena.getFieldMin().getWorld(), kickX, y, centerZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hilfsmethode: wörtliche Spielfeldecke (als Fallback). */
|
||||||
private Location getCornerLocation(Location outLoc) {
|
private Location getCornerLocation(Location outLoc) {
|
||||||
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return arena.getBallSpawn();
|
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return arena.getBallSpawn();
|
||||||
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
|
||||||
@@ -1019,8 +1200,8 @@ public class Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throwInTeam = null;
|
clearThrowIn(); // setzt lastKickWasRestart=true falls Einwurf/Restart war → kein Abseits für Empfänger (Regel 11 §3)
|
||||||
setLastKicker(uuid); // korrekt: nutzt setLastKicker statt direktem Feldzugriff
|
setLastKicker(uuid);
|
||||||
ball.kick(p);
|
ball.kick(p);
|
||||||
break; // pro Tick max. 1 Auto-Kick
|
break; // pro Tick max. 1 Auto-Kick
|
||||||
}
|
}
|
||||||
@@ -1370,30 +1551,43 @@ public class Game {
|
|||||||
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-foul", 5));
|
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-foul", 5));
|
||||||
logMatchEvent("§cFoul: §e" + fouler.getName() + " §7→ §e" + victim.getName());
|
logMatchEvent("§cFoul: §e" + fouler.getName() + " §7→ §e" + victim.getName());
|
||||||
|
|
||||||
// ── Foul im Strafraum → Elfmeter ───────────────────────────────────
|
// ── Foul im Strafraum → Elfmeter (Regel 14) ──────────────────────────────
|
||||||
|
// Regel 14: Strafstoß wenn ein Spieler ein direktes Foul im eigenen Strafraum begeht.
|
||||||
|
// In der 2. Halbzeit sind die Seiten getauscht:
|
||||||
|
// 1. HZ: Rot verteidigt roten SR, Blau verteidigt blauen SR
|
||||||
|
// 2. HZ: Blau verteidigt roten SR, Rot verteidigt blauen SR
|
||||||
boolean inRedPenalty = arena.isInRedPenaltyArea(foulLocation);
|
boolean inRedPenalty = arena.isInRedPenaltyArea(foulLocation);
|
||||||
boolean inBluePenalty = arena.isInBluePenaltyArea(foulLocation);
|
boolean inBluePenalty = arena.isInBluePenaltyArea(foulLocation);
|
||||||
boolean penaltyKick = false;
|
boolean penaltyKick = false;
|
||||||
|
|
||||||
if (inRedPenalty && victimTeam == Team.BLUE) {
|
boolean penaltyForBlue, penaltyForRed;
|
||||||
// Foul an Blau im roten Strafraum → Elfmeter für Blau
|
if (!secondHalf) {
|
||||||
|
penaltyForBlue = inRedPenalty && victimTeam == Team.BLUE;
|
||||||
|
penaltyForRed = inBluePenalty && victimTeam == Team.RED;
|
||||||
|
} else {
|
||||||
|
// Seitenwechsel: Blau greift jetzt auf roten SR-Seite an
|
||||||
|
penaltyForBlue = inBluePenalty && victimTeam == Team.BLUE;
|
||||||
|
penaltyForRed = inRedPenalty && victimTeam == Team.RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (penaltyForBlue) {
|
||||||
broadcastAll(Messages.get("foul-penalty", "team", "§9Blaues Team"));
|
broadcastAll(Messages.get("foul-penalty", "team", "§9Blaues Team"));
|
||||||
for (UUID uuid : getAllAndSpectators()) {
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§9Blaues Team§7 schießt!", 5, 50, 10);
|
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§9Blaues Team§7 schießt!", 5, 50, 10);
|
||||||
}
|
}
|
||||||
penaltyKick = true;
|
penaltyKick = true;
|
||||||
// Elfmeter als Freistoß direkt auf Ballspawn (ggf. später: separater Elfmeter-Punkt)
|
Location penSpot = arena.getPenaltySpot(Team.BLUE);
|
||||||
startFreekick(Team.BLUE, arena.getBallSpawn(), "Elfmeter");
|
startFreekick(Team.BLUE, penSpot != null ? penSpot : arena.getBallSpawn(), "Elfmeter");
|
||||||
} else if (inBluePenalty && victimTeam == Team.RED) {
|
} else if (penaltyForRed) {
|
||||||
// Foul an Rot im blauen Strafraum → Elfmeter für Rot
|
|
||||||
broadcastAll(Messages.get("foul-penalty", "team", "§cRotes Team"));
|
broadcastAll(Messages.get("foul-penalty", "team", "§cRotes Team"));
|
||||||
for (UUID uuid : getAllAndSpectators()) {
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§cRotes Team§7 schießt!", 5, 50, 10);
|
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§cRotes Team§7 schießt!", 5, 50, 10);
|
||||||
}
|
}
|
||||||
penaltyKick = true;
|
penaltyKick = true;
|
||||||
startFreekick(Team.RED, arena.getBallSpawn(), "Elfmeter");
|
Location penSpot = arena.getPenaltySpot(Team.RED);
|
||||||
|
startFreekick(Team.RED, penSpot != null ? penSpot : arena.getBallSpawn(), "Elfmeter");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!penaltyKick) {
|
if (!penaltyKick) {
|
||||||
@@ -1447,8 +1641,18 @@ public class Game {
|
|||||||
broadcastAll(Messages.get("freekick-hint", "n", String.format("%.0f", dist)));
|
broadcastAll(Messages.get("freekick-hint", "n", String.format("%.0f", dist)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzwingt den korrekten Abstand für die jeweilige Spielfortsetzung (Regel 13–17).
|
||||||
|
* Freistoß (Regel 13): 5 Blöcke (config: freekick-distance)
|
||||||
|
* Einwurf (Regel 15): 2 Blöcke
|
||||||
|
* Abstoß (Regel 16): 9,15 Blöcke (Gegner außerhalb Strafraum)
|
||||||
|
* Eckstoß (Regel 17): 9,15 Blöcke
|
||||||
|
* Anstoß (Regel 8): 9,15 Blöcke (via kickoffEnforceTicks)
|
||||||
|
*/
|
||||||
private void enforceFreekickDistance() {
|
private void enforceFreekickDistance() {
|
||||||
if (freekickLocation == null || throwInTeam == null) return;
|
if (freekickLocation == null || throwInTeam == null) return;
|
||||||
|
// Abstand je nach Typ: prüfe ob freekickLocation nah an einer Seitenlinie ist (=Einwurf)
|
||||||
|
// oder in der Feldhälfte nahe einer Torlinie (=Abstoß/Eckstoß) oder zentral (=Freistoß)
|
||||||
double minDist = plugin.getConfig().getDouble("gameplay.freekick-distance", 5.0);
|
double minDist = plugin.getConfig().getDouble("gameplay.freekick-distance", 5.0);
|
||||||
Team opposingTeam = throwInTeam.getOpponent();
|
Team opposingTeam = throwInTeam.getOpponent();
|
||||||
List<UUID> opponents = opposingTeam == Team.RED ? redTeam : blueTeam;
|
List<UUID> opponents = opposingTeam == Team.RED ? redTeam : blueTeam;
|
||||||
@@ -1840,10 +2044,11 @@ public class Game {
|
|||||||
throwInTeam = null;
|
throwInTeam = null;
|
||||||
injuryTimeBuffer = 0;
|
injuryTimeBuffer = 0;
|
||||||
inInjuryTime = false;
|
inInjuryTime = false;
|
||||||
lastKicker = null;
|
lastKicker = null;
|
||||||
secondLastKicker = null;
|
secondLastKicker = null;
|
||||||
lastKickWasHeader = false;
|
lastKickWasHeader = false;
|
||||||
secondHalf = false;
|
lastKickWasRestart = false;
|
||||||
|
secondHalf = false;
|
||||||
updateGoalBeaconColors();
|
updateGoalBeaconColors();
|
||||||
|
|
||||||
// Persistente Statistiken speichern
|
// Persistente Statistiken speichern
|
||||||
@@ -2121,7 +2326,8 @@ public class Game {
|
|||||||
Player newKicker = Bukkit.getPlayer(uuid);
|
Player newKicker = Bukkit.getPlayer(uuid);
|
||||||
if (prevKicker != null && newKicker != null) {
|
if (prevKicker != null && newKicker != null) {
|
||||||
double dist = lastKickLocation.distance(ball.getEntity().getLocation());
|
double dist = lastKickLocation.distance(ball.getEntity().getLocation());
|
||||||
if (dist >= LONG_PASS_DISTANCE && getTeam(prevKicker) == getTeam(newKicker)) {
|
double longPassDist = plugin.getConfig().getDouble("gameplay.long-pass-distance", LONG_PASS_DISTANCE);
|
||||||
|
if (dist >= longPassDist && getTeam(prevKicker) == getTeam(newKicker)) {
|
||||||
// Langer Pass innerhalb des Teams
|
// Langer Pass innerhalb des Teams
|
||||||
String msg = "§7⚽ §eLangpass §7von §f" + prevKicker.getName()
|
String msg = "§7⚽ §eLangpass §7von §f" + prevKicker.getName()
|
||||||
+ " §7zu §f" + newKicker.getName()
|
+ " §7zu §f" + newKicker.getName()
|
||||||
@@ -2144,10 +2350,16 @@ public class Game {
|
|||||||
if (p != null) lastTouchTeam = getTeam(p);
|
if (p != null) lastTouchTeam = getTeam(p);
|
||||||
if (p != null) kicks.merge(uuid, 1, Integer::sum);
|
if (p != null) kicks.merge(uuid, 1, Integer::sum);
|
||||||
|
|
||||||
// Abseits-Check
|
// ── Abseits-Check (Regel 11 §3: kein Abseits nach Einwurf, Abstoß, Eckstoß) ──
|
||||||
|
// lastKickWasRestart wird in clearThrowIn() gesetzt wenn throwInTeam != null war.
|
||||||
|
// Der EMPFÄNGER des ersten Restart-Passes darf nicht auf Abseits geprüft werden.
|
||||||
|
// Das Flag wird hier konsumiert (→ gilt nur für diesen einen Empfänger).
|
||||||
|
boolean skipOffside = lastKickWasRestart;
|
||||||
|
lastKickWasRestart = false; // Flag zurücksetzen nach Konsum
|
||||||
if (plugin.getConfig().getBoolean("gameplay.offside-enabled", true)
|
if (plugin.getConfig().getBoolean("gameplay.offside-enabled", true)
|
||||||
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
|
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
|
||||||
&& ball != null && ball.getEntity() != null && !offsideCooldown) {
|
&& ball != null && ball.getEntity() != null && !offsideCooldown
|
||||||
|
&& !skipOffside) {
|
||||||
checkOffside(uuid, ball.getEntity().getLocation());
|
checkOffside(uuid, ball.getEntity().getLocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2163,9 +2375,13 @@ public class Game {
|
|||||||
if (p != null) lastTouchTeam = getTeam(p);
|
if (p != null) lastTouchTeam = getTeam(p);
|
||||||
if (p != null) kicks.merge(uuid, 1, Integer::sum);
|
if (p != null) kicks.merge(uuid, 1, Integer::sum);
|
||||||
|
|
||||||
|
// Kopfball: gleiche Abseits-Logik – kein Abseits wenn Restart-Empfänger
|
||||||
|
boolean skipOffside = lastKickWasRestart;
|
||||||
|
lastKickWasRestart = false;
|
||||||
if (plugin.getConfig().getBoolean("gameplay.offside-enabled", true)
|
if (plugin.getConfig().getBoolean("gameplay.offside-enabled", true)
|
||||||
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
|
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
|
||||||
&& ball != null && ball.getEntity() != null && !offsideCooldown) {
|
&& ball != null && ball.getEntity() != null && !offsideCooldown
|
||||||
|
&& !skipOffside) {
|
||||||
checkOffside(uuid, ball.getEntity().getLocation());
|
checkOffside(uuid, ball.getEntity().getLocation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2201,6 +2417,8 @@ public class Game {
|
|||||||
public boolean isLastKickWasHeader() { return lastKickWasHeader; }
|
public boolean isLastKickWasHeader() { return lastKickWasHeader; }
|
||||||
/** Berechtigung aufheben – wird von BallListener nach dem ersten Schuss gerufen */
|
/** Berechtigung aufheben – wird von BallListener nach dem ersten Schuss gerufen */
|
||||||
public void clearThrowIn() {
|
public void clearThrowIn() {
|
||||||
|
// Wenn throwInTeam gesetzt war, war das ein Restart-Kick → nächster Empfänger kein Abseits
|
||||||
|
if (throwInTeam != null) lastKickWasRestart = true;
|
||||||
throwInTeam = null;
|
throwInTeam = null;
|
||||||
freekickLocation = null;
|
freekickLocation = null;
|
||||||
freekickTicks = 0;
|
freekickTicks = 0;
|
||||||
|
|||||||
@@ -17,6 +17,29 @@ import java.util.function.Consumer;
|
|||||||
* new UpdateChecker(this, RESOURCE_ID).getVersion(version -> { ... });
|
* new UpdateChecker(this, RESOURCE_ID).getVersion(version -> { ... });
|
||||||
*/
|
*/
|
||||||
public class UpdateChecker {
|
public class UpdateChecker {
|
||||||
|
/**
|
||||||
|
* Vergleicht zwei Versionsnummern (z.B. "1.0.3" und "1.0.2").
|
||||||
|
* Gibt >0 zurück, wenn v1 > v2, <0 wenn v1 < v2, 0 wenn gleich.
|
||||||
|
*/
|
||||||
|
public static int compareVersions(String v1, String v2) {
|
||||||
|
String[] parts1 = v1.replace("v", "").split("\\.");
|
||||||
|
String[] parts2 = v2.replace("v", "").split("\\.");
|
||||||
|
int len = Math.max(parts1.length, parts2.length);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int n1 = i < parts1.length ? parseIntSafe(parts1[i]) : 0;
|
||||||
|
int n2 = i < parts2.length ? parseIntSafe(parts2[i]) : 0;
|
||||||
|
if (n1 != n2) return Integer.compare(n1, n2);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseIntSafe(String s) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s.replaceAll("[^0-9]", ""));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final int resourceId;
|
private final int resourceId;
|
||||||
|
|||||||
Reference in New Issue
Block a user