Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-06-19 23:11:05 +02:00
parent 3c194f9e6a
commit fb83b736ea
24 changed files with 3180 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package de.lasertec;
import de.lasertec.arena.ArenaManager;
import de.lasertec.command.LasertecCommand;
import de.lasertec.command.LtAdminCommand;
import de.lasertec.config.ConfigManager;
import de.lasertec.game.GameManager;
import de.lasertec.listener.BlockListener;
import de.lasertec.listener.PlayerListener;
import de.lasertec.listener.SignListener;
import de.lasertec.listener.WeaponListener;
import de.lasertec.player.PlayerDataManager;
import de.lasertec.scoreboard.ScoreboardManager;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class LasertecPlugin extends JavaPlugin {
private static LasertecPlugin instance;
private ConfigManager configManager;
private ArenaManager arenaManager;
private GameManager gameManager;
private PlayerDataManager playerDataManager;
private ScoreboardManager scoreboardManager;
private SignListener signListener;
@Override
public void onEnable() {
instance = this;
banner();
configManager = new ConfigManager(this);
playerDataManager = new PlayerDataManager(this);
arenaManager = new ArenaManager(this);
scoreboardManager = new ScoreboardManager(this);
gameManager = new GameManager(this);
signListener = new SignListener(this);
getCommand("lasertec").setExecutor(new LasertecCommand(this));
getCommand("ltadmin").setExecutor(new LtAdminCommand(this));
var pm = Bukkit.getPluginManager();
pm.registerEvents(new PlayerListener(this), this);
pm.registerEvents(new WeaponListener(this), this);
pm.registerEvents(new BlockListener(this), this);
pm.registerEvents(signListener, this);
getLogger().info("Lasertec v" + getDescription().getVersion() + " aktiviert! ("
+ signListener.getSignCount() + " Schilder geladen)");
}
@Override
public void onDisable() {
if (signListener != null) signListener.stopUpdateTask();
if (gameManager != null) gameManager.stopAllGames();
if (playerDataManager != null) playerDataManager.saveAll();
getLogger().info("Lasertec deaktiviert.");
}
private void banner() {
getLogger().info("§b ██╗ █████╗ ███████╗███████╗██████╗ ████████╗███████╗ ██████╗");
getLogger().info("§b ██║ ██╔══██╗██╔════╝██╔════╝██╔══██╗╚══██╔══╝██╔════╝██╔════╝");
getLogger().info("§b ██║ ███████║███████╗█████╗ ██████╔╝ ██║ █████╗ ██║ ");
getLogger().info("§b ██║ ██╔══██║╚════██║██╔══╝ ██╔══██╗ ██║ ██╔══╝ ██║ ");
getLogger().info("§b ███████╗██║ ██║███████║███████╗██║ ██║ ██║ ███████╗╚██████╗");
getLogger().info("§7 v" + getDescription().getVersion() + " | Multi-Arena LaserTag | Anti-Camp | Mod-Protect");
}
public static LasertecPlugin getInstance() { return instance; }
public ConfigManager getConfigManager() { return configManager; }
public ArenaManager getArenaManager() { return arenaManager; }
public GameManager getGameManager() { return gameManager; }
public PlayerDataManager getPlayerDataManager() { return playerDataManager; }
public ScoreboardManager getScoreboardManager() { return scoreboardManager; }
public SignListener getSignListener() { return signListener; }
}

View File

@@ -0,0 +1,153 @@
package de.lasertec.arena;
import de.lasertec.game.Team;
import org.bukkit.Location;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Repräsentiert eine konfigurierte Lasertec-Arena.
*
* Jedes Team braucht:
* - mind. 1 Spawn-Punkt
* - 1 Basispunkt (Regenerationspunkt + angreifbarer Block)
*/
public class Arena {
private final String name;
private String displayName;
private boolean enabled;
// Team-Spawns: team -> Liste von Spawn-Locations
private final Map<Team, List<Location>> teamSpawns = new HashMap<>();
// Team-Basis: team -> Location des Basis-Blocks
private final Map<Team, Location> teamBases = new HashMap<>();
// Team-Basis-Gesundheit (wie oft kann Basis angegriffen werden)
private final Map<Team, Integer> baseHealth = new HashMap<>();
// Lobby dieser Arena (optionaler Wartebereich)
private Location lobbyLocation;
// Bounds (optional, für Block-Schutz innerhalb der Arena)
private Location corner1;
private Location corner2;
public Arena(String name) {
this.name = name;
this.displayName = name;
this.enabled = false;
for (Team t : Team.values()) {
teamSpawns.put(t, new ArrayList<>());
}
}
// ─── Validation ──────────────────────────────────────────────────────────
/**
* Arena ist bereit wenn jedes Team mindestens 1 Spawn und 1 Basis hat.
*/
public boolean isReady() {
for (Team t : Team.values()) {
if (teamSpawns.get(t).isEmpty()) return false;
if (!teamBases.containsKey(t)) return false;
}
return true;
}
public String getMissingSetup() {
StringBuilder sb = new StringBuilder();
for (Team t : Team.values()) {
if (teamSpawns.get(t).isEmpty())
sb.append("§c- Kein Spawn für Team ").append(t.colored()).append("§c!\n");
if (!teamBases.containsKey(t))
sb.append("§c- Keine Basis für Team ").append(t.colored()).append("§c!\n");
}
return sb.length() == 0 ? "§aArena bereit!" : sb.toString().trim();
}
// ─── Spawns ──────────────────────────────────────────────────────────────
public void addSpawn(Team team, Location loc) {
teamSpawns.get(team).add(loc.clone());
}
public List<Location> getSpawns(Team team) {
return teamSpawns.get(team);
}
public Location getRandomSpawn(Team team) {
List<Location> spawns = teamSpawns.get(team);
if (spawns.isEmpty()) return null;
return spawns.get((int)(Math.random() * spawns.size())).clone();
}
// ─── Bases ───────────────────────────────────────────────────────────────
public void setBase(Team team, Location loc) {
teamBases.put(team, loc.clone());
}
public Location getBase(Team team) {
return teamBases.get(team);
}
public boolean hasBase(Team team) {
return teamBases.containsKey(team);
}
/** Liefert das Team dessen Basis an der gegebenen Location steht, oder null. */
public Team getBaseTeam(Location loc) {
for (Map.Entry<Team, Location> e : teamBases.entrySet()) {
Location base = e.getValue();
if (base.getWorld().equals(loc.getWorld())
&& base.getBlockX() == loc.getBlockX()
&& base.getBlockY() == loc.getBlockY()
&& base.getBlockZ() == loc.getBlockZ()) {
return e.getKey();
}
}
return null;
}
// ─── Base Health ─────────────────────────────────────────────────────────
public void initBaseHealth(int maxHealth) {
for (Team t : Team.values()) baseHealth.put(t, maxHealth);
}
public int getBaseHealth(Team team) {
return baseHealth.getOrDefault(team, 0);
}
public boolean damageBase(Team team) {
int hp = baseHealth.getOrDefault(team, 0);
if (hp <= 0) return false;
baseHealth.put(team, hp - 1);
return true;
}
public boolean isBaseDestroyed(Team team) {
return baseHealth.getOrDefault(team, 0) <= 0;
}
// ─── Getters / Setters ───────────────────────────────────────────────────
public String getName() { return name; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String n) { this.displayName = n; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean e) { this.enabled = e; }
public Location getLobbyLocation() { return lobbyLocation; }
public void setLobbyLocation(Location l) { this.lobbyLocation = l != null ? l.clone() : null; }
public Location getCorner1() { return corner1; }
public void setCorner1(Location l) { this.corner1 = l != null ? l.clone() : null; }
public Location getCorner2() { return corner2; }
public void setCorner2(Location l) { this.corner2 = l != null ? l.clone() : null; }
public Map<Team, List<Location>> getAllSpawns() { return teamSpawns; }
public Map<Team, Location> getAllBases() { return teamBases; }
}

View File

@@ -0,0 +1,163 @@
package de.lasertec.arena;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Team;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class ArenaManager {
private final LasertecPlugin plugin;
private final Map<String, Arena> arenas = new LinkedHashMap<>();
private File file;
private FileConfiguration cfg;
public ArenaManager(LasertecPlugin plugin) {
this.plugin = plugin;
load();
}
// ─── CRUD ────────────────────────────────────────────────────────────────
public Arena createArena(String name) {
String key = name.toLowerCase();
if (arenas.containsKey(key)) return null;
Arena arena = new Arena(name);
arenas.put(key, arena);
save();
return arena;
}
public Arena getArena(String name) {
return arenas.get(name.toLowerCase());
}
public boolean deleteArena(String name) {
if (arenas.remove(name.toLowerCase()) == null) return false;
save();
return true;
}
public Collection<Arena> getAll() { return Collections.unmodifiableCollection(arenas.values()); }
public List<Arena> getReady() {
List<Arena> list = new ArrayList<>();
for (Arena a : arenas.values())
if (a.isReady() && a.isEnabled()) list.add(a);
return list;
}
// ─── Persistence ─────────────────────────────────────────────────────────
private void load() {
file = new File(plugin.getDataFolder(), "arenas.yml");
if (!file.exists()) {
try { plugin.getDataFolder().mkdirs(); file.createNewFile(); }
catch (IOException ex) { ex.printStackTrace(); return; }
}
cfg = YamlConfiguration.loadConfiguration(file);
ConfigurationSection root = cfg.getConfigurationSection("arenas");
if (root == null) return;
for (String key : root.getKeys(false)) {
ConfigurationSection sec = root.getConfigurationSection(key);
if (sec == null) continue;
Arena arena = new Arena(sec.getString("display-name", key));
arena.setEnabled(sec.getBoolean("enabled", false));
// Lobby
if (sec.contains("lobby"))
arena.setLobbyLocation(sec.getLocation("lobby"));
// Corners
if (sec.contains("corner1")) arena.setCorner1(sec.getLocation("corner1"));
if (sec.contains("corner2")) arena.setCorner2(sec.getLocation("corner2"));
// Team spawns
ConfigurationSection spawnsSec = sec.getConfigurationSection("spawns");
if (spawnsSec != null) {
for (Team t : Team.values()) {
List<Map<?,?>> locs = spawnsSec.getMapList(t.name());
for (Map<?,?> m : locs) {
Location loc = deserializeLoc(m);
if (loc != null) arena.addSpawn(t, loc);
}
}
}
// Team bases
ConfigurationSection basesSec = sec.getConfigurationSection("bases");
if (basesSec != null) {
for (Team t : Team.values()) {
if (basesSec.contains(t.name())) {
Location loc = basesSec.getLocation(t.name());
if (loc != null) arena.setBase(t, loc);
}
}
}
arenas.put(key.toLowerCase(), arena);
plugin.getLogger().info("Arena geladen: §e" + key + (arena.isReady() ? " §a[OK]" : " §c[Setup unvollständig]"));
}
}
public void save() {
cfg.set("arenas", null);
for (Map.Entry<String, Arena> entry : arenas.entrySet()) {
Arena a = entry.getValue();
String path = "arenas." + entry.getKey() + ".";
cfg.set(path + "display-name", a.getDisplayName());
cfg.set(path + "enabled", a.isEnabled());
if (a.getLobbyLocation() != null)
cfg.set(path + "lobby", a.getLobbyLocation());
if (a.getCorner1() != null) cfg.set(path + "corner1", a.getCorner1());
if (a.getCorner2() != null) cfg.set(path + "corner2", a.getCorner2());
// Spawns
for (Team t : Team.values()) {
List<Map<String, Object>> list = new ArrayList<>();
for (Location loc : a.getSpawns(t)) list.add(serializeLoc(loc));
cfg.set(path + "spawns." + t.name(), list);
}
// Bases
for (Map.Entry<Team, Location> be : a.getAllBases().entrySet()) {
cfg.set(path + "bases." + be.getKey().name(), be.getValue());
}
}
try { cfg.save(file); } catch (IOException ex) { ex.printStackTrace(); }
}
// ─── Location serialization (compatible without external libs) ───────────
private Map<String, Object> serializeLoc(Location loc) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("world", loc.getWorld().getName());
m.put("x", loc.getX()); m.put("y", loc.getY()); m.put("z", loc.getZ());
m.put("yaw", (double) loc.getYaw());
m.put("pitch", (double) loc.getPitch());
return m;
}
private Location deserializeLoc(Map<?,?> m) {
try {
String world = (String) m.get("world");
double x = ((Number) m.get("x")).doubleValue();
double y = ((Number) m.get("y")).doubleValue();
double z = ((Number) m.get("z")).doubleValue();
float yaw = m.containsKey("yaw") ? ((Number)m.get("yaw")).floatValue() : 0f;
float pitch = m.containsKey("pitch") ? ((Number)m.get("pitch")).floatValue() : 0f;
return new Location(plugin.getServer().getWorld(world), x, y, z, yaw, pitch);
} catch (Exception ex) { return null; }
}
}

View File

