Update from Git Manager GUI
This commit is contained in:
@@ -281,7 +281,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Hologramm-Verwaltung ─────────────────────────────────────────
|
// ── Hologramm-Verwaltung ─────────────────────────────────────────
|
||||||
// /fb hologram set <id> goals|wins – Hologramm erstellen
|
// /fb hologram set <id> goals|wins|match – Hologramm erstellen
|
||||||
// /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
// /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen
|
||||||
// /fb hologram delete <id> – Hologramm nach ID löschen
|
// /fb hologram delete <id> – Hologramm nach ID löschen
|
||||||
// /fb hologram reload – Alle Hologramme neu spawnen
|
// /fb hologram reload – Alle Hologramme neu spawnen
|
||||||
@@ -291,7 +291,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
if (!(sender instanceof Player player)) { sender.sendMessage("Nur für Spieler!"); return true; }
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
player.sendMessage(MessageUtil.header("Hologramm-Befehle"));
|
player.sendMessage(MessageUtil.header("Hologramm-Befehle"));
|
||||||
player.sendMessage("§e/fb hologram set <id> goals|wins §7– Hologramm setzen");
|
player.sendMessage("§e/fb hologram set <id> goals|wins|match §7– Hologramm setzen");
|
||||||
player.sendMessage("§e/fb hologram remove §7– Nächstes entfernen (< 5 Blöcke)");
|
player.sendMessage("§e/fb hologram remove §7– Nächstes entfernen (< 5 Blöcke)");
|
||||||
player.sendMessage("§e/fb hologram delete <id> §7– Nach ID löschen");
|
player.sendMessage("§e/fb hologram delete <id> §7– Nach ID löschen");
|
||||||
player.sendMessage("§e/fb hologram reload §7– Alle neu laden");
|
player.sendMessage("§e/fb hologram reload §7– Alle neu laden");
|
||||||
@@ -303,18 +303,27 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
switch (args[1].toLowerCase()) {
|
switch (args[1].toLowerCase()) {
|
||||||
case "set" -> {
|
case "set" -> {
|
||||||
if (args.length < 4) {
|
if (args.length < 4) {
|
||||||
player.sendMessage(MessageUtil.error("Benutze: /fb hologram set <id> goals|wins"));
|
player.sendMessage(MessageUtil.error("Benutze: /fb hologram set <id> goals|wins|match"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String id = args[2];
|
String id = args[2];
|
||||||
FussballHologram.HoloType type = switch (args[3].toLowerCase()) {
|
FussballHologram.HoloType type = switch (args[3].toLowerCase()) {
|
||||||
case "wins", "siege" -> FussballHologram.HoloType.WINS;
|
case "wins", "siege" -> FussballHologram.HoloType.WINS;
|
||||||
|
case "match", "live", "game" -> FussballHologram.HoloType.MATCH;
|
||||||
default -> FussballHologram.HoloType.GOALS;
|
default -> FussballHologram.HoloType.GOALS;
|
||||||
};
|
};
|
||||||
plugin.getHologramManager().createHologram(id, player.getLocation(), type);
|
plugin.getHologramManager().createHologram(id, player.getLocation(), type);
|
||||||
String holoLabel = type == FussballHologram.HoloType.WINS ? "Top-10-Siege" : "Top-10-Tore";
|
String holoLabel = switch (type) {
|
||||||
|
case WINS -> "Top-10-Siege";
|
||||||
|
case MATCH -> "Live-Match";
|
||||||
|
default -> "Top-10-Tore";
|
||||||
|
};
|
||||||
player.sendMessage(MessageUtil.success("§e" + id + " §a(" + holoLabel + ") Hologramm gesetzt!"));
|
player.sendMessage(MessageUtil.success("§e" + id + " §a(" + holoLabel + ") Hologramm gesetzt!"));
|
||||||
player.sendMessage("§7§oRechtsklick auf das Hologramm wechselt zwischen Tore und Siege.");
|
if (type == FussballHologram.HoloType.MATCH) {
|
||||||
|
player.sendMessage("§7§oLive-Match-Hologramm aktualisiert sich automatisch bei Toren und Nachspielzeit.");
|
||||||
|
} else {
|
||||||
|
player.sendMessage("§7§oRechtsklick auf das Hologramm wechselt zwischen Tore und Siege.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "remove" -> {
|
case "remove" -> {
|
||||||
String removed = plugin.getHologramManager().removeNearest(player.getLocation());
|
String removed = plugin.getHologramManager().removeNearest(player.getLocation());
|
||||||
@@ -346,7 +355,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> player.sendMessage(MessageUtil.error("Gültig: set <id> goals|wins | remove | delete <id> | reload | list"));
|
default -> player.sendMessage(MessageUtil.error("Gültig: set <id> goals|wins|match | remove | delete <id> | reload | list"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +494,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
s.sendMessage("§e/fb history [n] §7- Letzte Spiele anzeigen");
|
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 / dropball");
|
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|match / remove / reload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +528,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter {
|
|||||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
||||||
list.addAll(plugin.getArenaManager().getArenaNames()); // id-Vorschläge (frei wählbar, aber arena-namen passen)
|
list.addAll(plugin.getArenaManager().getArenaNames()); // id-Vorschläge (frei wählbar, aber arena-namen passen)
|
||||||
} else if (args.length == 4 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
} else if (args.length == 4 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("set")) {
|
||||||
list.addAll(List.of("goals", "wins"));
|
list.addAll(List.of("goals", "wins", "match"));
|
||||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("delete")) {
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("hologram") && args[1].equalsIgnoreCase("delete")) {
|
||||||
list.addAll(plugin.getHologramManager().getHologramIds());
|
list.addAll(plugin.getHologramManager().getHologramIds());
|
||||||
} else if (args.length == 3 && args[0].equalsIgnoreCase("setgk")) {
|
} else if (args.length == 3 && args[0].equalsIgnoreCase("setgk")) {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import org.bukkit.boss.BossBar;
|
|||||||
import org.bukkit.entity.Firework;
|
import org.bukkit.entity.Firework;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.FireworkEffect;
|
import org.bukkit.FireworkEffect;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.FireworkMeta;
|
import org.bukkit.inventory.meta.FireworkMeta;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
@@ -94,6 +96,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
|
||||||
|
private static final int GOAL_BEACON_SCAN_RADIUS = 10;
|
||||||
|
private static final int GOAL_BEACON_MAX_PER_SIDE = 4;
|
||||||
/** Ticks, in denen der Anstoß-Kreis erzwungen wird (Gegner müssen Abstand halten) */
|
/** Ticks, in denen der Anstoß-Kreis erzwungen wird (Gegner müssen Abstand halten) */
|
||||||
private int kickoffEnforceTicks = 0;
|
private int kickoffEnforceTicks = 0;
|
||||||
|
|
||||||
@@ -356,6 +360,7 @@ public class Game {
|
|||||||
secondHalf = false;
|
secondHalf = false;
|
||||||
timeLeft = arena.getGameDuration();
|
timeLeft = arena.getGameDuration();
|
||||||
secondsPlayed = 0;
|
secondsPlayed = 0;
|
||||||
|
updateGoalBeaconColors();
|
||||||
|
|
||||||
bossBar = Bukkit.createBossBar("§e⚽ §c0 §7: §90 §8| §e05:00", BarColor.GREEN, BarStyle.SOLID);
|
bossBar = Bukkit.createBossBar("§e⚽ §c0 §7: §90 §8| §e05:00", BarColor.GREEN, BarStyle.SOLID);
|
||||||
bossBar.setProgress(1.0);
|
bossBar.setProgress(1.0);
|
||||||
@@ -438,6 +443,7 @@ public class Game {
|
|||||||
state = GameState.RUNNING;
|
state = GameState.RUNNING;
|
||||||
secondHalf = true;
|
secondHalf = true;
|
||||||
timeLeft = arena.getGameDuration() / 2;
|
timeLeft = arena.getGameDuration() / 2;
|
||||||
|
updateGoalBeaconColors();
|
||||||
// Nachspielzeit der 1. Halbzeit zurücksetzen
|
// Nachspielzeit der 1. Halbzeit zurücksetzen
|
||||||
injuryTimeBuffer = 0;
|
injuryTimeBuffer = 0;
|
||||||
inInjuryTime = false;
|
inInjuryTime = false;
|
||||||
@@ -487,6 +493,7 @@ public class Game {
|
|||||||
state = GameState.OVERTIME;
|
state = GameState.OVERTIME;
|
||||||
overtimeDone = true;
|
overtimeDone = true;
|
||||||
timeLeft = 600; // 2x5min = 10 Min Verlängerung
|
timeLeft = 600; // 2x5min = 10 Min Verlängerung
|
||||||
|
updateGoalBeaconColors();
|
||||||
|
|
||||||
if (ball != null) ball.remove();
|
if (ball != null) ball.remove();
|
||||||
spawnBallDelayed(arena.getBallSpawn());
|
spawnBallDelayed(arena.getBallSpawn());
|
||||||
@@ -891,8 +898,7 @@ public class Game {
|
|||||||
|
|
||||||
switch (side) {
|
switch (side) {
|
||||||
case "side" -> {
|
case "side" -> {
|
||||||
resumeLocation = arena.clampToField(outLocation);
|
resumeLocation = moveInsideField(arena.clampToField(outLocation), 1.25);
|
||||||
resumeLocation.setY(outLocation.getY());
|
|
||||||
if (touchTeam == null) {
|
if (touchTeam == null) {
|
||||||
throwInTeam = null;
|
throwInTeam = null;
|
||||||
message = "§e⚽ §7Ball im Aus! §7Einwurf!";
|
message = "§e⚽ §7Ball im Aus! §7Einwurf!";
|
||||||
@@ -905,28 +911,28 @@ public class Game {
|
|||||||
}
|
}
|
||||||
case "redEnd" -> {
|
case "redEnd" -> {
|
||||||
if (touchTeam == Team.RED) {
|
if (touchTeam == Team.RED) {
|
||||||
resumeLocation = getCornerLocation(outLocation);
|
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
|
||||||
throwInTeam = Team.BLUE;
|
throwInTeam = Team.BLUE;
|
||||||
message = "§e⚽ §7Ball im Aus! §9Ecke für Blaues Team§7!";
|
message = "§e⚽ §7Ball im Aus! §9Ecke für Blaues Team§7!";
|
||||||
} else {
|
} else {
|
||||||
resumeLocation = arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn();
|
resumeLocation = moveInsideField(arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn(), 1.25);
|
||||||
throwInTeam = Team.RED;
|
throwInTeam = Team.RED;
|
||||||
message = "§e⚽ §7Ball im Aus! §cAbstoß für Rotes Team§7!";
|
message = "§e⚽ §7Ball im Aus! §cAbstoß für Rotes Team§7!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "blueEnd" -> {
|
case "blueEnd" -> {
|
||||||
if (touchTeam == Team.BLUE) {
|
if (touchTeam == Team.BLUE) {
|
||||||
resumeLocation = getCornerLocation(outLocation);
|
resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25);
|
||||||
throwInTeam = Team.RED;
|
throwInTeam = Team.RED;
|
||||||
message = "§e⚽ §7Ball im Aus! §cEcke für Rotes Team§7!";
|
message = "§e⚽ §7Ball im Aus! §cEcke für Rotes Team§7!";
|
||||||
} else {
|
} else {
|
||||||
resumeLocation = arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn();
|
resumeLocation = moveInsideField(arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn(), 1.25);
|
||||||
throwInTeam = Team.BLUE;
|
throwInTeam = Team.BLUE;
|
||||||
message = "§e⚽ §7Ball im Aus! §9Abstoß für Blaues Team§7!";
|
message = "§e⚽ §7Ball im Aus! §9Abstoß für Blaues Team§7!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default -> {
|
default -> {
|
||||||
resumeLocation = arena.getBallSpawn();
|
resumeLocation = moveInsideField(arena.getBallSpawn(), 1.25);
|
||||||
throwInTeam = null;
|
throwInTeam = null;
|
||||||
message = "§e⚽ §7Ball im Aus!";
|
message = "§e⚽ §7Ball im Aus!";
|
||||||
}
|
}
|
||||||
@@ -956,6 +962,25 @@ public class Game {
|
|||||||
return new Location(outLoc.getWorld(), cx, y, cz);
|
return new Location(outLoc.getWorld(), cx, y, cz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Location moveInsideField(Location source, double inset) {
|
||||||
|
if (source == null) return arena.getBallSpawn();
|
||||||
|
if (arena.getFieldMin() == null || arena.getFieldMax() == null) return source.clone();
|
||||||
|
|
||||||
|
double minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX()) + inset;
|
||||||
|
double maxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX()) - inset;
|
||||||
|
double minZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()) + inset;
|
||||||
|
double maxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()) - inset;
|
||||||
|
|
||||||
|
// Bei sehr kleinen Feldern: notfalls exakt klemmen statt invertierter Grenzen.
|
||||||
|
if (minX > maxX) { minX = Math.min(arena.getFieldMin().getX(), arena.getFieldMax().getX()); maxX = Math.max(arena.getFieldMin().getX(), arena.getFieldMax().getX()); }
|
||||||
|
if (minZ > maxZ) { minZ = Math.min(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()); maxZ = Math.max(arena.getFieldMin().getZ(), arena.getFieldMax().getZ()); }
|
||||||
|
|
||||||
|
double x = Math.max(minX, Math.min(source.getX(), maxX));
|
||||||
|
double z = Math.max(minZ, Math.min(source.getZ(), maxZ));
|
||||||
|
double y = arena.getBallSpawn() != null ? arena.getBallSpawn().getY() : source.getY();
|
||||||
|
return new Location(source.getWorld(), x, y, z, source.getYaw(), source.getPitch());
|
||||||
|
}
|
||||||
|
|
||||||
private void checkPlayerBallInteraction() {
|
private void checkPlayerBallInteraction() {
|
||||||
if (ball == null || !ball.isActive() || ball.isHeld()) return;
|
if (ball == null || !ball.isActive() || ball.isHeld()) return;
|
||||||
for (UUID uuid : allPlayers) {
|
for (UUID uuid : allPlayers) {
|
||||||
@@ -1744,6 +1769,14 @@ public class Game {
|
|||||||
if (bossBar != null) bossBar.removePlayer(player);
|
if (bossBar != null) bossBar.removePlayer(player);
|
||||||
scoreboard.remove(player);
|
scoreboard.remove(player);
|
||||||
resetPlayer(player);
|
resetPlayer(player);
|
||||||
|
|
||||||
|
// Wenn ein Team komplett disqualifiziert wurde, gewinnt das andere Team sofort.
|
||||||
|
if (state == GameState.RUNNING || state == GameState.OVERTIME || state == GameState.PENALTY || state == GameState.GOAL) {
|
||||||
|
if (redTeam.isEmpty() && !blueTeam.isEmpty()) { endGame(Team.BLUE); return; }
|
||||||
|
if (blueTeam.isEmpty() && !redTeam.isEmpty()) { endGame(Team.RED); return; }
|
||||||
|
if (redTeam.isEmpty() && blueTeam.isEmpty()) { endGame(null); return; }
|
||||||
|
}
|
||||||
|
|
||||||
if (allPlayers.size() < arena.getMinPlayers()) { endGame(getWinnerTeam()); return; }
|
if (allPlayers.size() < arena.getMinPlayers()) { endGame(getWinnerTeam()); return; }
|
||||||
scoreboard.updateAll(); refreshSigns();
|
scoreboard.updateAll(); refreshSigns();
|
||||||
}
|
}
|
||||||
@@ -1810,6 +1843,8 @@ public class Game {
|
|||||||
lastKicker = null;
|
lastKicker = null;
|
||||||
secondLastKicker = null;
|
secondLastKicker = null;
|
||||||
lastKickWasHeader = false;
|
lastKickWasHeader = false;
|
||||||
|
secondHalf = false;
|
||||||
|
updateGoalBeaconColors();
|
||||||
|
|
||||||
// Persistente Statistiken speichern
|
// Persistente Statistiken speichern
|
||||||
// BUG FIX: nach Elfmeter kann redScore == blueScore true sein obwohl ein Gewinner existiert.
|
// BUG FIX: nach Elfmeter kann redScore == blueScore true sein obwohl ein Gewinner existiert.
|
||||||
@@ -1945,6 +1980,70 @@ public class Game {
|
|||||||
|
|
||||||
private void refreshSigns() { plugin.getSignListener().refreshSignsForArena(arena.getName()); }
|
private void refreshSigns() { plugin.getSignListener().refreshSignsForArena(arena.getName()); }
|
||||||
|
|
||||||
|
private void updateGoalBeaconColors() {
|
||||||
|
setGoalBeaconColor(getGoalCenter(true), secondHalf ? Team.BLUE : Team.RED);
|
||||||
|
setGoalBeaconColor(getGoalCenter(false), secondHalf ? Team.RED : Team.BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setGoalBeaconColor(Location goalCenter, Team defendingTeam) {
|
||||||
|
List<Block> beacons = findNearbyBeacons(goalCenter, GOAL_BEACON_SCAN_RADIUS, GOAL_BEACON_MAX_PER_SIDE);
|
||||||
|
if (beacons.isEmpty()) return;
|
||||||
|
|
||||||
|
Material colorGlass = defendingTeam == Team.RED
|
||||||
|
? Material.RED_STAINED_GLASS
|
||||||
|
: Material.BLUE_STAINED_GLASS;
|
||||||
|
|
||||||
|
for (Block beacon : beacons) {
|
||||||
|
Block above = beacon.getRelative(BlockFace.UP);
|
||||||
|
Material current = above.getType();
|
||||||
|
if (current == Material.AIR || isStainedGlass(current)) {
|
||||||
|
above.setType(colorGlass, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Block> findNearbyBeacons(Location center, int radius, int maxCount) {
|
||||||
|
if (center == null || center.getWorld() == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<Block> found = new ArrayList<>();
|
||||||
|
int cx = center.getBlockX();
|
||||||
|
int cy = center.getBlockY();
|
||||||
|
int cz = center.getBlockZ();
|
||||||
|
|
||||||
|
for (int x = cx - radius; x <= cx + radius; x++) {
|
||||||
|
for (int y = cy - 6; y <= cy + 6; y++) {
|
||||||
|
for (int z = cz - radius; z <= cz + radius; z++) {
|
||||||
|
Block block = center.getWorld().getBlockAt(x, y, z);
|
||||||
|
if (block.getType() != Material.BEACON) continue;
|
||||||
|
found.add(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found.sort(Comparator.comparingDouble(b -> b.getLocation().distanceSquared(center)));
|
||||||
|
if (found.size() > maxCount) {
|
||||||
|
return new ArrayList<>(found.subList(0, maxCount));
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isStainedGlass(Material material) {
|
||||||
|
return material.name().endsWith("_STAINED_GLASS");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Location getGoalCenter(boolean redGoal) {
|
||||||
|
Location min = redGoal ? arena.getRedGoalMin() : arena.getBlueGoalMin();
|
||||||
|
Location max = redGoal ? arena.getRedGoalMax() : arena.getBlueGoalMax();
|
||||||
|
if (min == null || max == null || min.getWorld() == null) return null;
|
||||||
|
|
||||||
|
return new Location(
|
||||||
|
min.getWorld(),
|
||||||
|
(min.getX() + max.getX()) / 2.0,
|
||||||
|
(min.getY() + max.getY()) / 2.0,
|
||||||
|
(min.getZ() + max.getZ()) / 2.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Broadcast an alle Spieler UND Zuschauer */
|
/** Broadcast an alle Spieler UND Zuschauer */
|
||||||
public void broadcastAll(String msg) {
|
public void broadcastAll(String msg) {
|
||||||
for (UUID uuid : getAllAndSpectators()) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.sendMessage(msg); }
|
for (UUID uuid : getAllAndSpectators()) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.sendMessage(msg); }
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package de.fussball.plugin.hologram;
|
package de.fussball.plugin.hologram;
|
||||||
|
|
||||||
import de.fussball.plugin.Fussball;
|
import de.fussball.plugin.Fussball;
|
||||||
|
import de.fussball.plugin.game.Game;
|
||||||
|
import de.fussball.plugin.game.GameState;
|
||||||
import de.fussball.plugin.stats.StatsManager;
|
import de.fussball.plugin.stats.StatsManager;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Color;
|
import org.bukkit.Color;
|
||||||
@@ -9,6 +11,9 @@ import org.bukkit.entity.Display;
|
|||||||
import org.bukkit.entity.Interaction;
|
import org.bukkit.entity.Interaction;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.entity.TextDisplay;
|
import org.bukkit.entity.TextDisplay;
|
||||||
|
import org.bukkit.util.Transformation;
|
||||||
|
import org.joml.AxisAngle4f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -28,7 +33,7 @@ public class FussballHologram {
|
|||||||
/** Render-Radius: 48 Blöcke (2304 = 48²) */
|
/** Render-Radius: 48 Blöcke (2304 = 48²) */
|
||||||
private static final double RENDER_RADIUS_SQ = 2304.0;
|
private static final double RENDER_RADIUS_SQ = 2304.0;
|
||||||
|
|
||||||
public enum HoloType { GOALS, WINS }
|
public enum HoloType { GOALS, WINS, MATCH }
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final Location location;
|
private final Location location;
|
||||||
@@ -53,6 +58,9 @@ public class FussballHologram {
|
|||||||
|
|
||||||
/** Rechtsklick → zur nächsten Seite (GOALS ↔ WINS) */
|
/** Rechtsklick → zur nächsten Seite (GOALS ↔ WINS) */
|
||||||
public void nextPage(Player player) {
|
public void nextPage(Player player) {
|
||||||
|
if (type == HoloType.MATCH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Wir haben 2 Seiten: 0 = GOALS, 1 = WINS
|
// Wir haben 2 Seiten: 0 = GOALS, 1 = WINS
|
||||||
int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % 2;
|
int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % 2;
|
||||||
currentPage.put(player.getUniqueId(), next);
|
currentPage.put(player.getUniqueId(), next);
|
||||||
@@ -74,8 +82,13 @@ public class FussballHologram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Seite bestimmen
|
// Seite bestimmen
|
||||||
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), type == HoloType.WINS ? 1 : 0);
|
HoloType displayType;
|
||||||
HoloType displayType = pageIdx == 1 ? HoloType.WINS : HoloType.GOALS;
|
if (type == HoloType.MATCH) {
|
||||||
|
displayType = HoloType.MATCH;
|
||||||
|
} else {
|
||||||
|
int pageIdx = currentPage.getOrDefault(player.getUniqueId(), type == HoloType.WINS ? 1 : 0);
|
||||||
|
displayType = pageIdx == 1 ? HoloType.WINS : HoloType.GOALS;
|
||||||
|
}
|
||||||
|
|
||||||
String text = buildText(displayType);
|
String text = buildText(displayType);
|
||||||
|
|
||||||
@@ -93,34 +106,47 @@ public class FussballHologram {
|
|||||||
entity.setText(text);
|
entity.setText(text);
|
||||||
entity.setInvulnerable(true);
|
entity.setInvulnerable(true);
|
||||||
entity.setSeeThrough(false);
|
entity.setSeeThrough(false);
|
||||||
});
|
applyVisualScale(entity, displayType);
|
||||||
|
|
||||||
// ── Interaction-Entity spawnen (Hitbox für Rechtsklick) ──────────
|
|
||||||
Interaction interact = location.getWorld().spawn(location, Interaction.class, entity -> {
|
|
||||||
entity.setInteractionWidth(2.5f);
|
|
||||||
entity.setInteractionHeight(2.5f);
|
|
||||||
entity.setCustomNameVisible(false);
|
|
||||||
entity.setPersistent(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Nur für diesen Spieler sichtbar machen ───────────────────────
|
// ── Nur für diesen Spieler sichtbar machen ───────────────────────
|
||||||
TextDisplay finalDisplay = display;
|
TextDisplay finalDisplay = display;
|
||||||
|
Interaction interact = null;
|
||||||
|
if (displayType != HoloType.MATCH) {
|
||||||
|
// Match-Hologramme sind nur Anzeige, ohne Klick-Hitbox.
|
||||||
|
interact = location.getWorld().spawn(location, Interaction.class, entity -> {
|
||||||
|
entity.setInteractionWidth(2.5f);
|
||||||
|
entity.setInteractionHeight(2.5f);
|
||||||
|
entity.setCustomNameVisible(false);
|
||||||
|
entity.setPersistent(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Interaction finalInteract = interact;
|
Interaction finalInteract = interact;
|
||||||
for (Player other : Bukkit.getOnlinePlayers()) {
|
for (Player other : Bukkit.getOnlinePlayers()) {
|
||||||
if (!other.getUniqueId().equals(player.getUniqueId())) {
|
if (!other.getUniqueId().equals(player.getUniqueId())) {
|
||||||
other.hideEntity(plugin, finalDisplay);
|
other.hideEntity(plugin, finalDisplay);
|
||||||
other.hideEntity(plugin, finalInteract);
|
if (finalInteract != null) {
|
||||||
|
other.hideEntity(plugin, finalInteract);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playerEntities.put(player.getUniqueId(), display);
|
playerEntities.put(player.getUniqueId(), display);
|
||||||
playerInteractions.put(player.getUniqueId(), interact);
|
if (interact != null) {
|
||||||
|
playerInteractions.put(player.getUniqueId(), interact);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// ── Bestehende Entity nur aktualisieren (kein Re-Spawn) ──────────
|
// ── Bestehende Entity nur aktualisieren (kein Re-Spawn) ──────────
|
||||||
if (!display.getText().equals(text)) {
|
if (!display.getText().equals(text)) {
|
||||||
display.setText(text);
|
display.setText(text);
|
||||||
}
|
}
|
||||||
|
applyVisualScale(display, displayType);
|
||||||
|
if (displayType == HoloType.MATCH) {
|
||||||
|
Interaction interact = playerInteractions.remove(player.getUniqueId());
|
||||||
|
if (interact != null) interact.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +185,10 @@ public class FussballHologram {
|
|||||||
|
|
||||||
/** Baut den anzuzeigenden Text aus den aktuellen Top-10-Statistiken */
|
/** Baut den anzuzeigenden Text aus den aktuellen Top-10-Statistiken */
|
||||||
private String buildText(HoloType showType) {
|
private String buildText(HoloType showType) {
|
||||||
|
if (showType == HoloType.MATCH) {
|
||||||
|
return buildMatchText();
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
if (showType == HoloType.GOALS) {
|
if (showType == HoloType.GOALS) {
|
||||||
@@ -202,6 +232,91 @@ public class FussballHologram {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildMatchText() {
|
||||||
|
Game game = findRelevantGame();
|
||||||
|
if (game == null) {
|
||||||
|
return "§e§lLive Match\n§8§m────────────────\n§7Kein Spiel aktiv\n§8- : -\n§8--:--";
|
||||||
|
}
|
||||||
|
|
||||||
|
String header = "§e§lLive Match";
|
||||||
|
String separator = "§8§m────────────────";
|
||||||
|
String phase = buildPhaseLabel(game);
|
||||||
|
String score = "§c§l" + game.getRedScore() + " §r§7: §9§l" + game.getBlueScore();
|
||||||
|
String timeLabel = game.isInInjuryTime()
|
||||||
|
? "§c+" + formatInjury(game.getInjuryTimeBuffer()) + " §7(Nachspielzeit)"
|
||||||
|
: "§e" + formatMainTime(game.getTimeLeft());
|
||||||
|
|
||||||
|
return header + "\n" + separator + "\n" + phase + "\n" + score + "\n" + timeLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPhaseLabel(Game game) {
|
||||||
|
return switch (game.getState()) {
|
||||||
|
case WAITING -> "§7⏳ Wartet auf Spieler";
|
||||||
|
case STARTING -> "§aStartet...";
|
||||||
|
case RUNNING -> game.isSecondHalf() ? "§72. Halbzeit" : "§71. Halbzeit";
|
||||||
|
case HALFTIME -> "§e⏸ Halbzeit";
|
||||||
|
case OVERTIME -> "§6⚡ Verlängerung";
|
||||||
|
case GOAL -> "§6§l⚽ TOR!";
|
||||||
|
case PENALTY -> "§c🥊 Elfmeterschießen";
|
||||||
|
case ENDING -> "§8Spiel beendet";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyVisualScale(TextDisplay display, HoloType showType) {
|
||||||
|
float scale = showType == HoloType.MATCH ? 5.0f : 1.0f;
|
||||||
|
display.setTransformation(new Transformation(
|
||||||
|
new Vector3f(0f, 0f, 0f),
|
||||||
|
new AxisAngle4f(),
|
||||||
|
new Vector3f(scale, scale, scale),
|
||||||
|
new AxisAngle4f()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Game findRelevantGame() {
|
||||||
|
Game best = null;
|
||||||
|
int bestPrio = Integer.MAX_VALUE;
|
||||||
|
double bestDist = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
for (Game game : plugin.getGameManager().getAllGames()) {
|
||||||
|
Location ref = game.getArena().getCenter();
|
||||||
|
if (ref == null) ref = game.getArena().getBallSpawn();
|
||||||
|
if (ref == null) ref = game.getArena().getLobby();
|
||||||
|
if (ref == null || ref.getWorld() == null) continue;
|
||||||
|
if (!ref.getWorld().equals(location.getWorld())) continue;
|
||||||
|
|
||||||
|
int prio = switch (game.getState()) {
|
||||||
|
case RUNNING, OVERTIME, GOAL, PENALTY -> 0;
|
||||||
|
case STARTING, HALFTIME -> 1;
|
||||||
|
case WAITING -> 2;
|
||||||
|
case ENDING -> 3;
|
||||||
|
};
|
||||||
|
double dist = ref.distanceSquared(location);
|
||||||
|
|
||||||
|
if (prio < bestPrio || (prio == bestPrio && dist < bestDist)) {
|
||||||
|
best = game;
|
||||||
|
bestPrio = prio;
|
||||||
|
bestDist = dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMainTime(int totalSeconds) {
|
||||||
|
int safe = Math.max(0, totalSeconds);
|
||||||
|
int m = safe / 60;
|
||||||
|
int s = safe % 60;
|
||||||
|
return String.format("%02d:%02d", m, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatInjury(int seconds) {
|
||||||
|
int safe = Math.max(0, seconds);
|
||||||
|
if (safe >= 60) {
|
||||||
|
return "+" + (safe / 60) + ":" + String.format("%02d", safe % 60);
|
||||||
|
}
|
||||||
|
return "+" + safe + "s";
|
||||||
|
}
|
||||||
|
|
||||||
private String medal(int rank) {
|
private String medal(int rank) {
|
||||||
return switch (rank) {
|
return switch (rank) {
|
||||||
case 1 -> "§6§l#1"; // Gold bleibt – hebt sich gut ab
|
case 1 -> "§6§l#1"; // Gold bleibt – hebt sich gut ab
|
||||||
|
|||||||
@@ -106,10 +106,14 @@ public class HologramManager implements Listener {
|
|||||||
);
|
);
|
||||||
|
|
||||||
String typeStr = holoConfig.getString(path + ".type", "GOALS");
|
String typeStr = holoConfig.getString(path + ".type", "GOALS");
|
||||||
FussballHologram.HoloType type =
|
FussballHologram.HoloType type;
|
||||||
"WINS".equalsIgnoreCase(typeStr)
|
if ("WINS".equalsIgnoreCase(typeStr)) {
|
||||||
? FussballHologram.HoloType.WINS
|
type = FussballHologram.HoloType.WINS;
|
||||||
: FussballHologram.HoloType.GOALS;
|
} else if ("MATCH".equalsIgnoreCase(typeStr) || "LIVE".equalsIgnoreCase(typeStr)) {
|
||||||
|
type = FussballHologram.HoloType.MATCH;
|
||||||
|
} else {
|
||||||
|
type = FussballHologram.HoloType.GOALS;
|
||||||
|
}
|
||||||
|
|
||||||
holograms.put(id, new FussballHologram(id, loc, type, plugin));
|
holograms.put(id, new FussballHologram(id, loc, type, plugin));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package de.fussball.plugin.listeners;
|
|||||||
import de.fussball.plugin.Fussball;
|
import de.fussball.plugin.Fussball;
|
||||||
import de.fussball.plugin.game.Game;
|
import de.fussball.plugin.game.Game;
|
||||||
import de.fussball.plugin.game.Team;
|
import de.fussball.plugin.game.Team;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.entity.HumanEntity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
@@ -10,7 +12,11 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.entity.EntityDamageEvent;
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||||
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
import org.bukkit.event.entity.FoodLevelChangeEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||||
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
import org.bukkit.event.player.*;
|
import org.bukkit.event.player.*;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
public class PlayerListener implements Listener {
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
@@ -63,6 +69,64 @@ public class PlayerListener implements Listener {
|
|||||||
if (plugin.getGameManager().isInAnyGame(player)) event.setCancelled(true);
|
if (plugin.getGameManager().isInAnyGame(player)) event.setCancelled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Team-Rüstung darf im aktiven Spiel nicht ausgezogen oder verschoben werden.
|
||||||
|
*/
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onInventoryClick(InventoryClickEvent event) {
|
||||||
|
if (!(event.getWhoClicked() instanceof Player player)) return;
|
||||||
|
if (!plugin.getGameManager().isInGame(player)) return;
|
||||||
|
|
||||||
|
// Direkter Klick auf Helm/Brust/Beine/Schuhe-Slot
|
||||||
|
if (event.getSlotType() == InventoryType.SlotType.ARMOR) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift-Klick auf Rüstung im Inventar (würde in Rüstungsslot auto-equipen/verschieben)
|
||||||
|
if (event.isShiftClick() && isArmorPiece(event.getCurrentItem())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hotbar-Zahlen-Tausch mit Rüstungsteilen blockieren
|
||||||
|
if (event.getClick().isKeyboardClick()) {
|
||||||
|
int hotbar = event.getHotbarButton();
|
||||||
|
if (hotbar >= 0 && hotbar <= 8) {
|
||||||
|
ItemStack hotbarItem = player.getInventory().getItem(hotbar);
|
||||||
|
if (isArmorPiece(hotbarItem) || event.getSlotType() == InventoryType.SlotType.ARMOR) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draggen auf Rüstungsslots blockieren.
|
||||||
|
*/
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onInventoryDrag(InventoryDragEvent event) {
|
||||||
|
HumanEntity who = event.getWhoClicked();
|
||||||
|
if (!(who instanceof Player player)) return;
|
||||||
|
if (!plugin.getGameManager().isInGame(player)) return;
|
||||||
|
|
||||||
|
for (int rawSlot : event.getRawSlots()) {
|
||||||
|
if (event.getView().getSlotType(rawSlot) == InventoryType.SlotType.ARMOR) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isArmorPiece(ItemStack item) {
|
||||||
|
if (item == null) return false;
|
||||||
|
Material t = item.getType();
|
||||||
|
return t.name().endsWith("_HELMET")
|
||||||
|
|| t.name().endsWith("_CHESTPLATE")
|
||||||
|
|| t.name().endsWith("_LEGGINGS")
|
||||||
|
|| t.name().endsWith("_BOOTS");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zuschauer dürfen die Arena nicht verlassen (Weltenwechsel blockieren).
|
* Zuschauer dürfen die Arena nicht verlassen (Weltenwechsel blockieren).
|
||||||
* GM3 erlaubt Portale – das verhindert, dass ein Zuschauer durch ein Portal
|
* GM3 erlaubt Portale – das verhindert, dass ein Zuschauer durch ein Portal
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Fussball
|
name: Fussball
|
||||||
version: 1.0.0
|
version: 1.0.1
|
||||||
main: de.fussball.plugin.Fussball
|
main: de.fussball.plugin.Fussball
|
||||||
api-version: 1.21
|
api-version: 1.21
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
|
|||||||
Reference in New Issue
Block a user