diff --git a/src/main/java/de/fussball/plugin/commands/FussballCommand.java b/src/main/java/de/fussball/plugin/commands/FussballCommand.java index b9e850f..7a451f0 100644 --- a/src/main/java/de/fussball/plugin/commands/FussballCommand.java +++ b/src/main/java/de/fussball/plugin/commands/FussballCommand.java @@ -281,7 +281,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter { } // ── Hologramm-Verwaltung ───────────────────────────────────────── - // /fb hologram set goals|wins – Hologramm erstellen + // /fb hologram set goals|wins|match – Hologramm erstellen // /fb hologram remove – Nächstes Hologramm (< 5 Blöcke) entfernen // /fb hologram delete – Hologramm nach ID löschen // /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 (args.length < 2) { player.sendMessage(MessageUtil.header("Hologramm-Befehle")); - player.sendMessage("§e/fb hologram set goals|wins §7– Hologramm setzen"); + player.sendMessage("§e/fb hologram set goals|wins|match §7– Hologramm setzen"); player.sendMessage("§e/fb hologram remove §7– Nächstes entfernen (< 5 Blöcke)"); player.sendMessage("§e/fb hologram delete §7– Nach ID löschen"); player.sendMessage("§e/fb hologram reload §7– Alle neu laden"); @@ -303,18 +303,27 @@ public class FussballCommand implements CommandExecutor, TabCompleter { switch (args[1].toLowerCase()) { case "set" -> { if (args.length < 4) { - player.sendMessage(MessageUtil.error("Benutze: /fb hologram set goals|wins")); + player.sendMessage(MessageUtil.error("Benutze: /fb hologram set goals|wins|match")); return true; } String id = args[2]; FussballHologram.HoloType type = switch (args[3].toLowerCase()) { case "wins", "siege" -> FussballHologram.HoloType.WINS; + case "match", "live", "game" -> FussballHologram.HoloType.MATCH; default -> FussballHologram.HoloType.GOALS; }; 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("§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" -> { 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 goals|wins | remove | delete | reload | list")); + default -> player.sendMessage(MessageUtil.error("Gültig: set goals|wins|match | remove | delete | reload | list")); } } @@ -485,7 +494,7 @@ public class FussballCommand implements CommandExecutor, TabCompleter { s.sendMessage("§e/fb history [n] §7- Letzte Spiele anzeigen"); if (s.hasPermission("fussball.admin")) { 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")) { 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")) { - 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")) { list.addAll(plugin.getHologramManager().getHologramIds()); } else if (args.length == 3 && args[0].equalsIgnoreCase("setgk")) { diff --git a/src/main/java/de/fussball/plugin/game/Game.java b/src/main/java/de/fussball/plugin/game/Game.java index 5cfdad8..bc7afa1 100644 --- a/src/main/java/de/fussball/plugin/game/Game.java +++ b/src/main/java/de/fussball/plugin/game/Game.java @@ -16,6 +16,8 @@ import org.bukkit.boss.BossBar; import org.bukkit.entity.Firework; import org.bukkit.entity.Player; import org.bukkit.FireworkEffect; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.FireworkMeta; import org.bukkit.potion.PotionEffect; @@ -94,6 +96,8 @@ public class Game { private int spawnCooldown = 0; private int ballMissingTicks = 0; // Ticks ohne lebenden Ball → Respawn 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) */ private int kickoffEnforceTicks = 0; @@ -356,6 +360,7 @@ public class Game { secondHalf = false; timeLeft = arena.getGameDuration(); secondsPlayed = 0; + updateGoalBeaconColors(); bossBar = Bukkit.createBossBar("§e⚽ §c0 §7: §90 §8| §e05:00", BarColor.GREEN, BarStyle.SOLID); bossBar.setProgress(1.0); @@ -438,6 +443,7 @@ public class Game { state = GameState.RUNNING; secondHalf = true; timeLeft = arena.getGameDuration() / 2; + updateGoalBeaconColors(); // Nachspielzeit der 1. Halbzeit zurücksetzen injuryTimeBuffer = 0; inInjuryTime = false; @@ -487,6 +493,7 @@ public class Game { state = GameState.OVERTIME; overtimeDone = true; timeLeft = 600; // 2x5min = 10 Min Verlängerung + updateGoalBeaconColors(); if (ball != null) ball.remove(); spawnBallDelayed(arena.getBallSpawn()); @@ -891,8 +898,7 @@ public class Game { switch (side) { case "side" -> { - resumeLocation = arena.clampToField(outLocation); - resumeLocation.setY(outLocation.getY()); + resumeLocation = moveInsideField(arena.clampToField(outLocation), 1.25); if (touchTeam == null) { throwInTeam = null; message = "§e⚽ §7Ball im Aus! §7Einwurf!"; @@ -905,28 +911,28 @@ public class Game { } case "redEnd" -> { if (touchTeam == Team.RED) { - resumeLocation = getCornerLocation(outLocation); + resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25); throwInTeam = Team.BLUE; message = "§e⚽ §7Ball im Aus! §9Ecke für Blaues Team§7!"; } else { - resumeLocation = arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn(); + resumeLocation = moveInsideField(arena.getRedSpawn() != null ? arena.getRedSpawn() : arena.getBallSpawn(), 1.25); throwInTeam = Team.RED; message = "§e⚽ §7Ball im Aus! §cAbstoß für Rotes Team§7!"; } } case "blueEnd" -> { if (touchTeam == Team.BLUE) { - resumeLocation = getCornerLocation(outLocation); + resumeLocation = moveInsideField(getCornerLocation(outLocation), 1.25); throwInTeam = Team.RED; message = "§e⚽ §7Ball im Aus! §cEcke für Rotes Team§7!"; } else { - resumeLocation = arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn(); + resumeLocation = moveInsideField(arena.getBlueSpawn() != null ? arena.getBlueSpawn() : arena.getBallSpawn(), 1.25); throwInTeam = Team.BLUE; message = "§e⚽ §7Ball im Aus! §9Abstoß für Blaues Team§7!"; } } default -> { - resumeLocation = arena.getBallSpawn(); + resumeLocation = moveInsideField(arena.getBallSpawn(), 1.25); throwInTeam = null; message = "§e⚽ §7Ball im Aus!"; } @@ -956,6 +962,25 @@ public class Game { 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() { if (ball == null || !ball.isActive() || ball.isHeld()) return; for (UUID uuid : allPlayers) { @@ -1744,6 +1769,14 @@ public class Game { if (bossBar != null) bossBar.removePlayer(player); scoreboard.remove(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; } scoreboard.updateAll(); refreshSigns(); } @@ -1810,6 +1843,8 @@ public class Game { lastKicker = null; secondLastKicker = null; lastKickWasHeader = false; + secondHalf = false; + updateGoalBeaconColors(); // Persistente Statistiken speichern // 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 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 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 findNearbyBeacons(Location center, int radius, int maxCount) { + if (center == null || center.getWorld() == null) return Collections.emptyList(); + + List 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 */ public void broadcastAll(String msg) { for (UUID uuid : getAllAndSpectators()) { Player p = Bukkit.getPlayer(uuid); if (p != null) p.sendMessage(msg); } diff --git a/src/main/java/de/fussball/plugin/hologram/FussballHologram.java b/src/main/java/de/fussball/plugin/hologram/FussballHologram.java index cbee51e..ed3a623 100644 --- a/src/main/java/de/fussball/plugin/hologram/FussballHologram.java +++ b/src/main/java/de/fussball/plugin/hologram/FussballHologram.java @@ -1,6 +1,8 @@ package de.fussball.plugin.hologram; import de.fussball.plugin.Fussball; +import de.fussball.plugin.game.Game; +import de.fussball.plugin.game.GameState; import de.fussball.plugin.stats.StatsManager; import org.bukkit.Bukkit; import org.bukkit.Color; @@ -9,6 +11,9 @@ import org.bukkit.entity.Display; import org.bukkit.entity.Interaction; import org.bukkit.entity.Player; 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.UUID; @@ -28,7 +33,7 @@ public class FussballHologram { /** Render-Radius: 48 Blöcke (2304 = 48²) */ 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 Location location; @@ -53,6 +58,9 @@ public class FussballHologram { /** Rechtsklick → zur nächsten Seite (GOALS ↔ WINS) */ public void nextPage(Player player) { + if (type == HoloType.MATCH) { + return; + } // Wir haben 2 Seiten: 0 = GOALS, 1 = WINS int next = (currentPage.getOrDefault(player.getUniqueId(), 0) + 1) % 2; currentPage.put(player.getUniqueId(), next); @@ -74,8 +82,13 @@ public class FussballHologram { } // Seite bestimmen - int pageIdx = currentPage.getOrDefault(player.getUniqueId(), type == HoloType.WINS ? 1 : 0); - HoloType displayType = pageIdx == 1 ? HoloType.WINS : HoloType.GOALS; + HoloType displayType; + 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); @@ -93,34 +106,47 @@ public class FussballHologram { entity.setText(text); entity.setInvulnerable(true); entity.setSeeThrough(false); - }); - - // ── 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); + applyVisualScale(entity, displayType); }); // ── Nur für diesen Spieler sichtbar machen ─────────────────────── 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; for (Player other : Bukkit.getOnlinePlayers()) { if (!other.getUniqueId().equals(player.getUniqueId())) { other.hideEntity(plugin, finalDisplay); - other.hideEntity(plugin, finalInteract); + if (finalInteract != null) { + other.hideEntity(plugin, finalInteract); + } } } playerEntities.put(player.getUniqueId(), display); - playerInteractions.put(player.getUniqueId(), interact); + if (interact != null) { + playerInteractions.put(player.getUniqueId(), interact); + } } else { // ── Bestehende Entity nur aktualisieren (kein Re-Spawn) ────────── if (!display.getText().equals(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 */ private String buildText(HoloType showType) { + if (showType == HoloType.MATCH) { + return buildMatchText(); + } + StringBuilder sb = new StringBuilder(); if (showType == HoloType.GOALS) { @@ -202,6 +232,91 @@ public class FussballHologram { 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) { return switch (rank) { case 1 -> "§6§l#1"; // Gold bleibt – hebt sich gut ab diff --git a/src/main/java/de/fussball/plugin/hologram/HologramManager.java b/src/main/java/de/fussball/plugin/hologram/HologramManager.java index 1237abb..abef536 100644 --- a/src/main/java/de/fussball/plugin/hologram/HologramManager.java +++ b/src/main/java/de/fussball/plugin/hologram/HologramManager.java @@ -106,10 +106,14 @@ public class HologramManager implements Listener { ); String typeStr = holoConfig.getString(path + ".type", "GOALS"); - FussballHologram.HoloType type = - "WINS".equalsIgnoreCase(typeStr) - ? FussballHologram.HoloType.WINS - : FussballHologram.HoloType.GOALS; + FussballHologram.HoloType type; + if ("WINS".equalsIgnoreCase(typeStr)) { + type = FussballHologram.HoloType.WINS; + } 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)); } diff --git a/src/main/java/de/fussball/plugin/listeners/PlayerListener.java b/src/main/java/de/fussball/plugin/listeners/PlayerListener.java index d56d521..e89bc98 100644 --- a/src/main/java/de/fussball/plugin/listeners/PlayerListener.java +++ b/src/main/java/de/fussball/plugin/listeners/PlayerListener.java @@ -3,6 +3,8 @@ package de.fussball.plugin.listeners; import de.fussball.plugin.Fussball; import de.fussball.plugin.game.Game; import de.fussball.plugin.game.Team; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; 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.EntityPickupItemEvent; 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.inventory.ItemStack; public class PlayerListener implements Listener { @@ -63,6 +69,64 @@ public class PlayerListener implements Listener { 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). * GM3 erlaubt Portale – das verhindert, dass ein Zuschauer durch ein Portal diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 17320e2..834e60d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: Fussball -version: 1.0.0 +version: 1.0.1 main: de.fussball.plugin.Fussball api-version: 1.21 author: M_Viper