@@ -0,0 +1,149 @@
package de.lasertec.camp;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import de.lasertec.player.LaserPlayer;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Anti-Camp System.
*
* Erkennt Spieler die sich zu lange nicht bewegen und bestraft sie.
*
* Ablauf:
* 1. Position wird jede Sekunde geprüft
* 2. Hat sich der Spieler < camp-idle-radius Blöcke bewegt → idle-Zeit erhöhen
* 3. Nach warn-duration Sekunden → Warnung
* 4. Nach max-idle-seconds Sekunden → Strafe (Punkte-Abzug)
*
* Ausnahmen:
* - Spieler nahe der eigenen Basis (heilen)
* - Spieler die getroffen wurden (müssen zur Basis)
*/
public class AntiCampManager {
private final LasertecPlugin plugin;
/** UUID → letzte bekannte Position */
private final Map<UUID, Location> lastPosition = new HashMap<>();
/** UUID → Sekunden an gleicher Stelle */
private final Map<UUID, Integer> idleSeconds = new HashMap<>();
/** UUID → wurde bereits gewarnt (true = Warnung lief, false = noch nicht) */
private final Map<UUID, Boolean> warned = new HashMap<>();
public AntiCampManager(LasertecPlugin plugin) {
this.plugin = plugin;
}
/**
* Wird vom Game-Task jede Sekunde aufgerufen.
* Prüft alle Spieler des Spiels auf Camping-Verhalten.
*/
public void tick(Game game) {
if (!plugin.getConfigManager().isAntiCampEnabled()) return;
double idleRadius = plugin.getConfigManager().getCampIdleRadius();
double excludeRadius = plugin.getConfigManager().getCampExcludeRadius();
int maxIdle = plugin.getConfigManager().getCampMaxIdleSecs();
int warnDuration = plugin.getConfigManager().getCampWarnDuration();
String action = plugin.getConfigManager().getCampAction();
int penalty = plugin.getConfigManager().getCampScorePenalty();
for (Player player : game.getOnline()) {
UUID uid = player.getUniqueId();
LaserPlayer lp = game.getLP(uid);
if (lp == null) continue;
// Getroffene Spieler vom Camp-Check ausnehmen
if (lp.isHit()) {
reset(uid);
continue;
}
// Spieler nahe eigener Basis: ausschließen (Heilzone)
Location base = game.getArena().getBase(lp.getTeam());
if (base != null && player.getLocation().distanceSquared(base) <= excludeRadius * excludeRadius) {
reset(uid);
continue;
}
Location prev = lastPosition.get(uid);
Location curr = player.getLocation();
if (prev == null) {
lastPosition.put(uid, curr.clone());
idleSeconds.put(uid, 0);
warned.put(uid, false);
continue;
}
// Hat sich der Spieler bewegt?
double moved = prev.distanceSquared(curr);
if (moved > idleRadius * idleRadius) {
// Bewegt → Reset
lastPosition.put(uid, curr.clone());
idleSeconds.put(uid, 0);
warned.put(uid, false);
continue;
}
// Still gestanden
int idle = idleSeconds.merge(uid, 1, Integer::sum);
// Warnphase
if (action.contains("WARN") && idle == warnDuration && !warned.getOrDefault(uid, false)) {
warned.put(uid, true);
player.sendMessage(plugin.getConfigManager().getPrefix()
+ plugin.getConfigManager().getCampWarnMsg());
player.playSound(player.getLocation(),
plugin.getConfigManager().getCampWarnSound(), 1f,
plugin.getConfigManager().getCampWarnSoundPitch());
}
// Strafphase
if (action.contains("PUNISH") && idle >= maxIdle) {
lp.addScore(-penalty);
// Score darf nicht unter 0 fallen
if (lp.getScore() < 0) lp.addScore(-lp.getScore());
player.sendMessage(plugin.getConfigManager().getPrefix()
+ "§c⛔ Camp-Strafe: §7-" + penalty + " Punkte (Bewege dich!)");
player.playSound(player.getLocation(),
plugin.getConfigManager().getCampWarnSound(), 1f,
plugin.getConfigManager().getCampWarnSoundPitch());
// Idle-Zähler auf maxIdle halten (weiter bestrafen jede Sekunde)
idleSeconds.put(uid, maxIdle);
}
}
}
/** Spieler aus dem Camp-Tracking entfernen (bei Leave/End). */
public void remove(UUID uuid) {
lastPosition.remove(uuid);
idleSeconds.remove(uuid);
warned.remove(uuid);
}
/** Alle Daten zurücksetzen (bei Spiel-Reset). */
public void reset() {
lastPosition.clear();
idleSeconds.clear();
warned.clear();
}
private void reset(UUID uid) {
lastPosition.remove(uid);
idleSeconds.put(uid, 0);
warned.put(uid, false);
}
public int getIdleSeconds(UUID uuid) {
return idleSeconds.getOrDefault(uuid, 0);
}
}

View File

@@ -0,0 +1,126 @@
package de.lasertec.command;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import de.lasertec.game.Team;
import de.lasertec.player.LaserPlayer;
import de.lasertec.player.PlayerStats;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
public class LasertecCommand implements CommandExecutor {
private final LasertecPlugin plugin;
public LasertecCommand(LasertecPlugin plugin) { this.plugin = plugin; }
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
String pre = plugin.getConfigManager().getPrefix();
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
help(sender);
return true;
}
switch (args[0].toLowerCase()) {
// ── join ──────────────────────────────────────────────────────────
case "join", "j" -> {
if (!(sender instanceof Player player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
if (!player.hasPermission("lasertec.play")) {
player.sendMessage(pre + "§cKeine Berechtigung (lasertec.play)");
return true;
}
if (args.length >= 2) plugin.getGameManager().joinGame(player, args[1]);
else plugin.getGameManager().joinBest(player);
}
// ── leave ─────────────────────────────────────────────────────────
case "leave", "l" -> {
if (!(sender instanceof Player player)) return true;
Game g = plugin.getGameManager().getGameOf(player);
if (g == null) player.sendMessage(pre + "§cDu bist in keinem Spiel!");
else plugin.getGameManager().leaveGame(player);
}
// ── list ──────────────────────────────────────────────────────────
case "list" -> {
sender.sendMessage("§8§l═══════════════════════════════════");
sender.sendMessage("§b§l ⚡ LASERTEC ARENEN");
sender.sendMessage("§8§l═══════════════════════════════════");
var games = plugin.getGameManager().getAllGames();
if (games.isEmpty()) { sender.sendMessage("§c Keine Arenen konfiguriert."); return true; }
for (Game g : games) {
String status = switch (g.getState()) {
case WAITING -> "§aWartend";
case STARTING -> "§eStartet";
case RUNNING -> "§6Läuft";
case ENDING -> "§7Beendet";
};
String ready = g.getArena().isReady() ? "§a✔" : "§c✘";
sender.sendMessage(" " + ready + " §b" + g.getArena().getName()
+ " §8[" + status + "§8] §7Spieler: §e" + g.getPlayerCount()
+ "§7/§e" + g.getMaxPlayers());
}
sender.sendMessage("§8§l═══════════════════════════════════");
}
// ── stats ─────────────────────────────────────────────────────────
case "stats" -> {
if (!(sender instanceof Player player)) return true;
PlayerStats s = plugin.getPlayerDataManager().getStats(player.getUniqueId());
sender.sendMessage("§8§l══════════════════════════════");
sender.sendMessage("§b§l ⚡ DEINE STATISTIKEN");
sender.sendMessage("§8§l══════════════════════════════");
sender.sendMessage("§7Spiele: §e" + s.getGamesPlayed());
sender.sendMessage("§7Kills gesamt: §a" + s.getTotalKills());
sender.sendMessage("§7Tode gesamt: §c" + s.getTotalDeaths());
sender.sendMessage("§7K/D-Ratio: §6" + s.getKDR());
sender.sendMessage("§7Punkte gesamt: §6" + s.getTotalScore());
sender.sendMessage("§7Beste Kill-Serie:§b " + s.getBestStreak());
sender.sendMessage("§7Basis-Angriffe: §d" + s.getTotalBaseAttacks());
sender.sendMessage("§8§l══════════════════════════════");
}
// ── top ───────────────────────────────────────────────────────────
case "top" -> {
List<PlayerStats> top = plugin.getPlayerDataManager().getTopByScore(10);
sender.sendMessage("§8§l═══════════════════════════════");
sender.sendMessage("§6§l 🏆 BESTENLISTE");
sender.sendMessage("§8§l═══════════════════════════════");
for (int i = 0; i < top.size(); i++) {
PlayerStats s = top.get(i);
String medal = i == 0 ? "§6§l🥇" : i == 1 ? "§7§l🥈" : i == 2 ? "§c§l🥉" : "§8#" + (i+1);
sender.sendMessage(" " + medal + " §e" + s.getName()
+ " §7│ §6" + s.getTotalScore() + " Pkt §7│ §aK:" + s.getTotalKills()
+ " §7│ §bKDR:" + s.getKDR());
}
if (top.isEmpty()) sender.sendMessage("§7Noch keine Statistiken vorhanden.");
sender.sendMessage("§8§l═══════════════════════════════");
}
default -> help(sender);
}
return true;
}
private void help(CommandSender s) {
s.sendMessage("§8§l═══════════════════════════════════");
s.sendMessage("§b§l ⚡ LASERTEC HILFE");
s.sendMessage("§8§l═══════════════════════════════════");
s.sendMessage("§b/lt join §8[Arena] §7 Einem Spiel beitreten");
s.sendMessage("§b/lt leave §7 Spiel verlassen");
s.sendMessage("§b/lt list §7 Arenen anzeigen");
s.sendMessage("§b/lt stats §7 Deine Statistiken");
s.sendMessage("§b/lt top §7 Bestenliste (Top 10)");
if (s.hasPermission("lasertec.admin"))
s.sendMessage("§c/ltadmin help §7 Admin-Befehle");
s.sendMessage("§8§l═══════════════════════════════════");
}
}

View File

@@ -0,0 +1,220 @@
package de.lasertec.command;
import de.lasertec.LasertecPlugin;
import de.lasertec.arena.Arena;
import de.lasertec.game.Game;
import de.lasertec.game.Team;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class LtAdminCommand implements CommandExecutor {
private final LasertecPlugin plugin;
public LtAdminCommand(LasertecPlugin plugin) { this.plugin = plugin; }
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
if (!sender.hasPermission("lasertec.admin")) {
sender.sendMessage("§cKeine Berechtigung!");
return true;
}
String pre = plugin.getConfigManager().getPrefix();
if (args.length == 0 || args[0].equalsIgnoreCase("help")) {
help(sender);
return true;
}
switch (args[0].toLowerCase()) {
// ── Arena erstellen ───────────────────────────────────────────────
case "create", "createarena" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin create <Name>"); return true; }
Arena arena = plugin.getArenaManager().createArena(args[1]);
if (arena == null) { sender.sendMessage(pre + "§cArena existiert bereits!"); return true; }
plugin.getGameManager().createGame(arena);
sender.sendMessage(pre + "§aArena §e" + args[1] + " §aerstellt.");
sender.sendMessage(pre + "§7Nächste Schritte:");
sender.sendMessage("§7 /ltadmin setspawn §e" + args[1] + " red §7(stehe am Spawn)");
sender.sendMessage("§7 /ltadmin setbase §e" + args[1] + " red §7(stehe an der Basis)");
sender.sendMessage("§7 → Für alle 4 Teams wiederholen");
sender.sendMessage("§7 /ltadmin enable §e" + args[1]);
}
// ── Arena löschen ─────────────────────────────────────────────────
case "delete", "deletearena" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin delete <Name>"); return true; }
if (plugin.getArenaManager().deleteArena(args[1]))
sender.sendMessage(pre + "§aArena §e" + args[1] + " §agelöscht.");
else sender.sendMessage(pre + "§cArena nicht gefunden!");
}
// ── Spawn setzen ──────────────────────────────────────────────────
// /ltadmin setspawn <Arena> <red|blue|green|yellow>
case "setspawn" -> {
if (!(sender instanceof Player player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
if (args.length < 3) { sender.sendMessage(pre + "§c/ltadmin setspawn <Arena> <red|blue|green|yellow>"); return true; }
Arena arena = requireArena(sender, args[1]); if (arena == null) return true;
Team team = requireTeam(sender, args[2]); if (team == null) return true;
arena.addSpawn(team, player.getLocation());
plugin.getArenaManager().save();
sender.sendMessage(pre + "§aSpawn für Team " + team.colored() + " §ain Arena §e"
+ arena.getName() + " §ahinzugefügt. §8(Gesamt: " + arena.getSpawns(team).size() + ")");
}
// ── Basis setzen ──────────────────────────────────────────────────
// /ltadmin setbase <Arena> <red|blue|green|yellow>
case "setbase" -> {
if (!(sender instanceof Player player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
if (args.length < 3) { sender.sendMessage(pre + "§c/ltadmin setbase <Arena> <red|blue|green|yellow>"); return true; }
Arena arena = requireArena(sender, args[1]); if (arena == null) return true;
Team team = requireTeam(sender, args[2]); if (team == null) return true;
arena.setBase(team, player.getLocation());
plugin.getArenaManager().save();
sender.sendMessage(pre + "§aBasis für Team " + team.colored() + " §aauf §e"
+ blockStr(player) + " §agesetzt.");
sender.sendMessage(pre + "§7Beim Spielstart wird hier ein §b"
+ team.getGlassMat().name() + " §7platziert.");
}
// ── Lobby setzen ──────────────────────────────────────────────────
case "setlobby" -> {
if (!(sender instanceof Player player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
plugin.getConfigManager().setLobbyLocation(player.getLocation());
sender.sendMessage(pre + "§aLobby-Spawn gesetzt!");
}
// ── Arena aktivieren/deaktivieren ─────────────────────────────────
case "enable" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin enable <Arena>"); return true; }
Arena arena = requireArena(sender, args[1]); if (arena == null) return true;
if (!arena.isReady()) {
sender.sendMessage(pre + "§cArena nicht bereit!\n" + arena.getMissingSetup());
return true;
}
arena.setEnabled(true);
plugin.getArenaManager().save();
sender.sendMessage(pre + "§aArena §e" + arena.getName() + " §aaktiviert!");
}
case "disable" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin disable <Arena>"); return true; }
Arena arena = requireArena(sender, args[1]); if (arena == null) return true;
arena.setEnabled(false);
plugin.getArenaManager().save();
sender.sendMessage(pre + "§cArena §e" + arena.getName() + " §cdeaktiviert.");
}
// ── Arena-Info ────────────────────────────────────────────────────
case "info" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin info <Arena>"); return true; }
Arena arena = requireArena(sender, args[1]); if (arena == null) return true;
sender.sendMessage("§8§l══════════════════════════════");
sender.sendMessage("§b§l Arena: §e" + arena.getName());
sender.sendMessage("§8§l══════════════════════════════");
sender.sendMessage("§7Aktiviert: " + (arena.isEnabled() ? "§aJa" : "§cNein"));
sender.sendMessage("§7Bereit: " + (arena.isReady() ? "§aJa" : "§cNein"));
for (Team t : Team.values()) {
int spawns = arena.getSpawns(t).size();
boolean hasBase = arena.hasBase(t);
sender.sendMessage(" " + t.colored() + " §7 Spawns: §e" + spawns
+ " §7Basis: " + (hasBase ? "§a✔" : "§c✘"));
}
if (!arena.isReady()) {
sender.sendMessage("§c§lFehlend:");
sender.sendMessage(arena.getMissingSetup());
}
sender.sendMessage("§8§l══════════════════════════════");
// Spiel-Status
Game g = plugin.getGameManager().getGame(arena.getName());
if (g != null) {
sender.sendMessage("§7Spiel-Status: " + g.getState()
+ " §7Spieler: §e" + g.getPlayerCount());
}
}
// ── Spiel erzwingen ───────────────────────────────────────────────
case "forcestart" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin forcestart <Arena>"); return true; }
Game g = plugin.getGameManager().getGame(args[1]);
if (g == null) { sender.sendMessage(pre + "§cArena/Spiel nicht gefunden!"); return true; }
switch (g.getState()) {
case WAITING -> { g.startCountdown(); sender.sendMessage(pre + "§aCountdown gestartet!"); }
case STARTING -> { g.startGame(); sender.sendMessage(pre + "§aSpiel gestartet!"); }
default -> sender.sendMessage(pre + "§cSpiel läuft bereits!");
}
}
case "forcestop" -> {
if (args.length < 2) { sender.sendMessage(pre + "§c/ltadmin forcestop <Arena>"); return true; }
Game g = plugin.getGameManager().getGame(args[1]);
if (g == null) { sender.sendMessage(pre + "§cArena/Spiel nicht gefunden!"); return true; }
g.forceEnd();
sender.sendMessage(pre + "§aSpiel beendet!");
}
// ── Reload ────────────────────────────────────────────────────────
case "reload" -> {
plugin.getConfigManager().reload();
sender.sendMessage(pre + "§aConfig neu geladen!");
}
case "modinfo" -> {
new de.lasertec.protection.ModProtectionManager(plugin).sendProtectionInfo(sender);
}
case "signs" -> {
sender.sendMessage(pre + "§7Registrierte Join-Schilder: §b" + plugin.getSignListener().getSignCount());
sender.sendMessage(pre + "§7Erstelle ein Schild: Zeile 1 = §e[Lasertec] §7| Zeile 2 = Arenaname");
}
default -> help(sender);
}
return true;
}
// ─── Helper ──────────────────────────────────────────────────────────────
private Arena requireArena(CommandSender s, String name) {
Arena a = plugin.getArenaManager().getArena(name);
if (a == null) s.sendMessage(plugin.getConfigManager().getPrefix() + "§cArena '§e" + name + "§c' nicht gefunden!");
return a;
}
private Team requireTeam(CommandSender s, String name) {
try { return Team.valueOf(name.toUpperCase()); }
catch (IllegalArgumentException ex) {
s.sendMessage(plugin.getConfigManager().getPrefix()
+ "§cUngültiges Team '§e" + name + "§c'! Benutze: red, blue, green, yellow");
return null;
}
}
private String blockStr(Player p) {
return p.getLocation().getBlockX() + "," + p.getLocation().getBlockY() + "," + p.getLocation().getBlockZ();
}
private void help(CommandSender s) {
s.sendMessage("§8§l══════════════════════════════════════════");
s.sendMessage("§c§l ⚡ LASERTEC ADMIN-BEFEHLE");
s.sendMessage("§8§l══════════════════════════════════════════");
s.sendMessage("§c/ltadmin create §8<Name> §7 Arena erstellen");
s.sendMessage("§c/ltadmin delete §8<Name> §7 Arena löschen");
s.sendMessage("§c/ltadmin setspawn §8<Arena> <Team> §7 Spawn setzen (stehe am Punkt)");
s.sendMessage("§c/ltadmin setbase §8<Arena> <Team> §7 Basis-Block setzen");
s.sendMessage("§c/ltadmin setlobby §7 Lobby-Spawn setzen");
s.sendMessage("§c/ltadmin enable §8<Arena> §7 Arena aktivieren");
s.sendMessage("§c/ltadmin disable §8<Arena> §7 Arena deaktivieren");
s.sendMessage("§c/ltadmin info §8<Arena> §7 Arena-Infos anzeigen");
s.sendMessage("§c/ltadmin forcestart §8<Arena> §7 Countdown/Spiel starten");
s.sendMessage("§c/ltadmin forcestop §8<Arena> §7 Spiel beenden");
s.sendMessage("§c/ltadmin reload §7 Config neu laden");
s.sendMessage("§8§l══════════════════════════════════════════");
s.sendMessage("§c/ltadmin modinfo §7 Mod-Schutz Status anzeigen");
s.sendMessage("§c/ltadmin signs §7 Schild-Übersicht");
s.sendMessage("§8§l══════════════════════════════════════════");
s.sendMessage("§7Teams: §cred §9blue §agreen §eyelow");
}
}

