Update from Git Manager GUI
This commit is contained in:
@@ -7,6 +7,7 @@ import de.fussball.plugin.game.GameManager;
|
|||||||
import de.fussball.plugin.hologram.HologramManager;
|
import de.fussball.plugin.hologram.HologramManager;
|
||||||
import de.fussball.plugin.listeners.*;
|
import de.fussball.plugin.listeners.*;
|
||||||
import de.fussball.plugin.placeholders.FussballPlaceholders;
|
import de.fussball.plugin.placeholders.FussballPlaceholders;
|
||||||
|
import de.fussball.plugin.stats.MatchHistory;
|
||||||
import de.fussball.plugin.stats.StatsManager;
|
import de.fussball.plugin.stats.StatsManager;
|
||||||
import de.fussball.plugin.utils.Messages;
|
import de.fussball.plugin.utils.Messages;
|
||||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||||
@@ -20,6 +21,7 @@ public class Fussball extends JavaPlugin {
|
|||||||
private StatsManager statsManager;
|
private StatsManager statsManager;
|
||||||
private SignListener signListener;
|
private SignListener signListener;
|
||||||
private HologramManager hologramManager;
|
private HologramManager hologramManager;
|
||||||
|
private MatchHistory matchHistory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
@@ -33,6 +35,7 @@ public class Fussball extends JavaPlugin {
|
|||||||
statsManager = new StatsManager(this);
|
statsManager = new StatsManager(this);
|
||||||
signListener = new SignListener(this);
|
signListener = new SignListener(this);
|
||||||
hologramManager = new HologramManager(this);
|
hologramManager = new HologramManager(this);
|
||||||
|
matchHistory = new MatchHistory(this);
|
||||||
Messages.init(this);
|
Messages.init(this);
|
||||||
|
|
||||||
registerCommands();
|
registerCommands();
|
||||||
@@ -76,4 +79,5 @@ public class Fussball extends JavaPlugin {
|
|||||||
public StatsManager getStatsManager() { return statsManager; }
|
public StatsManager getStatsManager() { return statsManager; }
|
||||||
public SignListener getSignListener() { return signListener; }
|
public SignListener getSignListener() { return signListener; }
|
||||||
public HologramManager getHologramManager() { return hologramManager; }
|
public HologramManager getHologramManager() { return hologramManager; }
|
||||||
|
public MatchHistory getMatchHistory() { return matchHistory; }
|
||||||
}
|
}
|
||||||
@@ -93,20 +93,20 @@ public class Arena implements ConfigurationSerializable {
|
|||||||
if (Math.abs(dir.getZ()) > Math.abs(dir.getX())) {
|
if (Math.abs(dir.getZ()) > Math.abs(dir.getX())) {
|
||||||
// Feld läuft entlang Z-Achse
|
// Feld läuft entlang Z-Achse
|
||||||
if (isRedGoal) {
|
if (isRedGoal) {
|
||||||
if (redAxis < blueAxis) maxZ = Math.max(maxZ, maxZ + depth);
|
if (redAxis < blueAxis) maxZ += depth;
|
||||||
else minZ = Math.min(minZ, minZ - depth);
|
else minZ -= depth;
|
||||||
} else {
|
} else {
|
||||||
if (blueAxis > redAxis) minZ = Math.min(minZ, minZ - depth);
|
if (blueAxis > redAxis) minZ -= depth;
|
||||||
else maxZ = Math.max(maxZ, maxZ + depth);
|
else maxZ += depth;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Feld läuft entlang X-Achse
|
// Feld läuft entlang X-Achse
|
||||||
if (isRedGoal) {
|
if (isRedGoal) {
|
||||||
if (redAxis < blueAxis) maxX = Math.max(maxX, maxX + depth);
|
if (redAxis < blueAxis) maxX += depth;
|
||||||
else minX = Math.min(minX, minX - depth);
|
else minX -= depth;
|
||||||
} else {
|
} else {
|
||||||
if (blueAxis > redAxis) minX = Math.min(minX, minX - depth);
|
if (blueAxis > redAxis) minX -= depth;
|
||||||
else maxX = Math.max(maxX, maxX + depth);
|
else maxX += depth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import de.fussball.plugin.arena.Arena;
|
|||||||
import de.fussball.plugin.game.Ball;
|
import de.fussball.plugin.game.Ball;
|
||||||
import de.fussball.plugin.game.Game;
|
import de.fussball.plugin.game.Game;
|
||||||
import de.fussball.plugin.game.GameState;
|
import de.fussball.plugin.game.GameState;
|
||||||
|
import de.fussball.plugin.game.Team;
|
||||||
import de.fussball.plugin.hologram.FussballHologram;
|
import de.fussball.plugin.hologram.FussballHologram;
|
||||||
import de.fussball.plugin.hologram.HologramManager;
|
import de.fussball.plugin.hologram.HologramManager;
|
||||||
|
import de.fussball.plugin.stats.MatchHistory;
|
||||||
import de.fussball.plugin.stats.StatsManager;
|
import de.fussball.plugin.stats.StatsManager;
|
||||||
import de.fussball.plugin.utils.MessageUtil;
|
import de.fussball.plugin.utils.MessageUtil;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@@ -202,6 +204,82 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
handleDebug(player, arena);
|
handleDebug(player, arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Teamwahl ─────────────────────────────────────────────────────
|
||||||
|
case "team" -> {
|
||||||
|
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||||
|
if (args.length < 2) {
|
||||||
|
player.sendMessage(MessageUtil.error("Benutze: /fb team rot|blau"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Game game = plugin.getGameManager().getPlayerGame(player);
|
||||||
|
if (game == null) {
|
||||||
|
// Noch nicht im Spiel – Wunsch für nächstes Beitreten speichern
|
||||||
|
// (Spiel muss noch gesucht werden – Wunsch für alle Spiele gilt nicht;
|
||||||
|
// Spieler muss zuerst beitreten)
|
||||||
|
player.sendMessage(MessageUtil.warn("Du bist in keinem Spiel. Tritt zuerst mit /fb join <arena> bei."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (game.getState() != GameState.WAITING && game.getState() != GameState.STARTING) {
|
||||||
|
player.sendMessage(MessageUtil.error("Teamwahl ist nur vor Spielstart möglich!"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Team desired = switch (args[1].toLowerCase()) {
|
||||||
|
case "rot", "red", "r" -> Team.RED;
|
||||||
|
case "blau", "blue", "b" -> Team.BLUE;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
if (desired == null) {
|
||||||
|
player.sendMessage(MessageUtil.error("Ungültiges Team! Benutze: rot oder blau"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
game.requestTeam(player, desired);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Match-History ────────────────────────────────────────────────
|
||||||
|
case "history" -> {
|
||||||
|
int count = 5;
|
||||||
|
if (args.length >= 2) { try { count = Integer.parseInt(args[1]); } catch (NumberFormatException ignored) {} }
|
||||||
|
count = Math.max(1, Math.min(count, 20));
|
||||||
|
List<Map<?, ?>> matches = plugin.getMatchHistory().getMatches(count);
|
||||||
|
sender.sendMessage(MessageUtil.header("📋 Match-History (" + matches.size() + " Einträge)"));
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
sender.sendMessage(MessageUtil.warn("Noch keine Spiele gespeichert."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < matches.size(); i++) {
|
||||||
|
Map<?, ?> raw = matches.get(i);
|
||||||
|
// Sicher auslesen über Object → String/Number-Cast
|
||||||
|
String date = raw.get("date") instanceof String s ? s : "?";
|
||||||
|
String arena = raw.get("arena") instanceof String s ? s : "?";
|
||||||
|
String winner = raw.get("winner") instanceof String s ? s : "Unentschieden";
|
||||||
|
int rs = raw.get("redScore") instanceof Number n ? n.intValue() : 0;
|
||||||
|
int bs = raw.get("blueScore") instanceof Number n ? n.intValue() : 0;
|
||||||
|
int rp = raw.get("redPoss") instanceof Number n ? n.intValue() : 50;
|
||||||
|
int bp = raw.get("bluePoss") instanceof Number n ? n.intValue() : 50;
|
||||||
|
int pr = raw.get("penaltyRed") instanceof Number n ? n.intValue() : 0;
|
||||||
|
int pb = raw.get("penaltyBlue") instanceof Number n ? n.intValue() : 0;
|
||||||
|
String penStr = (pr + pb > 0) ? " §8(Elfm. §c" + pr + "§8:§9" + pb + "§8)" : "";
|
||||||
|
|
||||||
|
String winColor = winner.equals("Rot") ? "§c" : winner.equals("Blau") ? "§9" : "§7";
|
||||||
|
sender.sendMessage("§e#" + (i+1) + " §8[" + date + "] §7" + arena
|
||||||
|
+ " §c" + rs + "§7:§9" + bs + penStr
|
||||||
|
+ " §8│ " + winColor + winner
|
||||||
|
+ " §8│ §7Besitz §c" + rp + "% §7/ §9" + bp + "%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drop Ball (Admin) ─────────────────────────────────────────────
|
||||||
|
case "dropball" -> {
|
||||||
|
if (!sender.hasPermission("fussball.admin")) { sender.sendMessage(MessageUtil.error("Keine Berechtigung!")); return true; }
|
||||||
|
if (args.length < 2) { sender.sendMessage(MessageUtil.error("Benutze: /fb dropball <arena>")); return true; }
|
||||||
|
Game dropGame = plugin.getGameManager().getGame(args[1]);
|
||||||
|
if (dropGame == null) { sender.sendMessage(MessageUtil.error("Kein aktives Spiel in §e" + args[1] + "§c!")); return true; }
|
||||||
|
Location dropLoc = null;
|
||||||
|
if (sender instanceof Player p) dropLoc = p.getLocation();
|
||||||
|
dropGame.dropBall(dropLoc);
|
||||||
|
sender.sendMessage(MessageUtil.success("Schiedsrichterball ausgeführt!"));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Hologramm-Verwaltung ─────────────────────────────────────────
|
// ── Hologramm-Verwaltung ─────────────────────────────────────────
|
||||||
// /fb hologram set <id> goals|wins – Hologramm erstellen
|
// /fb hologram set <id> goals|wins – Hologramm erstellen
|
||||||
// /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
// /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
||||||
@@ -302,9 +380,21 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "redpenaltyspot" -> { arena.setRedPenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Roter Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
case "redpenaltyspot" -> { arena.setRedPenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Roter Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
||||||
case "bluepenaltyspot" -> { arena.setBluePenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Blauer Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
case "bluepenaltyspot" -> { arena.setBluePenaltySpot(player.getLocation()); player.sendMessage(MessageUtil.success("Blauer Elfmeter-Punkt gesetzt: " + locStr(player.getLocation()))); }
|
||||||
case "spectatorspawn" -> { arena.setSpectatorSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Zuschauer-Spawn gesetzt: " + locStr(player.getLocation()))); }
|
case "spectatorspawn" -> { arena.setSpectatorSpawn(player.getLocation()); player.sendMessage(MessageUtil.success("Zuschauer-Spawn 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 "minplayers" -> {
|
||||||
case "maxplayers" -> { if (args.length < 4) return; arena.setMaxPlayers(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Max-Spieler: §e" + args[3])); }
|
if (args.length < 4) return;
|
||||||
case "duration" -> { if (args.length < 4) return; arena.setGameDuration(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Spieldauer: §e" + args[3] + "s")); }
|
try { arena.setMinPlayers(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Min-Spieler: §e" + args[3])); }
|
||||||
|
catch (NumberFormatException ex) { player.sendMessage(MessageUtil.error("§e" + args[3] + " §cist keine gültige Zahl!")); }
|
||||||
|
}
|
||||||
|
case "maxplayers" -> {
|
||||||
|
if (args.length < 4) return;
|
||||||
|
try { arena.setMaxPlayers(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Max-Spieler: §e" + args[3])); }
|
||||||
|
catch (NumberFormatException ex) { player.sendMessage(MessageUtil.error("§e" + args[3] + " §cist keine gültige Zahl!")); }
|
||||||
|
}
|
||||||
|
case "duration" -> {
|
||||||
|
if (args.length < 4) return;
|
||||||
|
try { arena.setGameDuration(Integer.parseInt(args[3])); player.sendMessage(MessageUtil.success("Spieldauer: §e" + args[3] + "s")); }
|
||||||
|
catch (NumberFormatException ex) { player.sendMessage(MessageUtil.error("§e" + args[3] + " §cist keine gültige Zahl!")); }
|
||||||
|
}
|
||||||
case "info" -> {
|
case "info" -> {
|
||||||
player.sendMessage(MessageUtil.header("Arena: " + arena.getName()));
|
player.sendMessage(MessageUtil.header("Arena: " + arena.getName()));
|
||||||
player.sendMessage("§7 Lobby: " + check(arena.getLobby()));
|
player.sendMessage("§7 Lobby: " + check(arena.getLobby()));
|
||||||
@@ -388,11 +478,13 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
s.sendMessage("§e/fb join <arena> §7- Spiel beitreten");
|
s.sendMessage("§e/fb join <arena> §7- Spiel beitreten");
|
||||||
s.sendMessage("§e/fb leave §7- Spiel / Zuschauer verlassen");
|
s.sendMessage("§e/fb leave §7- Spiel / Zuschauer verlassen");
|
||||||
s.sendMessage("§e/fb spectate <arena> §7- Spiel zuschauen");
|
s.sendMessage("§e/fb spectate <arena> §7- Spiel zuschauen");
|
||||||
|
s.sendMessage("§e/fb team rot|blau §7- Teamwahl (vor Spielstart)");
|
||||||
s.sendMessage("§e/fb list §7- Arenen anzeigen");
|
s.sendMessage("§e/fb list §7- Arenen anzeigen");
|
||||||
s.sendMessage("§e/fb stats [spieler] §7- Statistiken anzeigen");
|
s.sendMessage("§e/fb stats [spieler] §7- Statistiken anzeigen");
|
||||||
s.sendMessage("§e/fb top [goals|wins] §7- Bestenliste");
|
s.sendMessage("§e/fb top [goals|wins] §7- Bestenliste");
|
||||||
|
s.sendMessage("§e/fb history [n] §7- Letzte Spiele anzeigen");
|
||||||
if (s.hasPermission("fussball.admin")) {
|
if (s.hasPermission("fussball.admin")) {
|
||||||
s.sendMessage("§c§lAdmin: §ccreate / delete / setup / stop / debug");
|
s.sendMessage("§c§lAdmin: §ccreate / delete / setup / stop / debug / dropball");
|
||||||
s.sendMessage("§c§lAdmin: §chologram set goals|wins / remove / reload");
|
s.sendMessage("§c§lAdmin: §chologram set goals|wins / remove / reload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,9 +510,9 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
|
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args) {
|
||||||
List<String> list = new ArrayList<>();
|
List<String> list = new ArrayList<>();
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
list.addAll(List.of("join", "leave", "list", "stats", "top", "spectate"));
|
list.addAll(List.of("join", "leave", "list", "stats", "top", "spectate", "team", "history"));
|
||||||
if (sender.hasPermission("fussball.admin")) list.addAll(List.of("create", "delete", "setup", "stop", "setgk", "debug", "hologram"));
|
if (sender.hasPermission("fussball.admin")) list.addAll(List.of("create", "delete", "setup", "stop", "setgk", "debug", "hologram", "dropball"));
|
||||||
} else if (args.length == 2 && List.of("join","delete","setup","stop","setgk","debug","spectate").contains(args[0].toLowerCase())) {
|
} else if (args.length == 2 && List.of("join","delete","setup","stop","setgk","debug","spectate","dropball").contains(args[0].toLowerCase())) {
|
||||||
list.addAll(plugin.getArenaManager().getArenaNames());
|
list.addAll(plugin.getArenaManager().getArenaNames());
|
||||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("hologram")) {
|
} else if (args.length == 2 && args[0].equalsIgnoreCase("hologram")) {
|
||||||
list.addAll(List.of("set", "remove", "delete", "reload", "list"));
|
list.addAll(List.of("set", "remove", "delete", "reload", "list"));
|
||||||
@@ -448,6 +540,8 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
"minplayers","maxplayers","duration","info"));
|
"minplayers","maxplayers","duration","info"));
|
||||||
} else if (args.length == 2 && args[0].equalsIgnoreCase("top")) {
|
} else if (args.length == 2 && args[0].equalsIgnoreCase("top")) {
|
||||||
list.addAll(List.of("goals", "wins"));
|
list.addAll(List.of("goals", "wins"));
|
||||||
|
} else if (args.length == 2 && args[0].equalsIgnoreCase("team")) {
|
||||||
|
list.addAll(List.of("rot", "blau"));
|
||||||
}
|
}
|
||||||
String input = args[args.length - 1].toLowerCase();
|
String input = args[args.length - 1].toLowerCase();
|
||||||
list.removeIf(s -> !s.toLowerCase().startsWith(input));
|
list.removeIf(s -> !s.toLowerCase().startsWith(input));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.fussball.plugin.game;
|
|||||||
import de.fussball.plugin.Fussball;
|
import de.fussball.plugin.Fussball;
|
||||||
import de.fussball.plugin.arena.Arena;
|
import de.fussball.plugin.arena.Arena;
|
||||||
import de.fussball.plugin.scoreboard.FussballScoreboard;
|
import de.fussball.plugin.scoreboard.FussballScoreboard;
|
||||||
|
import de.fussball.plugin.stats.MatchHistory;
|
||||||
import de.fussball.plugin.stats.StatsManager;
|
import de.fussball.plugin.stats.StatsManager;
|
||||||
import de.fussball.plugin.utils.MessageUtil;
|
import de.fussball.plugin.utils.MessageUtil;
|
||||||
import de.fussball.plugin.utils.Messages;
|
import de.fussball.plugin.utils.Messages;
|
||||||
@@ -47,6 +48,23 @@ public class Game {
|
|||||||
// ── Statistik ──────────────────────────────────────────────────────────
|
// ── Statistik ──────────────────────────────────────────────────────────
|
||||||
private final Map<UUID, Integer> goals = new HashMap<>();
|
private final Map<UUID, Integer> goals = new HashMap<>();
|
||||||
private final Map<UUID, Integer> kicks = new HashMap<>();
|
private final Map<UUID, Integer> kicks = new HashMap<>();
|
||||||
|
|
||||||
|
// ── Ballbesitz-Tracking ────────────────────────────────────────────────
|
||||||
|
/** Ticks, die Rot in Ballbesitz war (letzter Berührungs-Team = Rot) */
|
||||||
|
private long redPossessionTicks = 0;
|
||||||
|
/** Ticks, die Blau in Ballbesitz war */
|
||||||
|
private long bluePossessionTicks = 0;
|
||||||
|
/** Gesamtticks mit bekanntem Besitz */
|
||||||
|
private long totalPossessionTicks = 0;
|
||||||
|
|
||||||
|
// ── Teamwahl durch Spieler ─────────────────────────────────────────────
|
||||||
|
/** Spieler → gewünschtes Team (gesetzt per /fb team red|blue vor Spielstart) */
|
||||||
|
private final Map<UUID, Team> requestedTeam = new HashMap<>();
|
||||||
|
|
||||||
|
// ── Pass-Tracking ──────────────────────────────────────────────────────
|
||||||
|
/** Position des Balls beim letzten Schuss – für Kurz-/Langpass-Erkennung */
|
||||||
|
private Location lastKickLocation = null;
|
||||||
|
private static final double LONG_PASS_DISTANCE = 20.0;
|
||||||
|
|
||||||
// ── FEHLENDE VARIABLE ────────────────────────────────────────────────
|
// ── FEHLENDE VARIABLE ────────────────────────────────────────────────
|
||||||
private final Map<UUID, Integer> outOfBoundsCountdown = new HashMap<>();
|
private final Map<UUID, Integer> outOfBoundsCountdown = new HashMap<>();
|
||||||
@@ -76,6 +94,8 @@ public class Game {
|
|||||||
private int spawnCooldown = 0;
|
private int spawnCooldown = 0;
|
||||||
private int ballMissingTicks = 0; // Ticks ohne lebenden Ball → Respawn
|
private int ballMissingTicks = 0; // Ticks ohne lebenden Ball → Respawn
|
||||||
private static final int BALL_MISSING_TIMEOUT = 80; // 4s
|
private static final int BALL_MISSING_TIMEOUT = 80; // 4s
|
||||||
|
/** Ticks, in denen der Anstoß-Kreis erzwungen wird (Gegner müssen Abstand halten) */
|
||||||
|
private int kickoffEnforceTicks = 0;
|
||||||
|
|
||||||
// ── Torwart ────────────────────────────────────────────────────────────
|
// ── Torwart ────────────────────────────────────────────────────────────
|
||||||
private UUID redGoalkeeper = null;
|
private UUID redGoalkeeper = null;
|
||||||
@@ -204,17 +224,48 @@ public class Game {
|
|||||||
|
|
||||||
// ── Team-Zuweisung ───────────────────────────────────────────────────────
|
// ── Team-Zuweisung ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Auto-Balance: immer ins kleinere Team */
|
/** Auto-Balance: immer ins kleinere Team, sofern keine Teamwahl vorliegt */
|
||||||
private void assignTeam(Player player) {
|
private void assignTeam(Player player) {
|
||||||
if (redTeam.size() <= blueTeam.size()) {
|
Team preferred = requestedTeam.remove(player.getUniqueId());
|
||||||
|
Team assigned;
|
||||||
|
|
||||||
|
if (preferred != null) {
|
||||||
|
// Gewünschtes Team: nur wenn es nicht deutlich größer ist (max 1 mehr)
|
||||||
|
int redSize = redTeam.size();
|
||||||
|
int blueSize = blueTeam.size();
|
||||||
|
boolean canJoinPreferred = (preferred == Team.RED && redSize <= blueSize + 1)
|
||||||
|
|| (preferred == Team.BLUE && blueSize <= redSize + 1);
|
||||||
|
if (canJoinPreferred) {
|
||||||
|
assigned = preferred;
|
||||||
|
player.sendMessage("§a⚽ Du spielst wie gewünscht im " + preferred.getColorCode() + preferred.getDisplayName() + "§a Team!");
|
||||||
|
} else {
|
||||||
|
assigned = (redTeam.size() <= blueTeam.size()) ? Team.RED : Team.BLUE;
|
||||||
|
player.sendMessage(MessageUtil.warn("Dein Wunsch-Team war zu groß – du wurdest automatisch zugeteilt."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assigned = (redTeam.size() <= blueTeam.size()) ? Team.RED : Team.BLUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assigned == Team.RED) {
|
||||||
redTeam.add(player.getUniqueId());
|
redTeam.add(player.getUniqueId());
|
||||||
player.sendMessage(MessageUtil.success("Du bist im §cRoten Team§a!"));
|
if (preferred == null) player.sendMessage(MessageUtil.success("Du bist im §cRoten Team§a!"));
|
||||||
} else {
|
} else {
|
||||||
blueTeam.add(player.getUniqueId());
|
blueTeam.add(player.getUniqueId());
|
||||||
player.sendMessage(MessageUtil.success("Du bist im §9Blauen Team§a!"));
|
if (preferred == null) player.sendMessage(MessageUtil.success("Du bist im §9Blauen Team§a!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt das Wunsch-Team für den nächsten Spielbeitritt.
|
||||||
|
* Nur im WAITING/STARTING-Zustand sinnvoll.
|
||||||
|
*/
|
||||||
|
public void requestTeam(Player player, Team team) {
|
||||||
|
requestedTeam.put(player.getUniqueId(), team);
|
||||||
|
String c = team == Team.RED ? "§c" : "§9";
|
||||||
|
player.sendMessage(MessageUtil.info("Teamwunsch gesetzt: " + c + team.getDisplayName()
|
||||||
|
+ "§7. Wird beim nächsten Spielstart berücksichtigt (wenn möglich)."));
|
||||||
|
}
|
||||||
|
|
||||||
// ── Spieler vorbereiten / zurücksetzen ───────────────────────────────────
|
// ── Spieler vorbereiten / zurücksetzen ───────────────────────────────────
|
||||||
|
|
||||||
private void preparePlayer(Player player) {
|
private void preparePlayer(Player player) {
|
||||||
@@ -231,17 +282,31 @@ public class Game {
|
|||||||
|
|
||||||
private void applyTeamColors(Player player, Team team) {
|
private void applyTeamColors(Player player, Team team) {
|
||||||
org.bukkit.Color color = team == Team.RED ? org.bukkit.Color.RED : org.bukkit.Color.BLUE;
|
org.bukkit.Color color = team == Team.RED ? org.bukkit.Color.RED : org.bukkit.Color.BLUE;
|
||||||
|
String teamCode = team == Team.RED ? "§c" : "§9";
|
||||||
|
|
||||||
|
// Trikot-Nummer: Position im Team-Array (1-basiert)
|
||||||
|
List<UUID> teamList = team == Team.RED ? redTeam : blueTeam;
|
||||||
|
int jerseyNum = teamList.indexOf(player.getUniqueId()) + 1;
|
||||||
|
String numStr = jerseyNum > 0 ? " §f#" + jerseyNum : "";
|
||||||
|
|
||||||
ItemStack[] armor = {
|
ItemStack[] armor = {
|
||||||
new ItemStack(Material.LEATHER_HELMET), new ItemStack(Material.LEATHER_CHESTPLATE),
|
new ItemStack(Material.LEATHER_HELMET), new ItemStack(Material.LEATHER_CHESTPLATE),
|
||||||
new ItemStack(Material.LEATHER_LEGGINGS), new ItemStack(Material.LEATHER_BOOTS)
|
new ItemStack(Material.LEATHER_LEGGINGS), new ItemStack(Material.LEATHER_BOOTS)
|
||||||
};
|
};
|
||||||
for (ItemStack item : armor) {
|
for (ItemStack item : armor) {
|
||||||
if (item.getItemMeta() instanceof org.bukkit.inventory.meta.LeatherArmorMeta meta) {
|
if (item.getItemMeta() instanceof org.bukkit.inventory.meta.LeatherArmorMeta meta) {
|
||||||
meta.setColor(color); item.setItemMeta(meta);
|
meta.setColor(color);
|
||||||
|
// Trikot-Nummer auf dem Brustpanzer anzeigen
|
||||||
|
if (item.getType() == Material.LEATHER_CHESTPLATE) {
|
||||||
|
meta.setDisplayName(teamCode + team.getDisplayName() + numStr);
|
||||||
|
}
|
||||||
|
item.setItemMeta(meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.getInventory().setHelmet(armor[0]); player.getInventory().setChestplate(armor[1]);
|
player.getInventory().setHelmet(armor[0]);
|
||||||
player.getInventory().setLeggings(armor[2]); player.getInventory().setBoots(armor[3]);
|
player.getInventory().setChestplate(armor[1]);
|
||||||
|
player.getInventory().setLeggings(armor[2]);
|
||||||
|
player.getInventory().setBoots(armor[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Setzt einen Spieler oder Zuschauer zurück (Lobby, ADVENTURE, Inventar leer) */
|
/** Setzt einen Spieler oder Zuschauer zurück (Lobby, ADVENTURE, Inventar leer) */
|
||||||
@@ -619,11 +684,15 @@ public class Game {
|
|||||||
int itLeft = injuryTimeBuffer;
|
int itLeft = injuryTimeBuffer;
|
||||||
if (!inInjuryTime) {
|
if (!inInjuryTime) {
|
||||||
inInjuryTime = true;
|
inInjuryTime = true;
|
||||||
int mins = (int) Math.ceil((itLeft + 1) / 60.0);
|
// BUG FIX: tatsächliche Sekunden anzeigen statt gerundete Minuten
|
||||||
broadcastAll(Messages.get("injury-time", "n", String.valueOf(mins)));
|
int totalSec = itLeft + 1;
|
||||||
|
String injDisplay = totalSec >= 60
|
||||||
|
? String.format("+%d:%02d", totalSec / 60, totalSec % 60)
|
||||||
|
: "+" + totalSec + "s";
|
||||||
|
broadcastAll(Messages.get("injury-time", "n", injDisplay));
|
||||||
for (UUID uuid : allPlayers) {
|
for (UUID uuid : allPlayers) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) p.sendTitle("§c⏱", "§8+" + (itLeft + 1) + "s Nachspielzeit", 5, 30, 5);
|
if (p != null) p.sendTitle("§c⏱", "§8" + injDisplay + " Nachspielzeit", 5, 30, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scoreboard.updateAll(); updateBossBar(); return;
|
scoreboard.updateAll(); updateBossBar(); return;
|
||||||
@@ -641,6 +710,12 @@ public class Game {
|
|||||||
checkHeaderOpportunities();
|
checkHeaderOpportunities();
|
||||||
checkAfkPlayers();
|
checkAfkPlayers();
|
||||||
|
|
||||||
|
// Anstoß-Kreis: Gegner fernhalten wenn Anstoß läuft
|
||||||
|
if (kickoffEnforceTicks > 0) {
|
||||||
|
kickoffEnforceTicks--;
|
||||||
|
enforceKickoffCircle();
|
||||||
|
}
|
||||||
|
|
||||||
// Freistoß-Abstandsdurchsetzung
|
// Freistoß-Abstandsdurchsetzung
|
||||||
if (freekickLocation != null) {
|
if (freekickLocation != null) {
|
||||||
freekickTicks--;
|
freekickTicks--;
|
||||||
@@ -662,11 +737,14 @@ public class Game {
|
|||||||
injuryTimeBuffer--;
|
injuryTimeBuffer--;
|
||||||
if (!inInjuryTime) {
|
if (!inInjuryTime) {
|
||||||
inInjuryTime = true;
|
inInjuryTime = true;
|
||||||
int mins = (int) Math.ceil((injuryTimeBuffer + 1) / 60.0);
|
int totalSec = injuryTimeBuffer + 1;
|
||||||
broadcastAll(Messages.get("injury-time", "n", String.valueOf(mins)));
|
String injDisplay = totalSec >= 60
|
||||||
|
? String.format("+%d:%02d", totalSec / 60, totalSec % 60)
|
||||||
|
: "+" + totalSec + "s";
|
||||||
|
broadcastAll(Messages.get("injury-time", "n", injDisplay));
|
||||||
for (UUID uuid : allPlayers) {
|
for (UUID uuid : allPlayers) {
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
if (p != null) p.sendTitle("§c+"+mins+"'", "§8Nachspielzeit!", 5, 30, 5);
|
if (p != null) p.sendTitle("§c" + injDisplay, "§8Nachspielzeit!", 5, 30, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scoreboard.updateAll(); updateBossBar(); return;
|
scoreboard.updateAll(); updateBossBar(); return;
|
||||||
@@ -710,6 +788,15 @@ public class Game {
|
|||||||
}
|
}
|
||||||
ballMissingTicks = 0;
|
ballMissingTicks = 0;
|
||||||
|
|
||||||
|
// ── Ballbesitz-Tracking ──────────────────────────────────────
|
||||||
|
if (state == GameState.RUNNING || state == GameState.OVERTIME) {
|
||||||
|
if (lastTouchTeam != null) {
|
||||||
|
totalPossessionTicks++;
|
||||||
|
if (lastTouchTeam == Team.RED) redPossessionTicks++;
|
||||||
|
else bluePossessionTicks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Torwart: Ball-Position aktualisieren ─────────────────────
|
// ── Torwart: Ball-Position aktualisieren ─────────────────────
|
||||||
if (ball.isHeld()) {
|
if (ball.isHeld()) {
|
||||||
ball.updateHeldPosition();
|
ball.updateHeldPosition();
|
||||||
@@ -877,6 +964,24 @@ public class Game {
|
|||||||
if (ball.getDistanceTo(p) >= 2.2) continue;
|
if (ball.getDistanceTo(p) >= 2.2) continue;
|
||||||
if (throwInTeam != null && getTeam(p) != throwInTeam) continue;
|
if (throwInTeam != null && getTeam(p) != throwInTeam) continue;
|
||||||
|
|
||||||
|
// ── Handball-Erkennung ────────────────────────────────────────────
|
||||||
|
// Ein kauernder Spieler, der den Ball berührt, gilt als Handspiel.
|
||||||
|
// (Kauern = Arme seitlich gestreckt in Minecraft-Interpretation)
|
||||||
|
if (plugin.getConfig().getBoolean("gameplay.handball-enabled", true) && p.isSneaking()) {
|
||||||
|
Location ballLoc = ball.getEntity() != null ? ball.getEntity().getLocation() : null;
|
||||||
|
if (ballLoc != null) {
|
||||||
|
double relHeight = ballLoc.getY() - p.getLocation().getY();
|
||||||
|
// Ball auf "Arm-Höhe" (0.5 – 1.5 Blöcke) + Spieler kauert = Handball
|
||||||
|
if (relHeight >= 0.5 && relHeight <= 1.5) {
|
||||||
|
Team victimTeam = getTeam(p).getOpponent();
|
||||||
|
boolean inPenalty = (getTeam(p) == Team.RED && arena.isInRedPenaltyArea(p.getLocation()))
|
||||||
|
|| (getTeam(p) == Team.BLUE && arena.isInBluePenaltyArea(p.getLocation()));
|
||||||
|
handleHandball(p, victimTeam, p.getLocation(), inPenalty);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Rückpass-Regel für Torwarte ──────────────────────────────────
|
// ── Rückpass-Regel für Torwarte ──────────────────────────────────
|
||||||
// Torwart im Auto-Kick: falls Ball vom Mitspieler per Fuß zugespielt → überspringen
|
// Torwart im Auto-Kick: falls Ball vom Mitspieler per Fuß zugespielt → überspringen
|
||||||
if (isGoalkeeper(p) && isInOwnHalf(p)) {
|
if (isGoalkeeper(p) && isInOwnHalf(p)) {
|
||||||
@@ -993,6 +1098,9 @@ public class Game {
|
|||||||
+ (assistName != null ? " §8(Vorlage: §e" + assistName + "§8)" : "")
|
+ (assistName != null ? " §8(Vorlage: §e" + assistName + "§8)" : "")
|
||||||
+ " §8[" + color + scoringTeam.getDisplayName() + "§8] §7" + redScore + ":" + blueScore);
|
+ " §8[" + color + scoringTeam.getDisplayName() + "§8] §7" + redScore + ":" + blueScore);
|
||||||
|
|
||||||
|
// ── Stadionatmosphäre: Torjubel ──────────────────────────────────────
|
||||||
|
playStadiumGoalAtmosphere(scoringTeam, ownGoal);
|
||||||
|
|
||||||
final Team concedingTeam = scoringTeam.getOpponent(); // für Anstoß
|
final Team concedingTeam = scoringTeam.getOpponent(); // für Anstoß
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -1002,6 +1110,8 @@ public class Game {
|
|||||||
spawnBallDelayed(arena.getBallSpawn());
|
spawnBallDelayed(arena.getBallSpawn());
|
||||||
// ── Anstoß-Team: das Team, das den Treffer kassiert hat ──
|
// ── Anstoß-Team: das Team, das den Treffer kassiert hat ──
|
||||||
throwInTeam = concedingTeam;
|
throwInTeam = concedingTeam;
|
||||||
|
// ── Anstoß-Kreis für 10 Sekunden erzwingen ──────────────
|
||||||
|
kickoffEnforceTicks = 200; // 10 Sekunden
|
||||||
broadcastAll(Messages.get("kickoff-team", "team",
|
broadcastAll(Messages.get("kickoff-team", "team",
|
||||||
concedingTeam == Team.RED ? "§cRotes Team" : "§9Blaues Team"));
|
concedingTeam == Team.RED ? "§cRotes Team" : "§9Blaues Team"));
|
||||||
|
|
||||||
@@ -1038,8 +1148,13 @@ public class Game {
|
|||||||
if (bossBar == null) return;
|
if (bossBar == null) return;
|
||||||
String timeStr;
|
String timeStr;
|
||||||
if (inInjuryTime) {
|
if (inInjuryTime) {
|
||||||
int injMins = (int) Math.ceil(injuryTimeBuffer / 60.0);
|
// BUG FIX: zeige tatsächliche Restzeit in Sekunden statt gerundete Minuten
|
||||||
timeStr = "§c+" + injMins + "' ";
|
int injSec = injuryTimeBuffer;
|
||||||
|
if (injSec >= 60) {
|
||||||
|
timeStr = "§c+" + String.format("%d:%02d", injSec / 60, injSec % 60);
|
||||||
|
} else {
|
||||||
|
timeStr = "§c+" + injSec + "s";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int m = timeLeft / 60, s = timeLeft % 60;
|
int m = timeLeft / 60, s = timeLeft % 60;
|
||||||
timeStr = String.format("%02d:%02d", m, s);
|
timeStr = String.format("%02d:%02d", m, s);
|
||||||
@@ -1504,10 +1619,7 @@ public class Game {
|
|||||||
|
|
||||||
if (insideInner) {
|
if (insideInner) {
|
||||||
// ── Zu nah / auf dem Spielfeld → raus schieben ───────────────
|
// ── Zu nah / auf dem Spielfeld → raus schieben ───────────────
|
||||||
// Nächsten Punkt auf dem inneren Rand berechnen und leicht weiter raus
|
// Richtung nach außen bestimmen (kürzester Weg über alle 4 Seiten)
|
||||||
double pushX = Math.max(innerMinX, Math.min(px, innerMaxX));
|
|
||||||
double pushZ = Math.max(innerMinZ, Math.min(pz, innerMaxZ));
|
|
||||||
// Richtung nach außen bestimmen
|
|
||||||
double dxMin = Math.abs(px - innerMinX), dxMax = Math.abs(px - innerMaxX);
|
double dxMin = Math.abs(px - innerMinX), dxMax = Math.abs(px - innerMaxX);
|
||||||
double dzMin = Math.abs(pz - innerMinZ), dzMax = Math.abs(pz - innerMaxZ);
|
double dzMin = Math.abs(pz - innerMinZ), dzMax = Math.abs(pz - innerMaxZ);
|
||||||
double minDist = Math.min(Math.min(dxMin, dxMax), Math.min(dzMin, dzMax));
|
double minDist = Math.min(Math.min(dxMin, dxMax), Math.min(dzMin, dzMax));
|
||||||
@@ -1649,6 +1761,13 @@ public class Game {
|
|||||||
broadcastAll(Messages.get("report-header"));
|
broadcastAll(Messages.get("report-header"));
|
||||||
broadcastAll("§7Endergebnis: §c" + redScore + " §7: §9" + blueScore);
|
broadcastAll("§7Endergebnis: §c" + redScore + " §7: §9" + blueScore);
|
||||||
|
|
||||||
|
// Ballbesitz
|
||||||
|
int redPoss = getRedPossessionPercent();
|
||||||
|
int bluePoss = getBluePossessionPercent();
|
||||||
|
if (redPoss >= 0) {
|
||||||
|
broadcastAll("§7Ballbesitz: §c" + redPoss + "% §7(Rot) vs §9" + bluePoss + "% §7(Blau)");
|
||||||
|
}
|
||||||
|
|
||||||
List<String> goalLines = matchEvents.stream().filter(e -> e.contains("TOR:")).collect(Collectors.toList());
|
List<String> goalLines = matchEvents.stream().filter(e -> e.contains("TOR:")).collect(Collectors.toList());
|
||||||
List<String> cardLines = matchEvents.stream().filter(e -> e.contains("🟨") || e.contains("🟥")).collect(Collectors.toList());
|
List<String> cardLines = matchEvents.stream().filter(e -> e.contains("🟨") || e.contains("🟥")).collect(Collectors.toList());
|
||||||
List<String> foulLines = matchEvents.stream().filter(e -> e.contains("Foul")).collect(Collectors.toList());
|
List<String> foulLines = matchEvents.stream().filter(e -> e.contains("Foul")).collect(Collectors.toList());
|
||||||
@@ -1713,6 +1832,21 @@ public class Game {
|
|||||||
// Matchbericht senden
|
// Matchbericht senden
|
||||||
sendMatchReport();
|
sendMatchReport();
|
||||||
|
|
||||||
|
// ── Match-History speichern ──────────────────────────────────────────
|
||||||
|
{
|
||||||
|
int redPoss = getRedPossessionPercent();
|
||||||
|
int bluePoss = getBluePossessionPercent();
|
||||||
|
List<String> goalEvts = matchEvents.stream()
|
||||||
|
.filter(e -> e.contains("TOR:")).collect(Collectors.toList());
|
||||||
|
String winnerName = winner != null ? winner.getDisplayName() : null;
|
||||||
|
MatchHistory.MatchRecord record = new MatchHistory.MatchRecord(
|
||||||
|
arena.getName(), redScore, blueScore, winnerName,
|
||||||
|
secondsPlayed, redPoss < 0 ? 50 : redPoss, bluePoss < 0 ? 50 : bluePoss,
|
||||||
|
penaltyRedGoals, penaltyBlueGoals, goalEvts
|
||||||
|
);
|
||||||
|
plugin.getMatchHistory().saveMatch(record);
|
||||||
|
}
|
||||||
|
|
||||||
// Ergebnis-Nachrichten
|
// Ergebnis-Nachrichten
|
||||||
broadcastAll("§e§l╔══════════════════════╗");
|
broadcastAll("§e§l╔══════════════════════╗");
|
||||||
if (winner == null) {
|
if (winner == null) {
|
||||||
@@ -1723,7 +1857,7 @@ public class Game {
|
|||||||
}
|
}
|
||||||
broadcastAll("§e§l╚══════════════════════╝");
|
broadcastAll("§e§l╚══════════════════════╝");
|
||||||
broadcastAll("§7Endergebnis: §c" + redScore + " §7: §9" + blueScore);
|
broadcastAll("§7Endergebnis: §c" + redScore + " §7: §9" + blueScore);
|
||||||
if (state == GameState.ENDING && penaltyRedGoals + penaltyBlueGoals > 0) {
|
if (penaltyRedGoals + penaltyBlueGoals > 0) {
|
||||||
broadcastAll("§7Elfmeter: §c" + penaltyRedGoals + " §7: §9" + penaltyBlueGoals);
|
broadcastAll("§7Elfmeter: §c" + penaltyRedGoals + " §7: §9" + penaltyBlueGoals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1880,8 +2014,33 @@ public class Game {
|
|||||||
if (lastKicker != null && !lastKicker.equals(uuid)) {
|
if (lastKicker != null && !lastKicker.equals(uuid)) {
|
||||||
secondLastKicker = lastKicker;
|
secondLastKicker = lastKicker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Kurz-/Langpass-Erkennung ─────────────────────────────────────────
|
||||||
|
if (lastKicker != null && !lastKicker.equals(uuid) && ball != null && ball.getEntity() != null
|
||||||
|
&& lastKickLocation != null && (state == GameState.RUNNING || state == GameState.OVERTIME)) {
|
||||||
|
Player prevKicker = Bukkit.getPlayer(lastKicker);
|
||||||
|
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)) {
|
||||||
|
// Langer Pass innerhalb des Teams
|
||||||
|
String msg = "§7⚽ §eLangpass §7von §f" + prevKicker.getName()
|
||||||
|
+ " §7zu §f" + newKicker.getName()
|
||||||
|
+ " §8(" + String.format("%.0f", dist) + " Blöcke)";
|
||||||
|
for (UUID u : getAllAndSpectators()) {
|
||||||
|
Player pl = Bukkit.getPlayer(u);
|
||||||
|
if (pl != null) pl.spigot().sendMessage(
|
||||||
|
net.md_5.bungee.api.ChatMessageType.ACTION_BAR,
|
||||||
|
net.md_5.bungee.api.chat.TextComponent.fromLegacyText(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Aktuelle Ball-Position für nächsten Pass merken
|
||||||
|
if (ball != null && ball.getEntity() != null) lastKickLocation = ball.getEntity().getLocation().clone();
|
||||||
|
|
||||||
this.lastKicker = uuid;
|
this.lastKicker = uuid;
|
||||||
lastKickWasHeader = false; // normaler Schuss / Berührung
|
lastKickWasHeader = false;
|
||||||
Player p = Bukkit.getPlayer(uuid);
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
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);
|
||||||
@@ -1947,5 +2106,168 @@ public class Game {
|
|||||||
freekickLocation = null;
|
freekickLocation = null;
|
||||||
freekickTicks = 0;
|
freekickTicks = 0;
|
||||||
offsideCooldown = false;
|
offsideCooldown = false;
|
||||||
|
kickoffEnforceTicks = 0; // Anstoß-Kreis sofort freigeben
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// ANSTOSSKRANZ-DURCHSETZUNG
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Während des Anstoßes müssen Gegner einen Mindestabstand zum Mittelpunkt einhalten.
|
||||||
|
* Radius konfigurierbar über gameplay.kickoff-circle-radius (default: 9.15 Blöcke).
|
||||||
|
*/
|
||||||
|
private void enforceKickoffCircle() {
|
||||||
|
if (throwInTeam == null || arena.getCenter() == null) return;
|
||||||
|
double radius = plugin.getConfig().getDouble("gameplay.kickoff-circle-radius", 9.15);
|
||||||
|
double radiusSq = radius * radius;
|
||||||
|
Team opposingTeam = throwInTeam.getOpponent();
|
||||||
|
List<UUID> opponents = opposingTeam == Team.RED ? redTeam : blueTeam;
|
||||||
|
|
||||||
|
for (UUID uuid : opponents) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid); if (p == null) continue;
|
||||||
|
if (!p.getWorld().equals(arena.getCenter().getWorld())) continue;
|
||||||
|
// Nur X/Z-Abstand (2D-Kreis)
|
||||||
|
double dx = p.getLocation().getX() - arena.getCenter().getX();
|
||||||
|
double dz = p.getLocation().getZ() - arena.getCenter().getZ();
|
||||||
|
double distSq = dx * dx + dz * dz;
|
||||||
|
if (distSq < radiusSq) {
|
||||||
|
// Spieler nach außen schieben
|
||||||
|
double dist = Math.sqrt(distSq);
|
||||||
|
if (dist < 0.01) { dx = 1; dz = 0; dist = 1; }
|
||||||
|
double factor = (radius - dist) / dist * 0.5;
|
||||||
|
p.setVelocity(new org.bukkit.util.Vector(dx * factor, 0.1, dz * factor));
|
||||||
|
p.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR,
|
||||||
|
net.md_5.bungee.api.chat.TextComponent.fromLegacyText(
|
||||||
|
"§cAbstand halten! §7Anstoß-Kreis (" + String.format("%.0f", radius) + "m)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// HANDBALL-REGEL
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private void handleHandball(Player fouler, Team freekickForTeam, Location loc, boolean isPenaltyArea) {
|
||||||
|
if (state != GameState.RUNNING && state != GameState.OVERTIME) return;
|
||||||
|
broadcastAll("§e✋ §cHANDSPIEL §7von §f" + fouler.getName() + "§7!");
|
||||||
|
fouler.sendTitle("§c✋ HANDSPIEL!", "§7Freistoß für " + freekickForTeam.getColorCode()
|
||||||
|
+ freekickForTeam.getDisplayName(), 5, 50, 10);
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
|
if (p != null) p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1.2f);
|
||||||
|
}
|
||||||
|
logMatchEvent("§e✋ Handball: §e" + fouler.getName());
|
||||||
|
addInjuryTime(plugin.getConfig().getInt("gameplay.injury-time-per-foul", 5));
|
||||||
|
|
||||||
|
if (isPenaltyArea) {
|
||||||
|
broadcastAll("§c⚠ ELFMETER! §7Handspiel im Strafraum!");
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
|
if (p != null) p.sendTitle("§c⚠ ELFMETER!", "§7Handspiel im Strafraum!", 5, 50, 10);
|
||||||
|
}
|
||||||
|
startFreekick(freekickForTeam, arena.getBallSpawn(), "Elfmeter (Handball)");
|
||||||
|
} else {
|
||||||
|
startFreekick(freekickForTeam, loc, "Handspiel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// DROP BALL (Schiedsrichterball)
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schiedsrichterball: Ball wird neutral an einer bestimmten Position gespawnt.
|
||||||
|
* Beide Teams dürfen sofort spielen (throwInTeam = null).
|
||||||
|
* Wird bei neutralen Unterbrechungen verwendet.
|
||||||
|
*/
|
||||||
|
public void dropBall(Location location) {
|
||||||
|
if (state != GameState.RUNNING && state != GameState.OVERTIME) return;
|
||||||
|
if (ball != null) ball.remove();
|
||||||
|
throwInTeam = null;
|
||||||
|
freekickLocation = null;
|
||||||
|
freekickTicks = 0;
|
||||||
|
spawnBallDelayed(location != null ? location : arena.getBallSpawn());
|
||||||
|
broadcastAll("§8[Schiri] §7⬇ §eSchiedsrichterball §7– beide Teams dürfen spielen!");
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid);
|
||||||
|
if (p != null) {
|
||||||
|
p.sendTitle("§e⬇ DROPBALL", "§7Beide Teams – los!", 5, 30, 5);
|
||||||
|
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.5f, 0.8f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logMatchEvent("§8Schiedsrichterball");
|
||||||
|
addInjuryTime(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// STADIONATMOSPHÄRE
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private void playStadiumGoalAtmosphere(Team scoringTeam, boolean ownGoal) {
|
||||||
|
if (!plugin.getConfig().getBoolean("atmosphere.enabled", true)) return;
|
||||||
|
Location center = arena.getCenter() != null ? arena.getCenter() : arena.getBallSpawn();
|
||||||
|
if (center == null) return;
|
||||||
|
|
||||||
|
// Mehrere Feuerwerke für Jubel-Feeling
|
||||||
|
int fwCount = plugin.getConfig().getInt("atmosphere.goal-fireworks", 5);
|
||||||
|
for (int i = 0; i < fwCount; i++) {
|
||||||
|
final int delay = i * 12;
|
||||||
|
new BukkitRunnable() {
|
||||||
|
public void run() {
|
||||||
|
if (state == GameState.ENDING || state == GameState.WAITING) return;
|
||||||
|
Location offset = center.clone().add(
|
||||||
|
(Math.random() - 0.5) * 10, 3 + Math.random() * 4, (Math.random() - 0.5) * 10);
|
||||||
|
spawnFirework(offset, ownGoal ? scoringTeam.getOpponent() : scoringTeam);
|
||||||
|
}
|
||||||
|
}.runTaskLater(plugin, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jubel-Sounds für das siegende Team; Stille für das andere
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid); if (p == null) continue;
|
||||||
|
Team t = getTeam(p);
|
||||||
|
if (t == scoringTeam || t == null) {
|
||||||
|
// Jubel-Sound
|
||||||
|
p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 0.9f);
|
||||||
|
new BukkitRunnable() { public void run() {
|
||||||
|
if (p.isOnline()) p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1f, 1.2f);
|
||||||
|
}}.runTaskLater(plugin, 20L);
|
||||||
|
} else {
|
||||||
|
// Enttäuschungs-Sound
|
||||||
|
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 0.7f, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boden-Erschütterung durch Partikel
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid); if (p == null) continue;
|
||||||
|
p.playSound(p.getLocation(), Sound.ENTITY_FIREWORK_ROCKET_BLAST, 2f, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Spielt einen "Beinahe-Tor"-Sound (z. B. bei Abpraller am Pfosten) */
|
||||||
|
public void playNearMissAtmosphere() {
|
||||||
|
if (!plugin.getConfig().getBoolean("atmosphere.enabled", true)) return;
|
||||||
|
for (UUID uuid : getAllAndSpectators()) {
|
||||||
|
Player p = Bukkit.getPlayer(uuid); if (p == null) continue;
|
||||||
|
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.7f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
// BALLBESITZ-GETTER
|
||||||
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/** Ballbesitz Rot in Prozent (0–100), -1 wenn noch keine Daten */
|
||||||
|
public int getRedPossessionPercent() {
|
||||||
|
if (totalPossessionTicks == 0) return -1;
|
||||||
|
return (int) Math.round((double) redPossessionTicks / totalPossessionTicks * 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ballbesitz Blau in Prozent (0–100), -1 wenn noch keine Daten */
|
||||||
|
public int getBluePossessionPercent() {
|
||||||
|
if (totalPossessionTicks == 0) return -1;
|
||||||
|
return (int) Math.round((double) bluePossessionTicks / totalPossessionTicks * 100.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,6 +232,14 @@ public class BallListener implements Listener {
|
|||||||
TextComponent.fromLegacyText("§e⚽ Schuss-Power: " + bar + " §f" + (int)(power*100) + "%"));
|
TextComponent.fromLegacyText("§e⚽ Schuss-Power: " + bar + " §f" + (int)(power*100) + "%"));
|
||||||
if (power >= 1.0) {
|
if (power >= 1.0) {
|
||||||
chargeMap.remove(player.getUniqueId());
|
chargeMap.remove(player.getUniqueId());
|
||||||
|
// throwInTeam-Check: auch beim Auto-Feuer einhalten
|
||||||
|
if (game.getThrowInTeam() != null && game.getThrowInTeam() != game.getTeam(player)) {
|
||||||
|
player.spigot().sendMessage(ChatMessageType.ACTION_BAR,
|
||||||
|
TextComponent.fromLegacyText("§cDu bist nicht dran!"));
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game.clearThrowIn();
|
||||||
game.setLastKicker(player.getUniqueId());
|
game.setLastKicker(player.getUniqueId());
|
||||||
ball.chargedKick(player, 1.0);
|
ball.chargedKick(player, 1.0);
|
||||||
cancel();
|
cancel();
|
||||||
|
|||||||
@@ -119,6 +119,9 @@ public class PlayerListener implements Listener {
|
|||||||
* Da AsyncPlayerChatEvent asynchron läuft, wird die eigentliche Team-Nachricht
|
* Da AsyncPlayerChatEvent asynchron läuft, wird die eigentliche Team-Nachricht
|
||||||
* per runTask auf den Haupt-Thread verlagert.
|
* per runTask auf den Haupt-Thread verlagert.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation") // AsyncPlayerChatEvent ist in Paper 1.19+ deprecated.
|
||||||
|
// Für reines Spigot-Kompatibilitäts-Targeting wird es hier bewusst weiterverwendet.
|
||||||
|
// Migration zu io.papermc.paper.event.player.AsyncChatEvent möglich sobald Spigot-Support entfällt.
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onChat(AsyncPlayerChatEvent event) {
|
public void onChat(AsyncPlayerChatEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
|
|||||||
@@ -66,8 +66,11 @@ public class FussballScoreboard {
|
|||||||
case RUNNING, GOAL -> {
|
case RUNNING, GOAL -> {
|
||||||
String half = game.isSecondHalf() ? "§72. HZ" : "§71. HZ";
|
String half = game.isSecondHalf() ? "§72. HZ" : "§71. HZ";
|
||||||
if (game.isInInjuryTime()) {
|
if (game.isInInjuryTime()) {
|
||||||
int injMins = (int) Math.ceil(game.getInjuryTimeBuffer() / 60.0);
|
int injSec = game.getInjuryTimeBuffer();
|
||||||
set(obj, "§c⏱ +" + injMins + "' §7Nachsp. " + half, l--);
|
String injStr = injSec >= 60
|
||||||
|
? String.format("+%d:%02d", injSec/60, injSec%60)
|
||||||
|
: "+" + injSec + "s";
|
||||||
|
set(obj, "§c⏱ §f" + injStr + " §7Nachsp. " + half, l--);
|
||||||
} else {
|
} else {
|
||||||
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
||||||
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " " + half, l--);
|
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " " + half, l--);
|
||||||
@@ -76,8 +79,11 @@ public class FussballScoreboard {
|
|||||||
case HALFTIME -> set(obj, "§6⏸ HALBZEIT", l--);
|
case HALFTIME -> set(obj, "§6⏸ HALBZEIT", l--);
|
||||||
case OVERTIME -> {
|
case OVERTIME -> {
|
||||||
if (game.isInInjuryTime()) {
|
if (game.isInInjuryTime()) {
|
||||||
int injMins = (int) Math.ceil(game.getInjuryTimeBuffer() / 60.0);
|
int injSec = game.getInjuryTimeBuffer();
|
||||||
set(obj, "§c⏱ +" + injMins + "' §6VL Nachsp.", l--);
|
String injStr = injSec >= 60
|
||||||
|
? String.format("+%d:%02d", injSec/60, injSec%60)
|
||||||
|
: "+" + injSec + "s";
|
||||||
|
set(obj, "§c⏱ §f" + injStr + " §6VL Nachsp.", l--);
|
||||||
} else {
|
} else {
|
||||||
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
int m = game.getTimeLeft() / 60, s = game.getTimeLeft() % 60;
|
||||||
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " §6VL", l--);
|
set(obj, "§e⏱ " + String.format("%02d:%02d", m, s) + " §6VL", l--);
|
||||||
@@ -101,6 +107,12 @@ public class FussballScoreboard {
|
|||||||
|
|
||||||
set(obj, "§7Spieler: §f" + game.getAllPlayers().size() + "/" + game.getArena().getMaxPlayers(), l--);
|
set(obj, "§7Spieler: §f" + game.getAllPlayers().size() + "/" + game.getArena().getMaxPlayers(), l--);
|
||||||
set(obj, "§r§r§r§r", l--);
|
set(obj, "§r§r§r§r", l--);
|
||||||
|
|
||||||
|
// Ballbesitz anzeigen wenn Daten vorhanden
|
||||||
|
int redPoss = game.getRedPossessionPercent();
|
||||||
|
if (redPoss >= 0) {
|
||||||
|
set(obj, "§cR " + redPoss + "% §7│ §9" + game.getBluePossessionPercent() + "% B", l--);
|
||||||
|
}
|
||||||
set(obj, "§7Arena: §e" + game.getArena().getName(), l--);
|
set(obj, "§7Arena: §e" + game.getArena().getName(), l--);
|
||||||
set(obj, "§r§r§r§r§r", l--);
|
set(obj, "§r§r§r§r§r", l--);
|
||||||
set(obj, "§6§lFußball-Plugin", l);
|
set(obj, "§6§lFußball-Plugin", l);
|
||||||
|
|||||||
119
src/main/java/de/fussball/plugin/stats/MatchHistory.java
Normal file
119
src/main/java/de/fussball/plugin/stats/MatchHistory.java
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
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.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert die letzten 50 Spielergebnisse dauerhaft in matchhistory.yml.
|
||||||
|
* Wird am Spielende automatisch befüllt und kann per /fb history abgerufen werden.
|
||||||
|
*/
|
||||||
|
public class MatchHistory {
|
||||||
|
|
||||||
|
private static final int MAX_ENTRIES = 50;
|
||||||
|
private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||||
|
|
||||||
|
private final Fussball plugin;
|
||||||
|
private final File historyFile;
|
||||||
|
private FileConfiguration config;
|
||||||
|
|
||||||
|
public MatchHistory(Fussball plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.historyFile = new File(plugin.getDataFolder(), "matchhistory.yml");
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Laden / Speichern ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
if (!historyFile.exists()) {
|
||||||
|
try { historyFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
|
||||||
|
}
|
||||||
|
config = YamlConfiguration.loadConfiguration(historyFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert ein Spielergebnis an Stelle 0 (neueste zuerst).
|
||||||
|
* Alte Einträge über MAX_ENTRIES werden verworfen.
|
||||||
|
*/
|
||||||
|
public void saveMatch(MatchRecord record) {
|
||||||
|
List<Map<?, ?>> matches = new ArrayList<>(
|
||||||
|
config.contains("matches") ? config.getMapList("matches") : new ArrayList<>());
|
||||||
|
|
||||||
|
Map<String, Object> entry = new LinkedHashMap<>();
|
||||||
|
entry.put("arena", record.arena);
|
||||||
|
entry.put("date", record.date);
|
||||||
|
entry.put("redScore", record.redScore);
|
||||||
|
entry.put("blueScore", record.blueScore);
|
||||||
|
entry.put("winner", record.winner != null ? record.winner : "Unentschieden");
|
||||||
|
entry.put("duration", record.durationSeconds);
|
||||||
|
entry.put("redPoss", record.redPossession);
|
||||||
|
entry.put("bluePoss", record.bluePossession);
|
||||||
|
entry.put("penaltyRed", record.penaltyRed);
|
||||||
|
entry.put("penaltyBlue", record.penaltyBlue);
|
||||||
|
if (!record.goalEvents.isEmpty()) entry.put("goals", record.goalEvents);
|
||||||
|
|
||||||
|
matches.add(0, entry);
|
||||||
|
if (matches.size() > MAX_ENTRIES) matches = matches.subList(0, MAX_ENTRIES);
|
||||||
|
|
||||||
|
config.set("matches", matches);
|
||||||
|
try {
|
||||||
|
config.save(historyFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().severe("[MatchHistory] Konnte matchhistory.yml nicht speichern: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gibt die letzten n Spiele zurück (neueste zuerst). */
|
||||||
|
public List<Map<?, ?>> getMatches(int limit) {
|
||||||
|
if (!config.contains("matches")) return Collections.emptyList();
|
||||||
|
List<Map<?, ?>> all = config.getMapList("matches");
|
||||||
|
return all.subList(0, Math.min(limit, all.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gibt alle gespeicherten Spiele zurück. */
|
||||||
|
public List<Map<?, ?>> getMatches() { return getMatches(MAX_ENTRIES); }
|
||||||
|
|
||||||
|
// ── Datenklasse ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public static class MatchRecord {
|
||||||
|
public final String arena;
|
||||||
|
public final String date;
|
||||||
|
public final int redScore, blueScore;
|
||||||
|
/** "Rot", "Blau" oder null für Unentschieden */
|
||||||
|
public final String winner;
|
||||||
|
public final int durationSeconds;
|
||||||
|
/** Ballbesitz-Prozent (Rot) */
|
||||||
|
public final int redPossession;
|
||||||
|
/** Ballbesitz-Prozent (Blau) */
|
||||||
|
public final int bluePossession;
|
||||||
|
public final int penaltyRed, penaltyBlue;
|
||||||
|
/** Tor-Events aus dem Matchbericht (z. B. "12' Hans [Rot]") */
|
||||||
|
public final List<String> goalEvents;
|
||||||
|
|
||||||
|
public MatchRecord(String arena,
|
||||||
|
int redScore, int blueScore,
|
||||||
|
String winner,
|
||||||
|
int durationSeconds,
|
||||||
|
int redPossession, int bluePossession,
|
||||||
|
int penaltyRed, int penaltyBlue,
|
||||||
|
List<String> goalEvents) {
|
||||||
|
this.arena = arena;
|
||||||
|
this.date = DATE_FMT.format(new Date());
|
||||||
|
this.redScore = redScore;
|
||||||
|
this.blueScore = blueScore;
|
||||||
|
this.winner = winner;
|
||||||
|
this.durationSeconds = durationSeconds;
|
||||||
|
this.redPossession = redPossession;
|
||||||
|
this.bluePossession = bluePossession;
|
||||||
|
this.penaltyRed = penaltyRed;
|
||||||
|
this.penaltyBlue = penaltyBlue;
|
||||||
|
this.goalEvents = goalEvents != null ? goalEvents : Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,13 +103,6 @@ public class StatsManager {
|
|||||||
save();
|
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) {
|
public void addGameResult(UUID uuid, String name, GameResult result) {
|
||||||
PlayerStats s = getStats(uuid);
|
PlayerStats s = getStats(uuid);
|
||||||
s.name = name;
|
s.name = name;
|
||||||
@@ -35,6 +35,11 @@ gameplay:
|
|||||||
header-power: 1.3 # Schussstärke eines Kopfballs
|
header-power: 1.3 # Schussstärke eines Kopfballs
|
||||||
header-cooldown: 10 # Ticks Abklingzeit zwischen zwei Kopfbällen desselben Spielers
|
header-cooldown: 10 # Ticks Abklingzeit zwischen zwei Kopfbällen desselben Spielers
|
||||||
|
|
||||||
|
# ── AFK-Erkennung ────────────────────────────────────────────────────────
|
||||||
|
afk-warn-seconds: 20 # Sekunden Stillstand bis zur ersten AFK-Warnung
|
||||||
|
afk-kick-seconds: 40 # Sekunden Stillstand bis zur Disqualifikation
|
||||||
|
afk-move-threshold: 0.5 # Mindestbewegung in Blöcken pro Sekunde
|
||||||
|
|
||||||
# ── Nachspielzeit ────────────────────────────────────────────────────────
|
# ── Nachspielzeit ────────────────────────────────────────────────────────
|
||||||
injury-time-enabled: true
|
injury-time-enabled: true
|
||||||
injury-time-per-goal: 30 # Sekunden Nachspielzeit pro Tor
|
injury-time-per-goal: 30 # Sekunden Nachspielzeit pro Tor
|
||||||
@@ -42,7 +47,21 @@ gameplay:
|
|||||||
injury-time-per-foul: 5 # Sekunden pro Foul
|
injury-time-per-foul: 5 # Sekunden pro Foul
|
||||||
injury-time-per-out: 3 # Sekunden pro Aus-Situation
|
injury-time-per-out: 3 # Sekunden pro Aus-Situation
|
||||||
|
|
||||||
# ── Nachrichten (alle editierbar) ──────────────────
|
# ── Anstoß-Kreis ─────────────────────────────────────────────────────────
|
||||||
|
kickoff-circle-radius: 9.15 # Pflichtabstand für Gegner beim Anstoß (Blöcke, FIFA: 9.15m)
|
||||||
|
|
||||||
|
# ── Handball ─────────────────────────────────────────────────────────────
|
||||||
|
handball-enabled: true # Handspiel-Erkennung an/aus (Shift + Ball auf Armhöhe)
|
||||||
|
|
||||||
|
# ── Pässe ────────────────────────────────────────────────────────────────
|
||||||
|
long-pass-distance: 20.0 # Ab wie vielen Blöcken ein Pass als „Langpass" gilt
|
||||||
|
|
||||||
|
# ── Stadionatmosphäre ────────────────────────────────────────────────────────
|
||||||
|
atmosphere:
|
||||||
|
enabled: true
|
||||||
|
goal-fireworks: 5 # Anzahl Feuerwerke bei einem Tor (0 = deaktiviert)
|
||||||
|
|
||||||
|
# ── Nachrichten (alle editierbar) ─────────────────────────────────────────────
|
||||||
# Verfügbare Platzhalter je nach Kontext:
|
# Verfügbare Platzhalter je nach Kontext:
|
||||||
# {player} = Spielername
|
# {player} = Spielername
|
||||||
# {team} = Teamname
|
# {team} = Teamname
|
||||||
@@ -115,9 +134,9 @@ messages:
|
|||||||
out-goal-kick: "§e⚽ §7Ball im Aus! §7Abstoß für {team}§7!"
|
out-goal-kick: "§e⚽ §7Ball im Aus! §7Abstoß für {team}§7!"
|
||||||
|
|
||||||
# Feldgrenze-Warnung
|
# Feldgrenze-Warnung
|
||||||
boundary-warn: "§c⚠ §lSPIELFELDGRENZE! §7Kehre in §e{n} Sek §7zurück!"
|
boundary-warn: "§c⚠ §lSPIELFELDGRENZE! §7Kehre in §e{n} Sek §7zurück!"
|
||||||
boundary-return: "§aWieder im Spielfeld!"
|
boundary-return: "§aWieder im Spielfeld!"
|
||||||
boundary-disq: "§c⚠ §e{player} §cwurde disqualifiziert! (Spielfeldgrenze)"
|
boundary-disq: "§c⚠ §e{player} §cwurde disqualifiziert! (Spielfeldgrenze)"
|
||||||
boundary-disq-self: "§cDu wurdest disqualifiziert, weil du zu lange außerhalb warst!"
|
boundary-disq-self: "§cDu wurdest disqualifiziert, weil du zu lange außerhalb warst!"
|
||||||
|
|
||||||
# Eigentore & Assists
|
# Eigentore & Assists
|
||||||
@@ -126,12 +145,15 @@ messages:
|
|||||||
assist: "§7Vorlage: §e{player}"
|
assist: "§7Vorlage: §e{player}"
|
||||||
|
|
||||||
# Nachspielzeit
|
# Nachspielzeit
|
||||||
injury-time: "§c⏱ §l+{n} Min. Nachspielzeit!"
|
injury-time: "§c⏱ §l+{n} Nachspielzeit!"
|
||||||
injury-time-bar: "§c+{n}' §8│ "
|
injury-time-bar: "§c+{n} §8│ "
|
||||||
|
|
||||||
# Anstoß
|
# Anstoß
|
||||||
kickoff-team: "§e⚽ §7Anstoß für {team}§7!"
|
kickoff-team: "§e⚽ §7Anstoß für {team}§7!"
|
||||||
|
|
||||||
|
# Anstoß-Kreis
|
||||||
|
kickoff-circle: "§cAbstand halten! §7Anstoß-Kreis ({n}m)"
|
||||||
|
|
||||||
# Strafraum / Elfmeter bei Foul
|
# Strafraum / Elfmeter bei Foul
|
||||||
foul-penalty: "§c⚠ §lFOUL IM STRAFRAUM! §7Elfmeter für {team}§7!"
|
foul-penalty: "§c⚠ §lFOUL IM STRAFRAUM! §7Elfmeter für {team}§7!"
|
||||||
|
|
||||||
@@ -142,6 +164,26 @@ messages:
|
|||||||
# Kopfball
|
# Kopfball
|
||||||
header: "§e⚽ §7Kopfball von §e{player}§7!"
|
header: "§e⚽ §7Kopfball von §e{player}§7!"
|
||||||
|
|
||||||
|
# Handball
|
||||||
|
handball: "§e✋ §cHANDSPIEL §7von §e{player}§7!"
|
||||||
|
handball-title: "§c✋ HANDSPIEL!"
|
||||||
|
handball-penalty: "§c⚠ ELFMETER! §7Handspiel im Strafraum!"
|
||||||
|
|
||||||
|
# Drop Ball / Schiedsrichterball
|
||||||
|
dropball: "§8[Schiri] §7⬇ §eSchiedsrichterball §7– beide Teams dürfen spielen!"
|
||||||
|
dropball-title: "§e⬇ DROPBALL"
|
||||||
|
dropball-sub: "§7Beide Teams – los!"
|
||||||
|
|
||||||
|
# Pässe
|
||||||
|
long-pass: "§7⚽ §eLangpass §7von §f{player} §7zu §f{target} §8({n} Blöcke)"
|
||||||
|
|
||||||
|
# Teamwahl
|
||||||
|
team-request: "§7Teamwunsch gesetzt: {team}§7. Wird beim nächsten Spielstart berücksichtigt."
|
||||||
|
team-request-fail: "§cDein Wunsch-Team war zu groß – du wurdest automatisch zugeteilt."
|
||||||
|
|
||||||
|
# Trikot-Nummern
|
||||||
|
jersey-info: "§7Deine Trikot-Nummer: §e#{n}"
|
||||||
|
|
||||||
# Spieler beitreten / verlassen
|
# Spieler beitreten / verlassen
|
||||||
player-join: "§e{player} §7ist beigetreten! §8({n}/{max})"
|
player-join: "§e{player} §7ist beigetreten! §8({n}/{max})"
|
||||||
player-leave: "§e{player} §7hat das Spiel verlassen!"
|
player-leave: "§e{player} §7hat das Spiel verlassen!"
|
||||||
@@ -149,11 +191,12 @@ messages:
|
|||||||
team-blue: "§9Blaues Team"
|
team-blue: "§9Blaues Team"
|
||||||
|
|
||||||
# Matchbericht
|
# Matchbericht
|
||||||
report-header: "§e§l━━━━━━ MATCHBERICHT ━━━━━━"
|
report-header: "§e§l━━━━━━ MATCHBERICHT ━━━━━━"
|
||||||
report-footer: "§e§l━━━━━━━━━━━━━━━━━━━━━━━━━"
|
report-footer: "§e§l━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
report-goals: "§7Tore:"
|
report-goals: "§7Tore:"
|
||||||
report-cards: "§7Karten:"
|
report-cards: "§7Karten:"
|
||||||
report-fouls: "§7Fouls:"
|
report-fouls: "§7Fouls:"
|
||||||
report-offside: "§7Abseits:"
|
report-offside: "§7Abseits:"
|
||||||
report-mvp: "§6⭐ MVP: §e{player} §7({n} Tore)"
|
report-possession: "§7Ballbesitz: §c{n}% §7(Rot) vs §9{m}% §7(Blau)"
|
||||||
report-no-events: "§8Keine Ereignisse."
|
report-mvp: "§6⭐ MVP: §e{player} §7({n} Tore)"
|
||||||
|
report-no-events: "§8Keine Ereignisse."
|
||||||
@@ -19,5 +19,5 @@ permissions:
|
|||||||
commands:
|
commands:
|
||||||
fussball:
|
fussball:
|
||||||
description: Hauptbefehl des Fußball-Plugins
|
description: Hauptbefehl des Fußball-Plugins
|
||||||
usage: /fussball <join|leave|spectate|list|stats|top|create|delete|setup|stop|debug>
|
usage: /fussball <join|leave|spectate|team|list|stats|top|history|create|delete|setup|stop|setgk|dropball|debug|hologram>
|
||||||
aliases: [fb, soccer]
|
aliases: [fb, soccer]
|
||||||
Reference in New Issue
Block a user