Update from Git Manager GUI

This commit is contained in:
2026-03-27 10:05:43 +01:00
parent 3f6d91cdc7
commit 960290dfa8
2 changed files with 265 additions and 47 deletions

View File

@@ -79,6 +79,9 @@ public class Game {
private UUID lastKicker = null;
private UUID secondLastKicker = null; // für Assist-Erkennung
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 throwInTeam = null;
@@ -389,6 +392,10 @@ public class Game {
cd--;
} else {
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) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) {
@@ -444,9 +451,19 @@ public class Game {
secondHalf = true;
timeLeft = arena.getGameDuration() / 2;
updateGoalBeaconColors();
// Nachspielzeit der 1. Halbzeit zurücksetzen
// Nachspielzeit und Spielzustand der 1. Halbzeit zurücksetzen
injuryTimeBuffer = 0;
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
for (UUID uuid : redTeam) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.teleport(arena.getBlueSpawn()); }
@@ -472,6 +489,10 @@ public class Game {
cd--;
} else {
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) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) {
@@ -497,11 +518,16 @@ public class Game {
if (ball != null) ball.remove();
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║ ⚽ VERLÄNGERUNG! ║");
broadcastAll("§6§l╚══════════════════════╝");
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 : 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());
double distance = diff.length();
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++) {
double t = (double) i / steps;
Location p = from.clone().add(diff.clone().multiply(t));
Location head = p.clone().add(0, 1.4, 0);
Location base = from.clone().add(diff.clone().multiply(t));
for (double dy : Y_OFFSETS) {
Location check = base.clone().add(0, dy, 0);
// In der 2. Halbzeit sind die Seiten getauscht → Tore umkehren
if (!secondHalf) {
if (arena.isInRedGoal(p) || arena.isInRedGoal(head)) return Team.BLUE;
if (arena.isInBlueGoal(p) || arena.isInBlueGoal(head)) return Team.RED;
if (arena.isInRedGoal(check)) return Team.BLUE;
if (arena.isInBlueGoal(check)) return Team.RED;
} else {
if (arena.isInRedGoal(p) || arena.isInRedGoal(head)) return Team.RED;
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;
@@ -910,25 +946,47 @@ public class Game {
}
}
case "redEnd" -> {
if (touchTeam == Team.RED) {
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
throwInTeam = Team.BLUE;
message = "§e⚽ §7Ball im Aus! §9Ecke für Blaues Team§7!";
// Korrekte Fußball-Regel:
// Letzter Kontakt durch VERTEIDIGER an eigener Torlinie → ECKE für Angreifer
// Letzter Kontakt durch ANGREIFER an gegnerischer Torlinie → ABSTOSS für Verteidiger
// 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 {
resumeLocation = moveInsideField(arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn(), 1.25);
throwInTeam = Team.RED;
message = "§e⚽ §7Ball im Aus! §cAbstoß für Rotes Team§7!";
// Angreifer (oder unbekannt) hat den Ball ins Aus geschossen → ABSTOSS für Verteidiger
// Ball ~5,5 Blöcke vor der Torlinie (5-Meter-Raum), Feldmitte
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" -> {
if (touchTeam == Team.BLUE) {
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
throwInTeam = Team.RED;
message = "§e⚽ §7Ball im Aus! §cEcke für Rotes Team§7!";
// analog zu redEnd.
// 1. Halbzeit: BLAU verteidigt diese Seite (blueEnd = blaues Tor)
// 2. Halbzeit: ROT verteidigt diese Seite (Seitenwechsel)
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 {
resumeLocation = moveInsideField(arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn(), 1.25);
throwInTeam = Team.BLUE;
message = "§e⚽ §7Ball im Aus! §9Abstoß für Blaues Team§7!";
// Angreifer (oder unbekannt) → ABSTOSS für Verteidiger
resumeLocation = moveInsideField(getGoalKickSpawnLocation(false), 1.25);
throwInTeam = defenderHere;
String teamStr = defenderHere == Team.RED ? "§cRotes Team" : "§9Blaues Team";
message = "§e⚽ §7Ball im Aus! §7Abstoß für " + teamStr + "§7!";
}
}
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); }
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;
new BukkitRunnable() {
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);
}
/**
* 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) {
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return arena.getBallSpawn();
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX());
@@ -1019,8 +1200,8 @@ public class Game {
}
}
throwInTeam = null;
setLastKicker(uuid); // korrekt: nutzt setLastKicker statt direktem Feldzugriff
clearThrowIn(); // setzt lastKickWasRestart=true falls Einwurf/Restart war → kein Abseits für Empfänger (Regel 11 §3)
setLastKicker(uuid);
ball.kick(p);
break; // pro Tick max. 1 Auto-Kick
}
@@ -1370,30 +1551,43 @@ public class Game {
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-foul", 5));
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 inBluePenalty = arena.isInBluePenaltyArea(foulLocation);
boolean penaltyKick = false;
if (inRedPenalty && victimTeam == Team.BLUE) {
// Foul an Blau im roten Strafraum → Elfmeter für Blau
boolean penaltyForBlue, penaltyForRed;
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"));
for (UUID uuid : getAllAndSpectators()) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§9Blaues Team§7 schießt!", 5, 50, 10);
}
penaltyKick = true;
// Elfmeter als Freistoß direkt auf Ballspawn (ggf. später: separater Elfmeter-Punkt)
startFreekick(Team.BLUE, arena.getBallSpawn(), "Elfmeter");
} else if (inBluePenalty && victimTeam == Team.RED) {
// Foul an Rot im blauen Strafraum → Elfmeter für Rot
Location penSpot = arena.getPenaltySpot(Team.BLUE);
startFreekick(Team.BLUE, penSpot != null ? penSpot : arena.getBallSpawn(), "Elfmeter");
} else if (penaltyForRed) {
broadcastAll(Messages.get("foul-penalty", "team", "§cRotes Team"));
for (UUID uuid : getAllAndSpectators()) {
Player p = Bukkit.getPlayer(uuid);
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§cRotes Team§7 schießt!", 5, 50, 10);
}
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) {
@@ -1447,8 +1641,18 @@ public class Game {
broadcastAll(Messages.get("freekick-hint", "n", String.format("%.0f", dist)));
}
/**
* Erzwingt den korrekten Abstand für die jeweilige Spielfortsetzung (Regel 1317).
* 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() {
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);
Team opposingTeam = throwInTeam.getOpponent();
List<UUID> opponents = opposingTeam == Team.RED ? redTeam : blueTeam;
@@ -1843,6 +2047,7 @@ public class Game {
lastKicker = null;
secondLastKicker = null;
lastKickWasHeader = false;
lastKickWasRestart = false;
secondHalf = false;
updateGoalBeaconColors();
@@ -2121,7 +2326,8 @@ public class Game {
Player newKicker = Bukkit.getPlayer(uuid);
if (prevKicker != null && newKicker != null) {
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
String msg = "§7⚽ §eLangpass §7von §f" + prevKicker.getName()
+ " §7zu §f" + newKicker.getName()
@@ -2144,10 +2350,16 @@ public class Game {
if (p != null) lastTouchTeam = getTeam(p);
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)
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
&& ball != null && ball.getEntity() != null && !offsideCooldown) {
&& ball != null && ball.getEntity() != null && !offsideCooldown
&& !skipOffside) {
checkOffside(uuid, ball.getEntity().getLocation());
}
}
@@ -2163,9 +2375,13 @@ public class Game {
if (p != null) lastTouchTeam = getTeam(p);
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)
&& (state == GameState.RUNNING || state == GameState.OVERTIME)
&& ball != null && ball.getEntity() != null && !offsideCooldown) {
&& ball != null && ball.getEntity() != null && !offsideCooldown
&& !skipOffside) {
checkOffside(uuid, ball.getEntity().getLocation());
}
}
@@ -2201,6 +2417,8 @@ public class Game {
public boolean isLastKickWasHeader() { return lastKickWasHeader; }
/** Berechtigung aufheben wird von BallListener nach dem ersten Schuss gerufen */
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;
freekickLocation = null;
freekickTicks = 0;

View File

@@ -1,5 +1,5 @@
name: Fussball
version: 1.0.3
version: 1.0.4
main: de.fussball.plugin.Fussball
api-version: 1.21
author: M_Viper