View File

@@ -0,0 +1,178 @@
package de.lasertec.config;
import de.lasertec.LasertecPlugin;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.Sound;
/**
* Zentraler Zugriffspunkt für alle config.yml-Werte.
* Alle Getter lesen live aus dem Config-Objekt → /ltadmin reload wirkt sofort.
*/
public class ConfigManager {
private final LasertecPlugin plugin;
public ConfigManager(LasertecPlugin plugin) {
this.plugin = plugin;
plugin.saveDefaultConfig();
}
public void reload() {
plugin.reloadConfig();
}
// ── Allgemein ─────────────────────────────────────────────────────────────
public String getPrefix() {
return c().getString("messages.prefix", "§8[§b§lLASERTEC§8] §r");
}
public Location getLobbyLocation() {
return (Location) c().get("lobby.location");
}
public void setLobbyLocation(Location loc) {
c().set("lobby.location", loc);
plugin.saveConfig();
}
// ── Spiel-Grundeinstellungen ──────────────────────────────────────────────
public int getGameDuration() { return c().getInt("game.game-duration", 300); }
public int getCountdown() { return c().getInt("game.countdown", 10); }
public int getMinPlayers() { return c().getInt("game.min-players", 2); }
public int getMaxPlayersPerTeam() { return c().getInt("game.max-players-per-team", 4); }
public int getEndDisplayTime() { return c().getInt("game.end-display-time", 8); }
// ── Heal / Hit-Mechanik ───────────────────────────────────────────────────
public int getBaseHealTime() { return c().getInt("heal.base-heal-time", 2); }
public double getBaseRadius() { return c().getDouble("heal.base-radius", 4.0); }
public boolean isInvincibleAfterHeal() { return c().getBoolean("heal.invincible-after-heal", true); }
public int getInvincibleDuration() { return c().getInt("heal.invincible-duration", 60); }
// ── Punkte-System ─────────────────────────────────────────────────────────
public int getKillPoints() { return c().getInt("scoring.kill-points", 100); }
public int getBaseAttackPoints() { return c().getInt("scoring.base-attack-points", 60); }
public int getBaseDestroyBonus() { return c().getInt("scoring.base-destroy-bonus", 200); }
public int getStreak3Bonus() { return c().getInt("scoring.streak-3-bonus", 50); }
public int getStreak5Bonus() { return c().getInt("scoring.streak-5-bonus", 100); }
public int getStreak10Bonus() { return c().getInt("scoring.streak-10-bonus", 250); }
// ── Basen ─────────────────────────────────────────────────────────────────
public int getBaseHealth() { return c().getInt("base.health", 5); }
public int getBaseWarnHp() { return c().getInt("base.warn-at-hp", 2); }
public boolean isBaseRegenerate() { return c().getBoolean("base.regenerate", false); }
public int getBaseRegenInterval() { return c().getInt("base.regenerate-interval", 60); }
// ── Anti-Camp ─────────────────────────────────────────────────────────────
public boolean isAntiCampEnabled() { return c().getBoolean("anti-camp.enabled", true); }
public int getCampMaxIdleSecs() { return c().getInt("anti-camp.max-idle-seconds", 15); }
public double getCampIdleRadius() { return c().getDouble("anti-camp.idle-radius", 5.0); }
public String getCampAction() { return c().getString("anti-camp.action", "WARN_THEN_PUNISH"); }
public String getCampWarnMsg() { return c().getString("anti-camp.warn-message", "§c⚠ CAMPEN VERBOTEN!"); }
public int getCampScorePenalty() { return c().getInt("anti-camp.score-penalty", 10); }
public int getCampWarnDuration() { return c().getInt("anti-camp.warn-duration", 5); }
public double getCampExcludeRadius() { return c().getDouble("anti-camp.exclude-base-radius", 8.0); }
public Sound getCampWarnSound() {
try { return Sound.valueOf(c().getString("anti-camp.warn-sound", "BLOCK_NOTE_BLOCK_BASS")); }
catch (Exception e) { return Sound.BLOCK_NOTE_BLOCK_BASS; }
}
public float getCampWarnSoundPitch() { return (float) c().getDouble("anti-camp.warn-sound-pitch", 0.5); }
// ── Mod-Schutz ────────────────────────────────────────────────────────────
public boolean isModProtectionEnabled() { return c().getBoolean("mod-protection.enabled", true); }
public boolean isFogOfWarEnabled() { return c().getBoolean("mod-protection.fog-of-war", true); }
public int getFogRadius() { return c().getInt("mod-protection.fog-radius", 48); }
public boolean isArenaBarrierEnabled() { return c().getBoolean("mod-protection.arena-barrier", true); }
public boolean isHideCoordinates() { return c().getBoolean("mod-protection.hide-coordinates",true); }
public boolean isHideFromTab() { return c().getBoolean("mod-protection.hide-from-tab", true); }
public boolean isHideNametags() { return c().getBoolean("mod-protection.hide-nametags", true); }
public boolean isStrictInvisibility() { return c().getBoolean("mod-protection.strict-invisibility", true); }
// ── Join-Schild ───────────────────────────────────────────────────────────
public String getSignTriggerLine() { return c().getString("join-sign.trigger-line", "[Lasertec]"); }
public String getSignColorWaiting() { return c().getString("join-sign.color-waiting", "§a"); }
public String getSignColorStarting(){ return c().getString("join-sign.color-starting","§e"); }
public String getSignColorRunning() { return c().getString("join-sign.color-running", "§c"); }
public String getSignColorFull() { return c().getString("join-sign.color-full", "§8"); }
public int getSignUpdateInterval(){ return c().getInt("join-sign.update-interval", 20); }
// ── Waffen (dynamisch aus config lesen) ───────────────────────────────────
public int getWeaponDamage(String key) { return c().getInt("weapons." + key + ".damage", 25); }
public int getWeaponRange(String key) { return c().getInt("weapons." + key + ".range", 30); }
public int getWeaponCooldown(String key) { return c().getInt("weapons." + key + ".cooldown-ms", 300); }
public int getWeaponPellets(String key) { return c().getInt("weapons." + key + ".pellets", 1); }
public String getWeaponName(String key) { return c().getString("weapons." + key + ".display-name", "§fWaffe"); }
public boolean isWeaponEnabled(String key) { return c().getBoolean("weapons." + key + ".enabled", true); }
public String getWeaponDesc(String key) { return c().getString("weapons." + key + ".description", ""); }
public Particle getWeaponParticle(String key) {
try { return Particle.valueOf(c().getString("weapons." + key + ".particle", "CRIT")); }
catch (Exception e) { return Particle.CRIT; }
}
// ── Sounds ────────────────────────────────────────────────────────────────
public boolean isSoundsEnabled() { return c().getBoolean("sounds.enabled", true); }
public Sound getSound(String key, Sound fallback) {
if (!isSoundsEnabled()) return null;
try { return Sound.valueOf(c().getString("sounds." + key, fallback.name())); }
catch (Exception e) { return fallback; }
}
public float getSoundPitch(String key, float fallback) {
return (float) c().getDouble("sounds." + key, fallback);
}
public float getSoundVolume(String key, float fallback) {
return (float) c().getDouble("sounds." + key, fallback);
}
// ── Partikel ──────────────────────────────────────────────────────────────
public boolean isParticlesEnabled() { return c().getBoolean("particles.enabled", true); }
public boolean isLaserTrailEnabled() { return c().getBoolean("particles.laser-trail", true); }
public boolean isHitEffectEnabled() { return c().getBoolean("particles.hit-effect", true); }
public int getHitParticleCount() { return c().getInt("particles.hit-particle-count", 15); }
public boolean isHealEffectEnabled() { return c().getBoolean("particles.heal-effect", true); }
// ── Scoreboard ────────────────────────────────────────────────────────────
public boolean isScoreboardEnabled() { return c().getBoolean("scoreboard.enabled", true); }
public String getScoreboardTitle() { return c().getString("scoreboard.title", "§b§l⚡ LASERTEC"); }
public boolean showTeamScores() { return c().getBoolean("scoreboard.show-team-scores", true); }
public boolean showBaseHealth() { return c().getBoolean("scoreboard.show-base-health", true); }
public boolean showKillStreak() { return c().getBoolean("scoreboard.show-kill-streak", true); }
public int getScoreboardInterval() { return c().getInt("scoreboard.update-interval", 20); }
// ── Texte / Nachrichten ───────────────────────────────────────────────────
public String getText(String key, String fallback) {
return getPrefix() + c().getString("text." + key, fallback);
}
/** Ersetzt Platzhalter wie {player}, {team}, {pts} etc. */
public String getText(String key, String fallback, Object... replacements) {
String s = getText(key, fallback);
for (int i = 0; i + 1 < replacements.length; i += 2) {
s = s.replace("{" + replacements[i] + "}", String.valueOf(replacements[i + 1]));
}
return s;
}
// ── Intern ───────────────────────────────────────────────────────────────
private org.bukkit.configuration.file.FileConfiguration c() {
return plugin.getConfig();
}
}

View File

@@ -0,0 +1,733 @@
package de.lasertec.game;
import de.lasertec.LasertecPlugin;
import de.lasertec.arena.Arena;
import de.lasertec.camp.AntiCampManager;
import de.lasertec.player.LaserPlayer;
import de.lasertec.protection.ModProtectionManager;
import de.lasertec.weapon.WeaponType;
import de.lasertec.weapon.WeaponUtil;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class Game {
private final LasertecPlugin plugin;
private final Arena arena;
private final AntiCampManager antiCamp;
private final ModProtectionManager modProtect;
private GameState state = GameState.WAITING;
private int timeLeft;
private int countdown;
private final Map<UUID, LaserPlayer> players = new ConcurrentHashMap<>();
private final Map<Team, Integer> teamScore = new LinkedHashMap<>();
private final Map<UUID, Long> shotCooldown= new ConcurrentHashMap<>();
private BukkitTask countdownTask, gameTask, healTask;
public Game(LasertecPlugin plugin, Arena arena) {
this.plugin = plugin;
this.arena = arena;
this.antiCamp = new AntiCampManager(plugin);
this.modProtect = new ModProtectionManager(plugin);
this.timeLeft = plugin.getConfigManager().getGameDuration();
this.countdown = plugin.getConfigManager().getCountdown();
for (Team t : Team.values()) teamScore.put(t, 0);
arena.initBaseHealth(plugin.getConfigManager().getBaseHealth());
}
// ──────────────────────────────────────────────────────────────────────────
// SPIELER-VERWALTUNG
// ──────────────────────────────────────────────────────────────────────────
public boolean addPlayer(Player player) {
if (players.containsKey(player.getUniqueId())) return false;
if (!isJoinable()) return false;
int maxTotal = plugin.getConfigManager().getMaxPlayersPerTeam() * 4;
if (players.size() >= maxTotal) return false;
LaserPlayer lp = new LaserPlayer(player.getUniqueId(), player.getName());
lp.setTeam(pickBalancedTeam());
lp.setSavedInventory(player.getInventory().getContents().clone());
players.put(player.getUniqueId(), lp);
setupPlayer(player, lp);
sendToSpawn(player, lp.getTeam());
modProtect.applyProtection(player, this);
broadcast(plugin.getConfigManager().getText("join",
"§e{player} §7hat Team {team} §7beigetreten.",
"player", player.getName(), "team", lp.getTeam().colored()));
plugin.getScoreboardManager().update(player, this);
if (state == GameState.WAITING
&& players.size() >= plugin.getConfigManager().getMinPlayers()) {
startCountdown();
}
return true;
}
public void removePlayer(Player player) {
LaserPlayer lp = players.remove(player.getUniqueId());
if (lp == null) return;
shotCooldown.remove(player.getUniqueId());
antiCamp.remove(player.getUniqueId());
modProtect.removeProtection(player);
saveStats(lp, player);
restorePlayer(player, lp);
plugin.getScoreboardManager().remove(player);
broadcast(plugin.getConfigManager().getText("leave",
"§c{player} §7hat das Spiel verlassen.", "player", player.getName()));
if (state == GameState.RUNNING
&& players.size() < plugin.getConfigManager().getMinPlayers()) {
endGame();
}
}
private void saveStats(LaserPlayer lp, Player player) {
var stats = plugin.getPlayerDataManager().getStats(lp.getUuid());
stats.setName(player.getName());
stats.apply(lp);
}
// ──────────────────────────────────────────────────────────────────────────
// SPIEL-ABLAUF
// ──────────────────────────────────────────────────────────────────────────
public void startCountdown() {
if (state != GameState.WAITING) return;
state = GameState.STARTING;
final int[] cd = { plugin.getConfigManager().getCountdown() };
countdownTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (cd[0] <= 0) { countdownTask.cancel(); startGame(); return; }
if (cd[0] <= 5 || cd[0] == 10) {
broadcast("§eSpiel startet in §b" + cd[0] + " §eSekunden!");
playAll(Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f);
}
float prog = (float) cd[0] / plugin.getConfigManager().getCountdown();
for (Player p : getOnline()) p.setExp(Math.max(0f, Math.min(1f, prog)));
cd[0]--;
}, 0L, 20L);
}
public void startGame() {
state = GameState.RUNNING;
placeBaseBlocks();
for (Player p : getOnline()) {
p.sendTitle("§b§l⚡ LASERTEC", "§7Verteidige eure Basis!", 10, 50, 10);
p.setExp(1f);
}
playAll(plugin.getConfigManager().getSound("game-start", Sound.ENTITY_ENDER_DRAGON_GROWL), 0.5f, 1.2f);
broadcast(plugin.getConfigManager().getText("game-start", "§a§l⚡ DAS SPIEL BEGINNT!"));
// Heal-Check + AntiCamp: jede Sekunde
healTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
tickHeal();
antiCamp.tick(this);
}, 0L, 20L);
// Haupt-Timer
final int[] tl = { plugin.getConfigManager().getGameDuration() };
gameTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (tl[0] <= 0) { gameTask.cancel(); endGame(); return; }
if (tl[0] == 60) broadcast(plugin.getConfigManager().getText("time-60", "§e⏰ Noch §b60 §eSekunden!"));
if (tl[0] == 30) broadcast(plugin.getConfigManager().getText("time-30", "§c⏰ Noch §b30 §eSekunden!"));
if (tl[0] == 10) broadcast(plugin.getConfigManager().getText("time-10", "§4⏰ Noch §b10 §4Sekunden!"));
// Basis regenerieren?
if (plugin.getConfigManager().isBaseRegenerate()) {
int interval = plugin.getConfigManager().getBaseRegenInterval();
if (tl[0] % interval == 0) {
for (Team t : Team.values()) {
int hp = arena.getBaseHealth(t);
int max = plugin.getConfigManager().getBaseHealth();
if (hp < max && hp > 0) {
arena.damageBase(t); // Missbrauche: erhöhe via setBaseHealth direkt
// Eigentlich: arena.regenBase(t); — wir ergänzen das
}
}
}
}
timeLeft = tl[0];
for (Player p : getOnline()) {
plugin.getScoreboardManager().update(p, this);
sendActionBar(p);
}
tl[0]--;
}, 0L, 20L);
}
public void endGame() {
if (state == GameState.ENDING) return;
state = GameState.ENDING;
cancelTasks();
removeBaseBlocks();
modProtect.removeAll(this);
antiCamp.reset();
Team winner = getLeader();
for (Player p : getOnline()) {
LaserPlayer lp = players.get(p.getUniqueId());
boolean won = winner != null && lp != null && lp.getTeam() == winner;
p.sendTitle(won ? "§a§lSIEG!" : "§c§lNIEDERLAGE!",
winner != null ? "Team " + winner.colored() + " §7gewinnt!" : "§7Unentschieden!",
10, 80, 20);
p.playSound(p.getLocation(),
won ? Sound.UI_TOAST_CHALLENGE_COMPLETE : Sound.ENTITY_VILLAGER_NO, 1f, 1f);
showEndStats(p, lp);
saveStats(lp, p);
}
plugin.getPlayerDataManager().saveAll();
String endMsg = winner != null
? plugin.getConfigManager().getText("game-end", "§6§l🏆 Team {team} §6§lhat gewonnen!",
"team", winner.colored())
: plugin.getConfigManager().getText("game-draw", "§7Unentschieden!");
broadcast(endMsg);
int delay = plugin.getConfigManager().getEndDisplayTime() * 20;
Bukkit.getScheduler().runTaskLater(plugin, this::reset, delay);
}
private void reset() {
new ArrayList<>(getOnline()).forEach(this::removePlayer);
state = GameState.WAITING;
timeLeft = plugin.getConfigManager().getGameDuration();
countdown = plugin.getConfigManager().getCountdown();
players.clear();
shotCooldown.clear();
antiCamp.reset();
for (Team t : Team.values()) teamScore.put(t, 0);
arena.initBaseHealth(plugin.getConfigManager().getBaseHealth());
}
// ──────────────────────────────────────────────────────────────────────────
// SCHUSS-MECHANIK
// ──────────────────────────────────────────────────────────────────────────
public boolean handleShot(Player shooter, WeaponType weapon) {
LaserPlayer lp = players.get(shooter.getUniqueId());
if (lp == null || state != GameState.RUNNING) return false;
if (lp.isHit()) {
sendActionBarMsg(shooter, "§c☠ GETROFFEN Gehe zur §e"
+ lp.getTeam().colored() + " §cBasis!");
playSound(shooter, Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO, 0.5f, 0.5f);
return false;
}
long now = System.currentTimeMillis();
long last = shotCooldown.getOrDefault(shooter.getUniqueId(), 0L);
int cooldown = plugin.getConfigManager().getWeaponCooldown(weapon.getConfigKey());
if (now - last < cooldown) {
double rem = (cooldown - (now - last)) / 1000.0;
sendActionBarMsg(shooter, "§c⏳ Nachladen... §7(" + String.format("%.1f", rem) + "s)");
return false;
}
shotCooldown.put(shooter.getUniqueId(), now);
int pellets = plugin.getConfigManager().getWeaponPellets(weapon.getConfigKey());
if (pellets > 1) {
for (int i = 0; i < pellets; i++) shootRay(shooter, lp, weapon, spreadDir(shooter));
} else {
shootRay(shooter, lp, weapon, shooter.getEyeLocation().getDirection());
}
Sound shootSnd = plugin.getConfigManager().getSound("shoot", Sound.ENTITY_FIREWORK_ROCKET_BLAST);
float shootPitch = plugin.getConfigManager().getSoundPitch("shoot-pitch", 1.8f);
float shootVol = plugin.getConfigManager().getSoundVolume("shoot-volume", 0.4f);
if (shootSnd != null) shooter.getWorld().playSound(shooter.getLocation(), shootSnd, shootVol, shootPitch);
return true;
}
private void shootRay(Player shooter, LaserPlayer shooterLp, WeaponType weapon, Vector dir) {
Location eye = shooter.getEyeLocation();
int range = plugin.getConfigManager().getWeaponRange(weapon.getConfigKey());
if (plugin.getConfigManager().isLaserTrailEnabled())
drawLaser(eye, dir, weapon, range);
RayTraceResult result = shooter.getWorld().rayTrace(
eye, dir, range, FluidCollisionMode.NEVER, true, 0.5,
e -> e instanceof Player && !e.equals(shooter));
if (result == null) return;
// ── Spieler getroffen ─────────────────────────────────────────────────
if (result.getHitEntity() instanceof Player victim) {
LaserPlayer vLp = players.get(victim.getUniqueId());
if (vLp == null || vLp.isHit()) return;
if (vLp.getTeam() == shooterLp.getTeam()) return;
vLp.applyHit();
// Partikel
if (plugin.getConfigManager().isHitEffectEnabled()) {
victim.getWorld().spawnParticle(Particle.CRIT,
victim.getLocation().add(0,1,0),
plugin.getConfigManager().getHitParticleCount(),
0.4, 0.4, 0.4, 0.1);
}
// Sounds
playSound(victim, plugin.getConfigManager().getSound("hit-victim", Sound.ENTITY_PLAYER_HURT), 1f, 0.8f);
playSound(shooter, plugin.getConfigManager().getSound("hit-shooter", Sound.BLOCK_NOTE_BLOCK_BELL), 1f, 2f);
// Punkte
int pts = plugin.getConfigManager().getKillPoints();
int streak = shooterLp.getKillStreak() + 1;
int bonus = 0;
if (streak == 3) bonus = plugin.getConfigManager().getStreak3Bonus();
if (streak == 5) bonus = plugin.getConfigManager().getStreak5Bonus();
if (streak == 10) bonus = plugin.getConfigManager().getStreak10Bonus();
shooterLp.registerKill();
shooterLp.addScore(pts + bonus);
teamScore.merge(shooterLp.getTeam(), pts + bonus, Integer::sum);
String streakInfo = streak >= 3 ? " §6[" + streak + "er-Serie!+"+bonus+"]" : "";
shooter.sendMessage(plugin.getConfigManager().getText("hit-shooter",
"§aDu hast §e{victim} §agetroffen! §7(+{pts} Pkt){streak}",
"victim", victim.getName(), "pts", pts + bonus, "streak", streakInfo));
victim.sendMessage(plugin.getConfigManager().getText("hit-victim",
"§cDu wurdest von §e{shooter} §cgetroffen!",
"shooter", shooter.getName()));
// Streak-Broadcast
if (streak == 3) broadcastStreak(shooter, "streak-3", "§6TRIPLE KILL!", 3);
if (streak == 5) broadcastStreak(shooter, "streak-5", "§bPENTA KILL!", 5);
if (streak == 10) broadcastStreak(shooter, "streak-10", "§5§lGODLIKE!", 10);
sendActionBar(victim);
sendActionBar(shooter);
plugin.getScoreboardManager().update(shooter, this);
plugin.getScoreboardManager().update(victim, this);
return;
}
// ── Block getroffen (Basisangriff) ────────────────────────────────────
if (result.getHitBlock() != null) {
Team baseTeam = arena.getBaseTeam(result.getHitBlock().getLocation());
if (baseTeam != null && baseTeam != shooterLp.getTeam()) {
handleBaseAttack(shooter, shooterLp, baseTeam,
result.getHitBlock().getLocation());
}
}
}
// ──────────────────────────────────────────────────────────────────────────
// HEAL-TICK
// ──────────────────────────────────────────────────────────────────────────
private void tickHeal() {
double baseRadiusSq = Math.pow(plugin.getConfigManager().getBaseRadius(), 2);
int healTimeSec = plugin.getConfigManager().getBaseHealTime();
for (Map.Entry<UUID, LaserPlayer> e : players.entrySet()) {
Player p = Bukkit.getPlayer(e.getKey());
LaserPlayer lp = e.getValue();
if (p == null || !lp.isHit()) continue;
Location base = arena.getBase(lp.getTeam());
if (base == null) continue;
boolean near = p.getLocation().distanceSquared(base) <= baseRadiusSq;
if (near) {
if (!lp.isHealing()) {
lp.startHealing();
p.sendMessage(plugin.getConfigManager().getText("heal-start",
"§aHeilung gestartet...", "secs", healTimeSec));
playSound(p, plugin.getConfigManager().getSound("heal-start",
Sound.BLOCK_ENCHANTMENT_TABLE_USE), 1f, 1f);
} else if (lp.healElapsedMs() >= healTimeSec * 1000L) {
lp.completeHeal();
p.sendMessage(plugin.getConfigManager().getText("heal-complete",
"§a✔ Du bist wieder einsatzbereit!"));
Sound hs = plugin.getConfigManager().getSound("heal-complete",
Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
float hp = plugin.getConfigManager().getSoundPitch("heal-complete-pitch", 1.5f);
playSound(p, hs, 1f, hp);
p.sendTitle("§a§lBEREIT!", "§7Du kannst wieder schießen!", 5, 30, 5);
if (plugin.getConfigManager().isInvincibleAfterHeal()) {
p.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE,
plugin.getConfigManager().getInvincibleDuration(), 4, false, false));
}
if (plugin.getConfigManager().isHealEffectEnabled()) {
p.getWorld().spawnParticle(Particle.VILLAGER_HAPPY,
p.getLocation().add(0,1,0), 20, 0.5, 0.5, 0.5, 0.05);
}
} else {
double prog = lp.healElapsedMs() / (healTimeSec * 1000.0);
sendActionBarMsg(p, "§a🛡 Heilung: " + buildBar(prog, 15)
+ " §7" + String.format("%.1f", lp.healElapsedMs()/1000.0)
+ "§8/" + healTimeSec + "s");
}
} else {
if (lp.isHealing()) {
lp.stopHealing();
p.sendMessage(plugin.getConfigManager().getText("heal-interrupted",
"§c⚠ Heilung abgebrochen!"));
}
Location bl = base;
double dist = p.getLocation().distance(bl);
sendActionBarMsg(p, "§c☠ GETROFFEN Zur §e" + lp.getTeam().colored()
+ " §cBasis! §8(" + String.format("%.0f",dist) + "m)");
}
}
}
// ──────────────────────────────────────────────────────────────────────────
// BASIS-ANGRIFF
// ──────────────────────────────────────────────────────────────────────────
private void handleBaseAttack(Player shooter, LaserPlayer lp, Team baseTeam, Location blockLoc) {
if (arena.isBaseDestroyed(baseTeam)) return;
boolean damaged = arena.damageBase(baseTeam);
if (!damaged) return;
int pts = plugin.getConfigManager().getBaseAttackPoints();
int hp = arena.getBaseHealth(baseTeam);
int maxHp = plugin.getConfigManager().getBaseHealth();
lp.addScore(pts);
lp.addBaseAttack();
teamScore.merge(lp.getTeam(), pts, Integer::sum);
updateBaseBlock(baseTeam, hp, maxHp);
if (plugin.getConfigManager().isParticlesEnabled()) {
blockLoc.getWorld().spawnParticle(Particle.EXPLOSION_LARGE,
blockLoc.clone().add(0.5,0.5,0.5), 3, 0.2,0.2,0.2,0.05);
}
Sound bhs = plugin.getConfigManager().getSound("base-hit", Sound.ENTITY_GENERIC_EXPLODE);
if (bhs != null) blockLoc.getWorld().playSound(blockLoc, bhs, 0.5f, 1.5f);
shooter.sendMessage(plugin.getConfigManager().getPrefix() + "§6⚔ Basis von Team "
+ baseTeam.colored() + " §6angegriffen! §7(+" + pts + " Pkt) §8[HP: " + hp + "/" + maxHp + "]");
// Warn-HP aus Config
int warnHp = plugin.getConfigManager().getBaseWarnHp();
for (Map.Entry<UUID, LaserPlayer> e : players.entrySet()) {
if (e.getValue().getTeam() != baseTeam) continue;
Player p = Bukkit.getPlayer(e.getKey());
if (p == null) continue;
p.sendMessage(plugin.getConfigManager().getText("base-attacked",
"§c⚠ Eure Basis wird angegriffen! §8[HP: {hp}/{max}]",
"hp", hp, "max", maxHp));
if (hp <= warnHp)
p.sendTitle("§c§l⚠ BASIS KRITISCH!", "§7HP: " + hp + "/" + maxHp, 5, 40, 10);
playSound(p, plugin.getConfigManager().getSound("warning", Sound.BLOCK_BELL_USE), 1f, 0.5f);
}
broadcast(plugin.getConfigManager().getPrefix() + "§6⚔ §e" + shooter.getName()
+ " §6hat die Basis von Team " + baseTeam.colored()
+ " §6getroffen! §8(" + hp + "/" + maxHp + ")");
if (hp <= 0) {
broadcast(plugin.getConfigManager().getText("base-destroyed",
"§c§l💥 Die Basis von Team {team} §c§lwurde ZERSTÖRT!",
"team", baseTeam.colored()));
int bonus = plugin.getConfigManager().getBaseDestroyBonus();
lp.addScore(bonus);
teamScore.merge(lp.getTeam(), bonus, Integer::sum);
shooter.sendMessage(plugin.getConfigManager().getPrefix()
+ "§6§l+" + bonus + " Bonuspunkte für Basis-Zerstörung!");
playAll(Sound.ENTITY_ENDER_DRAGON_GROWL, 0.5f, 0.7f);
}
plugin.getScoreboardManager().update(shooter, this);
}
// ──────────────────────────────────────────────────────────────────────────
// SPIELER SETUP
// ──────────────────────────────────────────────────────────────────────────
private void setupPlayer(Player player, LaserPlayer lp) {
player.setGameMode(GameMode.ADVENTURE);
player.getInventory().clear();
player.setHealth(20); player.setFoodLevel(20); player.setSaturation(20f);
for (PotionEffect e : player.getActivePotionEffects())
player.removePotionEffect(e.getType());
int slot = 0;
for (WeaponType w : WeaponType.values()) {
if (plugin.getConfigManager().isWeaponEnabled(w.getConfigKey())) {
player.getInventory().setItem(slot++, WeaponUtil.create(plugin, w));
}
}
player.getInventory().setHeldItemSlot(0);
giveArmor(player, lp.getTeam());
}
private void giveArmor(Player player, Team team) {
// Helm: farbiger Woll-Block als Kopfbedeckung — auf einen Blick erkennbar
player.getInventory().setHelmet (makeHelm(team));
player.getInventory().setChestplate(coloredLeather(Material.LEATHER_CHESTPLATE, team));
player.getInventory().setLeggings (coloredLeather(Material.LEATHER_LEGGINGS, team));
player.getInventory().setBoots (coloredLeather(Material.LEATHER_BOOTS, team));
}
/**
* Helm: Farbige Wolle als Kopfblock.
* Wolle ist auf dem Kopf viel auffälliger als ein Leder-Helm,
* weil die satte Blockfarbe aus jeder Entfernung sichtbar ist.
*/
private ItemStack makeHelm(Team team) {
ItemStack wool = new ItemStack(team.getWoolMat());
ItemMeta meta = wool.getItemMeta();
meta.setDisplayName(team.getChatColor() + "Team " + team.getDisplayName() + " Helm");
meta.setLore(java.util.List.of(
"§8Lasertec " + team.colored() + " §8Team",
"§8Rüstung kann nicht abgelegt werden"
));
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE,
ItemFlag.HIDE_ENCHANTS);
wool.setItemMeta(meta);
return wool;
}
/**
* Lederrüstung mit kräftiger Teamfarbe (RGB) und Lore.
* Brustplatte, Hose und Stiefel sind farbig — zusammen mit dem
* Woll-Helm ergibt sich ein eindeutiges Team-Outfit.
*/
private ItemStack coloredLeather(Material mat, Team team) {
String partName = switch (mat) {
case LEATHER_CHESTPLATE -> "Brustplatte";
case LEATHER_LEGGINGS -> "Hose";
case LEATHER_BOOTS -> "Stiefel";
default -> "Rüstung";
};
ItemStack item = new ItemStack(mat);
LeatherArmorMeta meta = (LeatherArmorMeta) item.getItemMeta();
meta.setColor(team.getArmorColor());
meta.setDisplayName(team.getChatColor() + "Team " + team.getDisplayName()
+ " " + partName);
meta.setLore(java.util.List.of(
"§8Lasertec " + team.colored() + " §8Team",
"§8Rüstung kann nicht abgelegt werden"
));
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE,
ItemFlag.HIDE_ENCHANTS);
item.setItemMeta(meta);
return item;
}
private void restorePlayer(Player player, LaserPlayer lp) {
player.getInventory().clear();
if (lp.getSavedInventory() != null)
player.getInventory().setContents(lp.getSavedInventory());
player.setGameMode(GameMode.SURVIVAL);
player.setHealth(20); player.setFoodLevel(20);
for (PotionEffect e : player.getActivePotionEffects())
player.removePotionEffect(e.getType());
Location lobby = plugin.getConfigManager().getLobbyLocation();
if (lobby != null) player.teleport(lobby);
}
private void sendToSpawn(Player player, Team team) {
Location spawn = arena.getRandomSpawn(team);
if (spawn != null) player.teleport(spawn);
}
// ──────────────────────────────────────────────────────────────────────────
// BASIS-BLÖCKE
// ──────────────────────────────────────────────────────────────────────────
private void placeBaseBlocks() {
for (Team t : Team.values()) {
Location loc = arena.getBase(t);
if (loc != null) loc.getBlock().setType(t.getGlassMat());
}
}
private void updateBaseBlock(Team team, int hp, int maxHp) {
Location loc = arena.getBase(team);
if (loc == null) return;
if (hp <= 0) loc.getBlock().setType(Material.OBSIDIAN);
else if (hp < maxHp / 2.0) loc.getBlock().setType(team.getWoolMat());
if (plugin.getConfigManager().isParticlesEnabled())
loc.getWorld().spawnParticle(Particle.SMOKE_LARGE,
loc.clone().add(0.5,1.2,0.5), 5, 0.3,0.3,0.3,0.02);
}
private void removeBaseBlocks() {
for (Team t : Team.values()) {
Location loc = arena.getBase(t);
if (loc == null) continue;
Material m = loc.getBlock().getType();
if (m == t.getGlassMat() || m == t.getWoolMat() || m == Material.OBSIDIAN)
loc.getBlock().setType(Material.AIR);
}
}
// ──────────────────────────────────────────────────────────────────────────
// HILFSMETHODEN
// ──────────────────────────────────────────────────────────────────────────
private void drawLaser(Location start, Vector dir, WeaponType weapon, int range) {
var particle = plugin.getConfigManager().getWeaponParticle(weapon.getConfigKey());
Location cur = start.clone();
Vector step = dir.clone().normalize().multiply(0.6);
for (int i = 0; i < range * 1.7; i++) {
cur.add(step);
if (!cur.getBlock().isPassable()) break;
cur.getWorld().spawnParticle(particle, cur.clone(), 1, 0,0,0,0);
}
}
private Vector spreadDir(Player player) {
Vector base = player.getEyeLocation().getDirection();
double s = 0.12;
return base.clone().add(new Vector(
(Math.random()-0.5)*s, (Math.random()-0.5)*s*0.7, (Math.random()-0.5)*s
)).normalize();
}
private Team pickBalancedTeam() {
Map<Team,Integer> counts = new LinkedHashMap<>();
for (Team t : Team.values()) counts.put(t, 0);
for (LaserPlayer lp : players.values()) counts.merge(lp.getTeam(), 1, Integer::sum);
return counts.entrySet().stream().min(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(Team.RED);
}
private Team getLeader() {
return teamScore.entrySet().stream().max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey).orElse(null);
}
private void cancelTasks() {
if (countdownTask != null) countdownTask.cancel();
if (gameTask != null) gameTask.cancel();
if (healTask != null) healTask.cancel();
}
private void sendActionBar(Player player) {
LaserPlayer lp = players.get(player.getUniqueId());
if (lp == null) return;
if (!lp.isHit()) {
int idle = antiCamp.getIdleSeconds(player.getUniqueId());
int max = plugin.getConfigManager().getCampMaxIdleSecs();
String campBar = idle > 0
? " §c| Camp: " + buildBar(1.0 - (double)idle/max, 6)
: "";
sendActionBarMsg(player, "§7Kills: §a" + lp.getKills()
+ " §7Punkte: §6" + lp.getScore()
+ " §7Team: " + lp.getTeam().colored()
+ " §7Zeit: §b" + formatTime(timeLeft)
+ campBar);
}
// Wenn getroffen: wird in tickHeal() gesetzt
}
private void sendActionBarMsg(Player player, String msg) {
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(msg));
}
private void broadcastStreak(Player player, String textKey, String fallbackTitle, int streak) {
broadcast(plugin.getConfigManager().getText(textKey,
fallbackTitle + " §e{player} §7 " + streak + "er Serie!",
"player", player.getName()));
for (Player p : getOnline())
p.sendTitle(fallbackTitle, "§e" + player.getName(), 5, 40, 10);
}
private void showEndStats(Player p, LaserPlayer lp) {
if (lp == null) return;
p.sendMessage("§8§l══════════════════════════════");
p.sendMessage("§b§l ⚡ LASERTEC ERGEBNIS");
p.sendMessage("§8§l══════════════════════════════");
p.sendMessage("§7Team: " + lp.getTeam().colored());
p.sendMessage("§7Kills: §a" + lp.getKills());
p.sendMessage("§7Tode: §c" + lp.getDeaths());
p.sendMessage("§7Basis-Angriffe: §6" + lp.getBaseAttacks());
p.sendMessage("§7Punkte: §6" + lp.getScore());
p.sendMessage("§7Beste Serie: §b" + lp.getBestStreak());
p.sendMessage("§8──────────────────────────────");
p.sendMessage("§7Team-Punkte:");
teamScore.entrySet().stream()
.sorted((a,b) -> Integer.compare(b.getValue(), a.getValue()))
.forEach(e -> p.sendMessage(" " + e.getKey().colored()
+ " §7→ §6" + e.getValue() + " Punkte"));
p.sendMessage("§8§l══════════════════════════════");
}
private String buildBar(double progress, int length) {
int filled = Math.max(0, Math.min(length, (int)Math.round(progress * length)));
return "§a" + "".repeat(filled) + "§8" + "".repeat(length - filled);
}
private String formatTime(int secs) {
return String.format("%d:%02d", secs/60, secs%60);
}
private void broadcast(String msg) {
for (Player p : getOnline()) p.sendMessage(msg);
}
private void playAll(Sound sound, float vol, float pitch) {
if (sound == null) return;
for (Player p : getOnline()) p.playSound(p.getLocation(), sound, vol, pitch);
}
private void playSound(Player p, Sound s, float vol, float pitch) {
if (s != null) p.playSound(p.getLocation(), s, vol, pitch);
}
// ──────────────────────────────────────────────────────────────────────────
// GETTERS
// ──────────────────────────────────────────────────────────────────────────
public Arena getArena() { return arena; }
public GameState getState() { return state; }
public Map<UUID, LaserPlayer> getPlayers() { return players; }
public Map<Team, Integer> getTeamScore() { return teamScore; }
public int getTimeLeft() { return timeLeft; }
public LaserPlayer getLP(UUID uid) { return players.get(uid); }
public boolean isRunning() { return state == GameState.RUNNING; }
public boolean isJoinable() { return state == GameState.WAITING || state == GameState.STARTING; }
public int getPlayerCount() { return players.size(); }
public int getMaxPlayers() { return plugin.getConfigManager().getMaxPlayersPerTeam() * 4; }
public AntiCampManager getAntiCamp() { return antiCamp; }
public List<Player> getOnline() {
List<Player> list = new ArrayList<>();
for (UUID uid : players.keySet()) {
Player p = Bukkit.getPlayer(uid);
if (p != null) list.add(p);
}
return list;
}
public void forceEnd() { endGame(); }
public void forceStart() {
if (state == GameState.WAITING) startCountdown();
else if (state == GameState.STARTING) startGame();
}
}

View File

@@ -0,0 +1,93 @@
package de.lasertec.game;
import de.lasertec.LasertecPlugin;
import de.lasertec.arena.Arena;
import de.lasertec.player.LaserPlayer;
import org.bukkit.entity.Player;
import java.util.*;
public class GameManager {
private final LasertecPlugin plugin;
/** arenaName (lowercase) → Game */
private final Map<String, Game> games = new LinkedHashMap<>();
public GameManager(LasertecPlugin plugin) {
this.plugin = plugin;
// Spiele für alle vorhandenen Arenen erstellen
for (Arena arena : plugin.getArenaManager().getAll()) {
games.put(arena.getName().toLowerCase(), new Game(plugin, arena));
}
}
// ─── Spiel-Verwaltung ────────────────────────────────────────────────────
/** Wird aufgerufen wenn eine neue Arena erstellt wird. */
public Game createGame(Arena arena) {
Game g = new Game(plugin, arena);
games.put(arena.getName().toLowerCase(), g);
return g;
}
public Game getGame(String arenaName) {
return games.get(arenaName.toLowerCase());
}
/** Liefert das Spiel in dem der Spieler gerade ist, oder null. */
public Game getGameOf(Player player) {
return games.values().stream()
.filter(g -> g.getPlayers().containsKey(player.getUniqueId()))
.findFirst().orElse(null);
}
public LaserPlayer getLaserPlayer(Player player) {
Game g = getGameOf(player);
return g == null ? null : g.getLP(player.getUniqueId());
}
// ─── Join / Leave ────────────────────────────────────────────────────────
public boolean joinGame(Player player, String arenaName) {
String pre = plugin.getConfig().getString("messages.prefix","§8[§b§lLASERTEC§8] §r");
if (getGameOf(player) != null) {
player.sendMessage(pre + "§cDu bist bereits in einem Spiel! /lt leave");
return false;
}
Game g = games.get(arenaName.toLowerCase());
if (g == null) { player.sendMessage(pre + "§cArena '§e" + arenaName + "§c' nicht gefunden!"); return false; }
if (!g.getArena().isReady()) { player.sendMessage(pre + "§cDiese Arena ist noch nicht fertig eingerichtet!"); return false; }
if (!g.isJoinable()) { player.sendMessage(pre + "§cDas Spiel in dieser Arena läuft bereits!"); return false; }
return g.addPlayer(player);
}
/** Tritt dem am besten gefüllten, joinbaren Spiel bei. */
public boolean joinBest(Player player) {
String pre = plugin.getConfig().getString("messages.prefix","§8[§b§lLASERTEC§8] §r");
if (getGameOf(player) != null) {
player.sendMessage(pre + "§cDu bist bereits in einem Spiel!");
return false;
}
return games.values().stream()
.filter(g -> g.isJoinable() && g.getArena().isReady())
.max(Comparator.comparingInt(Game::getPlayerCount))
.map(g -> g.addPlayer(player))
.orElseGet(() -> { player.sendMessage(pre + "§cKein verfügbares Spiel! Benutze /lt list"); return false; });
}
public void leaveGame(Player player) {
Game g = getGameOf(player);
if (g != null) g.removePlayer(player);
}
// ─── Admin-Operationen ───────────────────────────────────────────────────
public void stopAllGames() {
for (Game g : games.values()) {
List<Player> online = new ArrayList<>(g.getOnline());
online.forEach(g::removePlayer);
}
}
public Collection<Game> getAllGames() { return Collections.unmodifiableCollection(games.values()); }
}

View File

@@ -0,0 +1,8 @@
package de.lasertec.game;
public enum GameState {
WAITING, // Wartet auf Spieler
STARTING, // Countdown läuft
RUNNING, // Spiel aktiv
ENDING // Ergebnis wird angezeigt
}

View File

@@ -0,0 +1,38 @@
package de.lasertec.game;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
public enum Team {
// Name ChatColor Leder-Farbe (kräftig) Wolle Glas
RED ("Rot", ChatColor.RED, Color.fromRGB(220, 20, 20), Material.RED_WOOL, Material.RED_STAINED_GLASS),
BLUE ("Blau", ChatColor.AQUA, Color.fromRGB( 30, 80, 220), Material.BLUE_WOOL, Material.BLUE_STAINED_GLASS),
GREEN ("Grün", ChatColor.GREEN, Color.fromRGB( 20, 160, 20), Material.GREEN_WOOL, Material.GREEN_STAINED_GLASS),
YELLOW("Gelb", ChatColor.YELLOW, Color.fromRGB(220, 180, 0), Material.YELLOW_WOOL, Material.YELLOW_STAINED_GLASS);
private final String displayName;
private final ChatColor chatColor;
private final Color armorColor;
private final Material woolMat;
private final Material glassMat;
Team(String displayName, ChatColor chatColor, Color armorColor,
Material woolMat, Material glassMat) {
this.displayName = displayName;
this.chatColor = chatColor;
this.armorColor = armorColor;
this.woolMat = woolMat;
this.glassMat = glassMat;
}
public String getDisplayName() { return displayName; }
public ChatColor getChatColor() { return chatColor; }
public Color getArmorColor() { return armorColor; }
public Material getWoolMat() { return woolMat; }
public Material getGlassMat() { return glassMat; }
/** z.B. "§cRot" */
public String colored() { return chatColor + displayName; }
}

View File

@@ -0,0 +1,31 @@
package de.lasertec.listener;
import de.lasertec.LasertecPlugin;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.entity.Player;
public class BlockListener implements Listener {
private final LasertecPlugin plugin;
public BlockListener(LasertecPlugin plugin) { this.plugin = plugin; }
@EventHandler
public void onBreak(BlockBreakEvent e) {
Player p = e.getPlayer();
if (plugin.getGameManager().getGameOf(p) != null) {
e.setCancelled(true); // Kein Block-Abbau per Hand; Basisangriff nur per Schuss
}
}
@EventHandler
public void onPlace(BlockPlaceEvent e) {
Player p = e.getPlayer();
if (plugin.getGameManager().getGameOf(p) != null) {
e.setCancelled(true);
}
}
}

View File

@@ -0,0 +1,44 @@
package de.lasertec.listener;
import de.lasertec.LasertecPlugin;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.entity.Player;
public class PlayerListener implements Listener {
private final LasertecPlugin plugin;
public PlayerListener(LasertecPlugin plugin) { this.plugin = plugin; }
/** Schaden komplett sperren — Lasertec verwaltet Tode selbst. */
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false)
public void onDamage(EntityDamageEvent e) {
if (!(e.getEntity() instanceof Player p)) return;
if (plugin.getGameManager().getGameOf(p) != null) e.setCancelled(true);
}
/** Hunger sperren. */
@EventHandler
public void onFood(FoodLevelChangeEvent e) {
if (!(e.getEntity() instanceof Player p)) return;
if (plugin.getGameManager().getGameOf(p) != null) e.setCancelled(true);
}
/** Items droppen sperren. */
@EventHandler
public void onDrop(PlayerDropItemEvent e) {
if (plugin.getGameManager().getGameOf(e.getPlayer()) != null) e.setCancelled(true);
}
/** Spieler verlässt den Server. */
@EventHandler
public void onQuit(PlayerQuitEvent e) {
plugin.getGameManager().leaveGame(e.getPlayer());
}
}

View File

@@ -0,0 +1,246 @@
package de.lasertec.listener;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.block.sign.Side;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.SignChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.scheduler.BukkitTask;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Join-Schild System.
*
* EIN SCHILD ERSTELLEN:
* Schreibe auf Zeile 1 exakt: [Lasertec]
* Schreibe auf Zeile 2: Den Arenannamen
*
* Das Plugin erkennt das Schild automatisch und:
* - Aktualisiert Status/Spielerzahl live
* - Lässt Spieler durch Rechtsklick beitreten
*
* Format:
* Zeile 1: §b[LASERTEC]
* Zeile 2: §eArena-Name
* Zeile 3: §aWartend / §eStartet / §cLäuft / §8Voll
* Zeile 4: §72/16
*/
public class SignListener implements Listener {
private final LasertecPlugin plugin;
/** Location → Arenaname (gespeicherte Schilder) */
private final Map<Location, String> signs = new HashMap<>();
private File signsFile;
private BukkitTask updateTask;
public SignListener(LasertecPlugin plugin) {
this.plugin = plugin;
loadSigns();
startUpdateTask();
}
// ─── Schild erstellen ────────────────────────────────────────────────────
@EventHandler
public void onSignChange(SignChangeEvent e) {
// Zeile 1 (Index 0) prüfen
String line0 = e.getLine(0);
if (line0 == null) return;
String trigger = plugin.getConfigManager().getSignTriggerLine();
if (!ChatColor.stripColor(line0).equalsIgnoreCase(ChatColor.stripColor(trigger))) return;
if (!e.getPlayer().hasPermission("lasertec.admin")) {
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§cNur Admins können Lasertec-Schilder erstellen!");
return;
}
String arenaName = ChatColor.stripColor(e.getLine(1) != null ? e.getLine(1) : "");
if (arenaName.isEmpty()) {
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§cZeile 2 muss den Arenannamen enthalten!");
return;
}
if (plugin.getArenaManager().getArena(arenaName) == null) {
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§cArena '§e" + arenaName + "§c' nicht gefunden!");
return;
}
// Schild registrieren
signs.put(e.getBlock().getLocation(), arenaName);
saveSigns();
// Zeilen setzen
e.setLine(0, "§b§l[LASERTEC]");
e.setLine(1, "§e" + arenaName);
e.setLine(2, "§7Initialisierung...");
e.setLine(3, "§7...");
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§aJoin-Schild für Arena §e" + arenaName + " §aerstellt!");
// Sofort aktualisieren
Bukkit.getScheduler().runTaskLater(plugin, () -> updateSign(e.getBlock().getLocation(), arenaName), 2L);
}
// ─── Schild anklicken (Beitreten) ────────────────────────────────────────
@EventHandler
public void onInteract(PlayerInteractEvent e) {
if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (e.getClickedBlock() == null) return;
if (!(e.getClickedBlock().getState() instanceof Sign)) return;
Location loc = e.getClickedBlock().getLocation();
String arenaName = signs.get(loc);
if (arenaName == null) return;
e.setCancelled(true);
Player player = e.getPlayer();
// Ist Spieler bereits in einem Spiel?
if (plugin.getGameManager().getGameOf(player) != null) {
player.sendMessage(plugin.getConfigManager().getPrefix()
+ "§cDu bist bereits in einem Spiel! /lt leave");
return;
}
// Beitreten versuchen
boolean joined = plugin.getGameManager().joinGame(player, arenaName);
if (joined) {
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.2f);
}
}
// ─── Schild zerstören ────────────────────────────────────────────────────
@EventHandler
public void onBreak(BlockBreakEvent e) {
Location loc = e.getBlock().getLocation();
if (!signs.containsKey(loc)) return;
if (!e.getPlayer().hasPermission("lasertec.admin")) {
e.setCancelled(true);
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§cNur Admins können Lasertec-Schilder abbauen!");
return;
}
signs.remove(loc);
saveSigns();
e.getPlayer().sendMessage(plugin.getConfigManager().getPrefix()
+ "§aJoin-Schild entfernt.");
}
// ─── Update-Task ─────────────────────────────────────────────────────────
private void startUpdateTask() {
int interval = plugin.getConfigManager().getSignUpdateInterval();
updateTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
for (Map.Entry<Location, String> entry : new HashMap<>(signs).entrySet()) {
updateSign(entry.getKey(), entry.getValue());
}
}, 20L, interval);
}
public void stopUpdateTask() {
if (updateTask != null) updateTask.cancel();
}
private void updateSign(Location loc, String arenaName) {
Block block = loc.getBlock();
if (!(block.getState() instanceof Sign sign)) {
// Block ist kein Schild mehr → entfernen
signs.remove(loc);
saveSigns();
return;
}
Game game = plugin.getGameManager().getGame(arenaName);
var cfg = plugin.getConfigManager();
String line2, line3;
if (game == null || !game.getArena().isReady() || !game.getArena().isEnabled()) {
line2 = "§c§lNICHT BEREIT";
line3 = "§8Setup fehlt";
} else {
boolean full = game.getPlayerCount() >= game.getMaxPlayers();
String color = switch (game.getState()) {
case WAITING -> full ? cfg.getSignColorFull() : cfg.getSignColorWaiting();
case STARTING -> cfg.getSignColorStarting();
case RUNNING -> cfg.getSignColorRunning();
case ENDING -> "§7";
};
String statusText = switch (game.getState()) {
case WAITING -> full ? "Voll" : "Warten";
case STARTING -> "Startet...";
case RUNNING -> "Läuft";
case ENDING -> "Beendet";
};
line2 = color + statusText;
line3 = "§7" + game.getPlayerCount() + "§8/§7" + game.getMaxPlayers();
}
// Spigot 1.20: Sign hat Vorder- und Rückseite
try {
var signSide = sign.getSide(Side.FRONT);
signSide.setLine(0, "§b§l[LASERTEC]");
signSide.setLine(1, "§e" + arenaName);
signSide.setLine(2, line2);
signSide.setLine(3, line3);
} catch (Exception ex) {
// Fallback für ältere API
sign.setLine(0, "§b§l[LASERTEC]");
sign.setLine(1, "§e" + arenaName);
sign.setLine(2, line2);
sign.setLine(3, line3);
}
sign.update();
}
// ─── Persistenz ──────────────────────────────────────────────────────────
private void loadSigns() {
signsFile = new File(plugin.getDataFolder(), "signs.yml");
if (!signsFile.exists()) return;
FileConfiguration cfg = YamlConfiguration.loadConfiguration(signsFile);
if (!cfg.contains("signs")) return;
for (String key : cfg.getConfigurationSection("signs").getKeys(false)) {
Location loc = cfg.getLocation("signs." + key + ".location");
String arena = cfg.getString("signs." + key + ".arena");
if (loc != null && arena != null) signs.put(loc, arena);
}
plugin.getLogger().info("§a" + signs.size() + " Join-Schilder geladen.");
}
private void saveSigns() {
FileConfiguration cfg = new YamlConfiguration();
int i = 0;
for (Map.Entry<Location, String> e : signs.entrySet()) {
cfg.set("signs." + i + ".location", e.getKey());
cfg.set("signs." + i + ".arena", e.getValue());
i++;
}
try { cfg.save(signsFile); } catch (IOException ex) { ex.printStackTrace(); }
}
public int getSignCount() { return signs.size(); }
}

View File

@@ -0,0 +1,35 @@
package de.lasertec.listener;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import de.lasertec.weapon.WeaponType;
import de.lasertec.weapon.WeaponUtil;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
public class WeaponListener implements Listener {
private final LasertecPlugin plugin;
public WeaponListener(LasertecPlugin plugin) { this.plugin = plugin; }
@EventHandler
public void onInteract(PlayerInteractEvent e) {
if (e.getAction() != Action.RIGHT_CLICK_AIR
&& e.getAction() != Action.RIGHT_CLICK_BLOCK) return;
var player = e.getPlayer();
Game game = plugin.getGameManager().getGameOf(player);
if (game == null || !game.isRunning()) return;
ItemStack item = player.getInventory().getItemInMainHand();
WeaponType weapon = WeaponUtil.identify(plugin, item);
if (weapon == null) return;
e.setCancelled(true);
game.handleShot(player, weapon);
}
}

View File

@@ -0,0 +1,125 @@
package de.lasertec.player;
import de.lasertec.game.Team;
import org.bukkit.inventory.ItemStack;
import java.util.UUID;
/**
* Repräsentiert einen Spieler während eines laufenden Spiels.
*
* KERN-MECHANIK:
* - Wird ein Spieler getroffen → isHit() == true
* - Getroffene Spieler können NICHT schießen
* - Getroffene Spieler müssen zur eigenen Basis zurück
* - An der Basis: heal() aufrufen → isHit() == false, kann wieder schießen
*/
public class LaserPlayer {
private final UUID uuid;
private final String name;
// Team
private Team team;
// ─── Hit-Stun Mechanik ───────────────────────────────────────────────────
/** true = getroffen, muss zur Basis zurück, kann nicht schießen */
private boolean hit;
/** Zeitstempel des letzten Treffers */
private long hitTime;
/** Wann der Spieler anfing, sich an der Basis aufzuhalten */
private long healStart;
/** Heilt gerade an der Basis */
private boolean healing;
// ─── Spiel-Statistiken (diese Runde) ────────────────────────────────────
private int kills;
private int deaths;
private int baseAttacks; // Wie oft hat er eine gegnerische Basis angegriffen
private int score;
private int killStreak;
private int bestStreak;
// Gespeicherter Zustand vor dem Spiel
private ItemStack[] savedInventory;
public LaserPlayer(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
}
// ─── Hit-Stun API ────────────────────────────────────────────────────────
/** Spieler wird getroffen — kann ab sofort nicht mehr schießen. */
public void applyHit() {
this.hit = true;
this.hitTime = System.currentTimeMillis();
this.healing = false;
this.healStart = 0;
this.deaths++;
this.killStreak = 0;
}
/** Spieler betritt die eigene Basis → Heilung beginnt. */
public void startHealing() {
if (!hit || healing) return;
healing = true;
healStart = System.currentTimeMillis();
}
/** Spieler verlässt die Basis → Heilung abbrechen. */
public void stopHealing() {
healing = false;
healStart = 0;
}
/**
* Heilung abschließen (wird vom Game-Task nach heal-time Sekunden aufgerufen).
* Der Spieler kann danach wieder schießen.
*/
public void completeHeal() {
hit = false;
healing = false;
healStart = 0;
}
/** Ist der Spieler gerade getroffen (= gesperrt)? */
public boolean isHit() { return hit; }
/** Heilt der Spieler gerade an der Basis? */
public boolean isHealing() { return healing; }
/** Millisekunden seit Heilungsbeginn */
public long healElapsedMs() {
if (!healing || healStart == 0) return 0;
return System.currentTimeMillis() - healStart;
}
// ─── Kill-Counting ───────────────────────────────────────────────────────
public void registerKill() {
kills++;
killStreak++;
if (killStreak > bestStreak) bestStreak = killStreak;
}
public void addScore(int amount) { score += amount; }
public void addBaseAttack() { baseAttacks++; }
// ─── Getters / Setters ───────────────────────────────────────────────────
public UUID getUuid() { return uuid; }
public String getName() { return name; }
public Team getTeam() { return team; }
public void setTeam(Team team) { this.team = team; }
public int getKills() { return kills; }
public int getDeaths() { return deaths; }
public int getBaseAttacks() { return baseAttacks; }
public int getScore() { return score; }
public int getKillStreak() { return killStreak; }
public int getBestStreak() { return bestStreak; }
public ItemStack[] getSavedInventory() { return savedInventory; }
public void setSavedInventory(ItemStack[] i) { savedInventory = i; }
public String getKDString() { return kills + "/" + deaths; }
}

View File

@@ -0,0 +1,73 @@
package de.lasertec.player;
import de.lasertec.LasertecPlugin;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class PlayerDataManager {
private final LasertecPlugin plugin;
private final Map<UUID, PlayerStats> map = new HashMap<>();
private File file;
private FileConfiguration cfg;
public PlayerDataManager(LasertecPlugin plugin) {
this.plugin = plugin;
load();
}
public PlayerStats getStats(UUID uuid) {
return map.computeIfAbsent(uuid, PlayerStats::new);
}
public List<PlayerStats> getTopByScore(int limit) {
List<PlayerStats> list = new ArrayList<>(map.values());
list.sort((a, b) -> Integer.compare(b.getTotalScore(), a.getTotalScore()));
return list.subList(0, Math.min(limit, list.size()));
}
private void load() {
file = new File(plugin.getDataFolder(), "stats.yml");
if (!file.exists()) {
try { plugin.getDataFolder().mkdirs(); file.createNewFile(); }
catch (IOException ex) { ex.printStackTrace(); return; }
}
cfg = YamlConfiguration.loadConfiguration(file);
ConfigurationSection root = cfg.getConfigurationSection("players");
if (root == null) return;
for (String key : root.getKeys(false)) {
UUID uuid = UUID.fromString(key);
PlayerStats s = new PlayerStats(uuid);
String p = "players." + key + ".";
s.setName(cfg.getString(p + "name", "Unknown"));
s.addKills(cfg.getInt(p + "kills"));
s.addDeaths(cfg.getInt(p + "deaths"));
s.addScore(cfg.getInt(p + "score"));
s.addGames(cfg.getInt(p + "games"));
s.setBestStreak(cfg.getInt(p + "best-streak"));
s.addBaseAttacks(cfg.getInt(p + "base-attacks"));
map.put(uuid, s);
}
}
public void saveAll() {
cfg.set("players", null);
for (Map.Entry<UUID, PlayerStats> e : map.entrySet()) {
PlayerStats s = e.getValue();
String p = "players." + e.getKey() + ".";
cfg.set(p + "name", s.getName());
cfg.set(p + "kills", s.getTotalKills());
cfg.set(p + "deaths", s.getTotalDeaths());
cfg.set(p + "score", s.getTotalScore());
cfg.set(p + "games", s.getGamesPlayed());
cfg.set(p + "best-streak", s.getBestStreak());
cfg.set(p + "base-attacks",s.getTotalBaseAttacks());
}
try { cfg.save(file); } catch (IOException ex) { ex.printStackTrace(); }
}
}

View File

@@ -0,0 +1,48 @@
package de.lasertec.player;
import java.util.UUID;
public class PlayerStats {
private final UUID uuid;
private String name = "Unknown";
private int totalKills;
private int totalDeaths;
private int totalScore;
private int gamesPlayed;
private int bestStreak;
private int totalBaseAttacks;
public PlayerStats(UUID uuid) { this.uuid = uuid; }
public void apply(LaserPlayer lp) {
totalKills += lp.getKills();
totalDeaths += lp.getDeaths();
totalScore += lp.getScore();
totalBaseAttacks += lp.getBaseAttacks();
gamesPlayed++;
if (lp.getBestStreak() > bestStreak) bestStreak = lp.getBestStreak();
}
public double getKDR() {
if (totalDeaths == 0) return totalKills;
return Math.round(totalKills * 100.0 / totalDeaths) / 100.0;
}
// Getters & Setters
public UUID getUuid() { return uuid; }
public String getName() { return name; }
public void setName(String n) { name = n; }
public int getTotalKills() { return totalKills; }
public void addKills(int k) { totalKills += k; }
public int getTotalDeaths() { return totalDeaths; }
public void addDeaths(int d) { totalDeaths += d; }
public int getTotalScore() { return totalScore; }
public void addScore(int s) { totalScore += s; }
public int getGamesPlayed() { return gamesPlayed; }
public void addGames(int g) { gamesPlayed += g; }
public int getBestStreak() { return bestStreak; }
public void setBestStreak(int s) { bestStreak = s; }
public int getTotalBaseAttacks() { return totalBaseAttacks; }
public void addBaseAttacks(int b) { totalBaseAttacks += b; }
}

View File

@@ -0,0 +1,215 @@
package de.lasertec.protection;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Mod-Schutz System gegen Minimap-Mods (Xaero, VoxelMap etc.)
*
* WAS MINIMAP-MODS NUTZEN:
* - Spieler-Positionen aus Entity-Tracking Paketen
* - Chunk-Daten für Kartenansicht
* - Tab-Liste für Spielernamen mit Positionen
*
* WAS WIR TUN KÖNNEN (ohne ProtocolLib):
* 1. NAMETAGS verbergen (Scoreboard-Teams mit hidden nametags)
* 2. FOG-OF-WAR simulieren via Blindness-Effekt der zu naher Spieler
* → Macht Karten-Mods wertlos (sie sehen nur was der Client sieht)
* 3. TAB-LISTE leeren für Spieler im Match
* 4. SPIELER-KOLLISION deaktivieren
*
* WAS NICHT MÖGLICH IST (ohne ProtocolLib / NMS):
* - Chunk-Pakete manipulieren
* - Entity-Positionen in Paketen fälschen
* - F3-Koordinaten blockieren
*
* MIT ProtocolLib (optional, erweiterbar):
* - Echte Koordinaten-Verschleierung möglich
* - Spieler-Pakete filtern
*/
public class ModProtectionManager {
private final LasertecPlugin plugin;
private BukkitTask fogTask;
// Scoreboard-Team für versteckte Nametags
private org.bukkit.scoreboard.Scoreboard hiddenNametagBoard;
private final Map<UUID, String> originalDisplayNames = new HashMap<>();
public ModProtectionManager(LasertecPlugin plugin) {
this.plugin = plugin;
}
/** Wird aufgerufen wenn ein Spieler einem Spiel beitritt. */
public void applyProtection(Player player, Game game) {
if (!plugin.getConfigManager().isModProtectionEnabled()) return;
// 1. Nametags verstecken
if (plugin.getConfigManager().isHideNametags()) {
hideNametag(player, game);
}
// 2. Tab-Liste leeren (nur den eigenen Tab-Name anpassen)
if (plugin.getConfigManager().isHideFromTab()) {
applyTabHide(player, game);
}
// 3. Fog-of-War: Spieler außerhalb fog-radius unsichtbar machen
if (plugin.getConfigManager().isFogOfWarEnabled()) {
startFogOfWar(game);
}
}
/** Alle Schutz-Effekte entfernen wenn Spieler das Spiel verlässt. */
public void removeProtection(Player player) {
// Nametag zurücksetzen
restoreNametag(player);
// Tab-Liste zurücksetzen
player.setPlayerListName(null);
// Blindheit entfernen (falls aktiv)
player.removePotionEffect(PotionEffectType.BLINDNESS);
player.removePotionEffect(PotionEffectType.NIGHT_VISION);
}
/** Alle Schutz-Effekte für alle Spieler entfernen. */
public void removeAll(Game game) {
if (fogTask != null) { fogTask.cancel(); fogTask = null; }
for (Player p : game.getOnline()) removeProtection(p);
}
// ─── Nametag verstecken ──────────────────────────────────────────────────
private void hideNametag(Player player, Game game) {
// Eigenes Scoreboard für jeden Spieler im Spiel
// Wir nutzen das scoreboard das der ScoreboardManager bereits verwaltet —
// dort ein "hidden" Team erstellen
Scoreboard sb = player.getScoreboard();
if (sb == null) sb = Bukkit.getScoreboardManager().getNewScoreboard();
// Team per Spieler-Name (max 16 Zeichen im Team-Namen)
String teamName = "lt_" + player.getName().substring(0, Math.min(player.getName().length(), 13));
org.bukkit.scoreboard.Team team = sb.getTeam(teamName);
if (team == null) team = sb.registerNewTeam(teamName);
team.setOption(org.bukkit.scoreboard.Team.Option.NAME_TAG_VISIBILITY,
org.bukkit.scoreboard.Team.OptionStatus.FOR_OTHER_TEAMS); // Eigene Tags nicht verstecken
team.addEntry(player.getName());
// Alle anderen Spieler im Spiel aktualisieren
for (Player other : game.getOnline()) {
if (other.equals(player)) continue;
Scoreboard otherSb = other.getScoreboard();
if (otherSb == null) continue;
String tn = "lt_" + player.getName().substring(0, Math.min(player.getName().length(), 13));
org.bukkit.scoreboard.Team t = otherSb.getTeam(tn);
if (t == null) t = otherSb.registerNewTeam(tn);
t.setOption(org.bukkit.scoreboard.Team.Option.NAME_TAG_VISIBILITY,
org.bukkit.scoreboard.Team.OptionStatus.NEVER);
t.addEntry(player.getName());
}
}
private void restoreNametag(Player player) {
// Nametag-Teams aus Scoreboard entfernen
Scoreboard sb = player.getScoreboard();
if (sb == null) return;
String teamName = "lt_" + player.getName().substring(0, Math.min(player.getName().length(), 13));
org.bukkit.scoreboard.Team team = sb.getTeam(teamName);
if (team != null) {
team.removeEntry(player.getName());
}
}
// ─── Tab-Liste ────────────────────────────────────────────────────────────
private void applyTabHide(Player player, Game game) {
// Spielernamen in der Tab-Liste durch Team-Tag ersetzen
// (versteckt echte Position-Infos, die manche Mods aus der Tab-Liste lesen)
var lp = game.getLP(player.getUniqueId());
if (lp == null) return;
String teamTag = lp.getTeam().getChatColor() + "[" + lp.getTeam().getDisplayName().substring(0,1) + "] ";
player.setPlayerListName(teamTag + player.getName());
}
// ─── Fog-of-War ──────────────────────────────────────────────────────────
/**
* Fog-of-War: Spieler außerhalb des fog-radius werden für andere
* Spieler unsichtbar gemacht. Das verhindert dass Minimap-Mods
* Gegner-Positionen auf der Karte anzeigen können.
*
* Technisch: Wir nutzen die Spigot hide/show Player API.
* Spieler die außerhalb des Radius sind werden per hidePlayer() ausgeblendet.
*/
private void startFogOfWar(Game game) {
if (fogTask != null) return; // Läuft bereits
int fogRadius = plugin.getConfigManager().getFogRadius();
long fogRadiusSq = (long) fogRadius * fogRadius;
fogTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
if (!game.isRunning()) {
fogTask.cancel(); fogTask = null; return;
}
for (Player viewer : game.getOnline()) {
for (Player target : game.getOnline()) {
if (viewer.equals(target)) continue;
boolean sameTeam = false;
var vlp = game.getLP(viewer.getUniqueId());
var tlp = game.getLP(target.getUniqueId());
if (vlp != null && tlp != null) sameTeam = vlp.getTeam() == tlp.getTeam();
long distSq = (long) viewer.getLocation().distanceSquared(target.getLocation());
if (distSq <= fogRadiusSq || sameTeam) {
// In Reichweite oder gleiches Team → sichtbar
viewer.showPlayer(plugin, target);
} else {
// Außerhalb → verstecken (Minimap sieht diesen Spieler nicht)
viewer.hidePlayer(plugin, target);
}
}
}
}, 0L, 10L); // Alle 10 Ticks (0.5s) aktualisieren
}
// ─── Info-Methode ────────────────────────────────────────────────────────
/**
* Sendet eine Erklärung der Mod-Schutz-Maßnahmen an den Spieler.
* Nützlich für Admins.
*/
public void sendProtectionInfo(org.bukkit.command.CommandSender sender) {
sender.sendMessage("§8§l═══════════════════════════════════════");
sender.sendMessage("§b§l MOD-SCHUTZ — STATUS & ERKLÄRUNG");
sender.sendMessage("§8§l═══════════════════════════════════════");
sender.sendMessage((plugin.getConfigManager().isModProtectionEnabled() ? "§a✔" : "§c✘")
+ " §7Mod-Schutz aktiv");
sender.sendMessage((plugin.getConfigManager().isHideNametags() ? "§a✔" : "§c✘")
+ " §7Nametags versteckt §8(verhindert Spieler-Tracking)");
sender.sendMessage((plugin.getConfigManager().isHideFromTab() ? "§a✔" : "§c✘")
+ " §7Tab-Liste angepasst §8(versteckt Spielernamen)");
sender.sendMessage((plugin.getConfigManager().isFogOfWarEnabled() ? "§a✔" : "§c✘")
+ " §7Fog-of-War aktiv §8(Radius: §b"
+ plugin.getConfigManager().getFogRadius() + " Blöcke§8)");
sender.sendMessage("§8§l───────────────────────────────────────");
sender.sendMessage("§7§oWas NICHT blockiert werden kann:");
sender.sendMessage("§c§o - Chunk-basierte Kartendaten (bräuchte ProtocolLib)");
sender.sendMessage("§c§o - F3-Koordinaten (Clientseitig)");
sender.sendMessage("§c§o - Vollständige Minimap-Blöcke");
sender.sendMessage("§7§oFür vollständigen Schutz: ProtocolLib + PacketBlocker");
sender.sendMessage("§8§l═══════════════════════════════════════");
}
}

View File

@@ -0,0 +1,113 @@
package de.lasertec.scoreboard;
import de.lasertec.LasertecPlugin;
import de.lasertec.game.Game;
import de.lasertec.game.Team;
import de.lasertec.player.LaserPlayer;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ScoreboardManager {
private final LasertecPlugin plugin;
private final Map<UUID, Scoreboard> boards = new HashMap<>();
public ScoreboardManager(LasertecPlugin plugin) { this.plugin = plugin; }
public void update(Player player, Game game) {
Scoreboard board = boards.computeIfAbsent(player.getUniqueId(),
k -> Bukkit.getScoreboardManager().getNewScoreboard());
// Altes Objective entfernen
Objective old = board.getObjective("lt");
if (old != null) old.unregister();
Objective obj = board.registerNewObjective("lt", "dummy",
ChatColor.AQUA + "" + ChatColor.BOLD + "⚡ LASERTEC");
obj.setDisplaySlot(DisplaySlot.SIDEBAR);
LaserPlayer lp = game.getLP(player.getUniqueId());
int line = 15;
// Trennlinie oben
setLine(obj, board, "§b§l─────────────────", line--);
// Zeit
int secs = game.getTimeLeft();
String timeStr = String.format("%d:%02d", secs / 60, secs % 60);
setLine(obj, board, "§7⏰ Zeit: §e" + timeStr, line--);
setLine(obj, board, "§7§l─────────────────", line--);
// Team-Punkte (sortiert nach Punkte)
setLine(obj, board, "§7Team-Punkte:", line--);
Map<Team, Integer> scores = game.getTeamScore();
Team[] sorted = Team.values().clone();
// Bubble sort (nur 4 Teams)
for (int i = 0; i < sorted.length - 1; i++)
for (int j = 0; j < sorted.length - 1 - i; j++)
if (scores.getOrDefault(sorted[j],0) < scores.getOrDefault(sorted[j+1],0)) {
Team tmp = sorted[j]; sorted[j] = sorted[j+1]; sorted[j+1] = tmp;
}
for (Team t : sorted) {
int hp = game.getArena().getBaseHealth(t);
int maxHp = plugin.getConfig().getInt("game.base-health", 5);
String baseHpBar = buildHpBar(hp, maxHp, 5);
String pts = scores.getOrDefault(t, 0) + "§7pts";
setLine(obj, board,
t.getChatColor() + "" + t.getDisplayName() + " §8| §f" + pts
+ " §8| " + baseHpBar, line--);
}
setLine(obj, board, "§7§l─────────────────", line--);
// Spieler-Stats
if (lp != null) {
String hitStatus = lp.isHit()
? (lp.isHealing() ? "§e⚕ Heilend..." : "§c☠ Getroffen!")
: "§a✔ Aktiv";
setLine(obj, board, "§7Status: " + hitStatus, line--);
setLine(obj, board, "§7Kills: §a" + lp.getKills() + " §8| §7Tode: §c" + lp.getDeaths(), line--);
setLine(obj, board, "§7Punkte: §6" + lp.getScore(), line--);
if (lp.getKillStreak() >= 3)
setLine(obj, board, "§6🔥 Serie: §e×" + lp.getKillStreak(), line--);
}
setLine(obj, board, "§b§l─────────────────", line--);
setLine(obj, board, "§bwww.lasertec.de", line--);
player.setScoreboard(board);
}
public void remove(Player player) {
boards.remove(player.getUniqueId());
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
}
// ─── Hilfsmethoden ───────────────────────────────────────────────────────
private int uniqueSuffix = 0;
private void setLine(Objective obj, Scoreboard board, String text, int score) {
// Scoreboard-Entries müssen einzigartig sein
String entry = text;
while (board.getEntries().contains(entry))
entry = entry + ChatColor.values()[uniqueSuffix++ % ChatColor.values().length];
obj.getScore(entry).setScore(score);
}
private String buildHpBar(int hp, int max, int len) {
if (max <= 0) return "§8-----";
int filled = (int) Math.round((double) hp / max * len);
filled = Math.max(0, Math.min(len, filled));
String color = filled > len / 2 ? "§a" : filled > 1 ? "§e" : "§c";
return color + "".repeat(filled) + "§8" + "".repeat(len - filled);
}
}

View File

@@ -0,0 +1,30 @@
package de.lasertec.weapon;
import org.bukkit.Material;
import org.bukkit.Particle;
/**
* Waffen-Typen. Basis-Werte hier sind NUR Fallbacks.
* Die echten Werte kommen aus config.yml unter weapons.<configKey>.*
*/
public enum WeaponType {
LASER_GUN ("laser-gun", Material.BLAZE_ROD),
SNIPER ("sniper", Material.STICK),
SHOTGUN ("shotgun", Material.FISHING_ROD),
RAPID_FIRE("rapid-fire", Material.FEATHER);
private final String configKey;
private final Material material;
WeaponType(String configKey, Material material) {
this.configKey = configKey;
this.material = material;
}
public String getConfigKey() { return configKey; }
public Material getMaterial() { return material; }
/** Hotbar-Slot (0-basiert) */
public int getSlot() { return ordinal(); }
}

View File

@@ -0,0 +1,52 @@
package de.lasertec.weapon;
import de.lasertec.LasertecPlugin;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays;
public final class WeaponUtil {
private WeaponUtil() {}
/** Erstellt ein Waffen-ItemStack — Werte aus config.yml. */
public static ItemStack create(LasertecPlugin plugin, WeaponType type) {
var cfg = plugin.getConfigManager();
String key = type.getConfigKey();
ItemStack item = new ItemStack(type.getMaterial());
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(cfg.getWeaponName(key));
meta.setLore(Arrays.asList(
"§7" + cfg.getWeaponDesc(key),
"§8──────────────────",
"§7Schaden: §c" + cfg.getWeaponDamage(key),
"§7Reichweite: §e" + cfg.getWeaponRange(key) + " Blöcke",
"§7Cooldown: §b" + String.format("%.2f", cfg.getWeaponCooldown(key) / 1000.0) + "s",
cfg.getWeaponPellets(key) > 1
? "§7Pellets: §6" + cfg.getWeaponPellets(key)
: "§8(1 Schuss)",
"§8──────────────────",
"§8Lasertec · Rechtsklick = Schießen"
));
meta.addEnchant(Enchantment.DURABILITY, 1, true);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE);
meta.setUnbreakable(true);
item.setItemMeta(meta);
return item;
}
/** Identifiziert WeaponType anhand des Item-Namens (aus config). */
public static WeaponType identify(LasertecPlugin plugin, ItemStack item) {
if (item == null || !item.hasItemMeta()) return null;
String name = item.getItemMeta().getDisplayName();
for (WeaponType t : WeaponType.values()) {
if (plugin.getConfigManager().getWeaponName(t.getConfigKey()).equals(name)) return t;
}
return null;
}
}

View File

@@ -0,0 +1,206 @@
# ============================================================
# LASERTEC v3.0 — Vollständige Konfiguration
# Alle Werte hier können ohne Neustart geändert werden:
# /ltadmin reload
# ============================================================
# ── Allgemeines ──────────────────────────────────────────────
messages:
prefix: "§8[§b§lLASERTEC§8] §r"
language: "DE" # Nur DE unterstützt (erweiterbar)
lobby:
location: null # Wird per /ltadmin setlobby gesetzt
# ── Spiel-Grundeinstellungen ─────────────────────────────────
game:
game-duration: 300 # Spielzeit in Sekunden
countdown: 10 # Countdown-Sekunden vor Spielstart
min-players: 2 # Mindestspieler (alle Teams zusammen)
max-players-per-team: 4 # Max. Spieler pro Team (×4 = 16 gesamt)
end-display-time: 8 # Sekunden bis Reset nach Spielende
# ── Respawn / Hit-Mechanik ───────────────────────────────────
heal:
base-heal-time: 2 # Sekunden an der Basis zum Heilen
base-radius: 4.0 # Radius (Blöcke) um Basis-Block zum Heilen
invincible-after-heal: true # Kurze Unverwundbarkeit nach Heilung
invincible-duration: 60 # Ticks (60 = 3s) Unverwundbarkeit nach Heal
# ── Punkte-System ────────────────────────────────────────────
scoring:
kill-points: 100 # Punkte für Treffer auf Gegner
base-attack-points: 60 # Punkte für Treffer auf Gegnerbasis
base-destroy-bonus: 200 # Bonus wenn Basis vollständig zerstört
streak-3-bonus: 50 # Extra-Punkte bei 3er-Serie
streak-5-bonus: 100 # Extra-Punkte bei 5er-Serie
streak-10-bonus: 250 # Extra-Punkte bei 10er-Serie
# ── Basen ────────────────────────────────────────────────────
base:
health: 5 # Treffer bis Basis zerstört
warn-at-hp: 2 # Team warnen wenn Basis-HP auf diesen Wert fällt
regenerate: false # Basis-HP regenerieren während Spiel?
regenerate-interval: 60 # Sekunden zwischen Regen (wenn aktiviert)
# ── Anti-Camp System ─────────────────────────────────────────
anti-camp:
enabled: true
# Maximale Zeit in Sekunden die ein Spieler im selben Bereich bleiben darf
max-idle-seconds: 15
# Radius in Blöcken der als "gleicher Bereich" gilt
idle-radius: 5.0
# Was passiert wenn der Spieler zu lange still steht:
action: WARN_THEN_PUNISH # WARN_ONLY | WARN_THEN_PUNISH | PUNISH_ONLY
# Warn-Nachricht (bei WARN)
warn-message: "§c⚠ CAMPEN VERBOTEN! Bewege dich oder verliere Punkte!"
# Punkte-Abzug pro Sekunde beim Campen (bei PUNISH)
score-penalty: 10
# Sekunden Warnung bevor Strafe beginnt
warn-duration: 5
# Basisbereich von Anti-Camp ausschließen? (Spieler können an Basis heilen)
exclude-base-radius: 8.0
# Sound bei Warnung
warn-sound: BLOCK_NOTE_BLOCK_BASS
warn-sound-pitch: 0.5
# ── Minimap / Mod-Schutz ─────────────────────────────────────
mod-protection:
enabled: true
# Xaero's Minimap / VoxelMap etc. blockieren durch Fog-of-War:
# Spieler sehen nur einen begrenzten Radius um sich herum auf der Map
fog-of-war: true
fog-radius: 48 # Blöcke sichtbarer Radius (Render-Distance Trick)
# Regelmäßig unsichtbare Barrier-Blöcke um die Arena spawnen
# um Außenansicht-Cheats zu erschweren
arena-barrier: true
# F3-Debug-Screen blockieren (verhindert Koordinaten-Anzeige)
block-f3: false # experimentell, kann Lag verursachen
# Spieler-Koordinaten aus Tab-Liste entfernen
hide-coordinates: true
# Alle Spieler im Spiel aus der normalen Tab-Liste entfernen
hide-from-tab: true
# Spieler-Nametags im Spiel verbergen
hide-nametags: true
# Unsichtbare Spieler wirklich unsichtbar (verhindert Skeleton-Outline durch Mods)
strict-invisibility: true
# ── Schild-System (Join-Schild) ──────────────────────────────
join-sign:
# Erste Zeile des Schilds (exakt so schreiben!)
trigger-line: "[Lasertec]"
# Farben im Schild
color-waiting: "§a" # Grün = Wartend
color-starting: "§e" # Gelb = Startet
color-running: "§c" # Rot = Läuft
color-full: "§8" # Grau = Voll
# Format Zeile 2 (Arenaname), Zeile 3 (Status), Zeile 4 (Spieler)
line-arena: "§b{arena}"
line-status: "{color}{status}"
line-players: "§7{players}§8/§7{max}"
# Update-Intervall des Schilds in Ticks (20 = 1s)
update-interval: 20
# ── Waffen-Einstellungen ─────────────────────────────────────
weapons:
laser-gun:
enabled: true
display-name: "§b⚡ Laser-Pistole"
damage: 25
range: 30
cooldown-ms: 300
pellets: 1
particle: END_ROD
description: "Standard-Waffe. Zuverlässig & schnell."
sniper:
enabled: true
display-name: "§5🎯 Laser-Sniper"
damage: 80
range: 60
cooldown-ms: 2000
pellets: 1
particle: DRAGON_BREATH
description: "Hoher Schaden, lange Reichweite."
shotgun:
enabled: true
display-name: "§6💥 Laser-Shotgun"
damage: 20
range: 12
cooldown-ms: 900
pellets: 5
particle: FLAME
description: "5 Pellets gleichzeitig, kurze Reichweite."
rapid-fire:
enabled: true
display-name: "§a⚡⚡ Rapid-Fire"
damage: 12
range: 22
cooldown-ms: 120
pellets: 1
particle: CRIT
description: "Niedrig Schaden, sehr hohe Feuerrate."
# ── Sound-Einstellungen ──────────────────────────────────────
sounds:
enabled: true
shoot: ENTITY_FIREWORK_ROCKET_BLAST
shoot-pitch: 1.8
shoot-volume: 0.4
hit-shooter: BLOCK_NOTE_BLOCK_BELL
hit-shooter-pitch: 2.0
hit-victim: ENTITY_PLAYER_HURT
heal-start: BLOCK_ENCHANTMENT_TABLE_USE
heal-complete: ENTITY_EXPERIENCE_ORB_PICKUP
heal-complete-pitch: 1.5
base-hit: ENTITY_GENERIC_EXPLODE
game-start: ENTITY_ENDER_DRAGON_GROWL
game-end: UI_TOAST_CHALLENGE_COMPLETE
countdown-tick: BLOCK_NOTE_BLOCK_PLING
warning: BLOCK_NOTE_BLOCK_BASS
streak-3: BLOCK_BELL_USE
streak-5: ENTITY_PLAYER_LEVELUP
streak-10: ENTITY_LIGHTNING_BOLT_THUNDER
# ── Partikel-Einstellungen ───────────────────────────────────
particles:
enabled: true
laser-trail: true
hit-effect: true
hit-particle-count: 15
base-hit-effect: true
heal-effect: true
# ── Scoreboard-Einstellungen ─────────────────────────────────
scoreboard:
enabled: true
title: "§b§l⚡ LASERTEC"
show-team-scores: true
show-base-health: true
show-kill-streak: true # Nur bei Streak ≥ 3 anzeigen
show-player-status: true
update-interval: 20 # Ticks
# ── Nachrichten (anpassbar) ──────────────────────────────────
text:
join: "§e{player} §7hat Team {team} §7beigetreten."
leave: "§c{player} §7hat das Spiel verlassen."
game-start: "§a§l⚡ DAS SPIEL BEGINNT!"
game-end: "§6§l🏆 Team {team} §6§lhat gewonnen!"
game-draw: "§7Unentschieden!"
hit-shooter: "§aDu hast §e{victim} §agetroffen! §7(+{pts} Pkt){streak}"
hit-victim: "§cDu wurdest von §e{shooter} §cgetroffen! Gehe zur Basis!"
heal-start: "§aHeilung gestartet... §7({secs}s an der Basis bleiben!)"
heal-interrupted: "§c⚠ Heilung abgebrochen! Bleib an der Basis!"
heal-complete: "§a✔ Du bist wieder einsatzbereit!"
base-attacked: "§c⚠ Eure Basis wird angegriffen! §8[HP: {hp}/{max}]"
base-destroyed: "§c§l💥 Die Basis von Team {team} §c§lwurde ZERSTÖRT!"
camp-warn: "§c⚠ CAMPEN VERBOTEN! Bewege dich!"
time-60: "§e⏰ Noch §b60 §eSekunden!"
time-30: "§c⏰ Noch §b30 §eSekunden!"
time-10: "§4⏰ Noch §b10 §4Sekunden!"
streak-3: "§6TRIPLE KILL! §e{player} §7 3er Serie!"
streak-5: "§bPENTA KILL! §e{player} §7 5er Serie!"
streak-10: "§5§lGODLIKE! §e{player} §7 10er Serie!"

View File

@@ -0,0 +1,24 @@
name: Lasertec
version: '2.0.0'
main: de.lasertec.LasertecPlugin
api-version: 1.20
description: Realistic LaserTag Minigame with Base Mechanics
authors: [ LaserTec-Dev ]
commands:
lasertec:
description: Main Lasertec command
aliases: [ lt, laser ]
usage: /lasertec <join|leave|stats|list|top>
ltadmin:
description: Lasertec admin commands
usage: /ltadmin <help>
permission: lasertec.admin
permissions:
lasertec.play:
description: Allows playing Lasertec
default: true
lasertec.admin:
description: Allows admin commands
default: op