Upload folder via GUI - src
This commit is contained in:
77
src/main/java/de/lasertec/LasertecPlugin.java
Normal file
77
src/main/java/de/lasertec/LasertecPlugin.java
Normal 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; }
|
||||
}
|
||||
153
src/main/java/de/lasertec/arena/Arena.java
Normal file
153
src/main/java/de/lasertec/arena/Arena.java
Normal 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; }
|
||||
}
|
||||
163
src/main/java/de/lasertec/arena/ArenaManager.java
Normal file
163
src/main/java/de/lasertec/arena/ArenaManager.java
Normal 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; }
|
||||
}
|
||||
}
|
||||
149
src/main/java/de/lasertec/camp/AntiCampManager.java
Normal file
149
src/main/java/de/lasertec/camp/AntiCampManager.java
Normal 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);
|
||||
}
|
||||
}
|
||||
126
src/main/java/de/lasertec/command/LasertecCommand.java
Normal file
126
src/main/java/de/lasertec/command/LasertecCommand.java
Normal 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═══════════════════════════════════");
|
||||
}
|
||||
}
|
||||
220
src/main/java/de/lasertec/command/LtAdminCommand.java
Normal file
220
src/main/java/de/lasertec/command/LtAdminCommand.java
Normal 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");
|
||||
}
|
||||
}
|
||||
178
src/main/java/de/lasertec/config/ConfigManager.java
Normal file
178
src/main/java/de/lasertec/config/ConfigManager.java
Normal 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();
|
||||
}
|
||||
}
|
||||
733
src/main/java/de/lasertec/game/Game.java
Normal file
733
src/main/java/de/lasertec/game/Game.java
Normal 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();
|
||||
}
|
||||
}
|
||||
93
src/main/java/de/lasertec/game/GameManager.java
Normal file
93
src/main/java/de/lasertec/game/GameManager.java
Normal 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()); }
|
||||
}
|
||||
8
src/main/java/de/lasertec/game/GameState.java
Normal file
8
src/main/java/de/lasertec/game/GameState.java
Normal 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
|
||||
}
|
||||
38
src/main/java/de/lasertec/game/Team.java
Normal file
38
src/main/java/de/lasertec/game/Team.java
Normal 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; }
|
||||
}
|
||||
31
src/main/java/de/lasertec/listener/BlockListener.java
Normal file
31
src/main/java/de/lasertec/listener/BlockListener.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/main/java/de/lasertec/listener/PlayerListener.java
Normal file
44
src/main/java/de/lasertec/listener/PlayerListener.java
Normal 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());
|
||||
}
|
||||
}
|
||||
246
src/main/java/de/lasertec/listener/SignListener.java
Normal file
246
src/main/java/de/lasertec/listener/SignListener.java
Normal 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(); }
|
||||
}
|
||||
35
src/main/java/de/lasertec/listener/WeaponListener.java
Normal file
35
src/main/java/de/lasertec/listener/WeaponListener.java
Normal 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);
|
||||
}
|
||||
}
|
||||
125
src/main/java/de/lasertec/player/LaserPlayer.java
Normal file
125
src/main/java/de/lasertec/player/LaserPlayer.java
Normal 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; }
|
||||
}
|
||||
73
src/main/java/de/lasertec/player/PlayerDataManager.java
Normal file
73
src/main/java/de/lasertec/player/PlayerDataManager.java
Normal 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(); }
|
||||
}
|
||||
}
|
||||
48
src/main/java/de/lasertec/player/PlayerStats.java
Normal file
48
src/main/java/de/lasertec/player/PlayerStats.java
Normal 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; }
|
||||
}
|
||||
215
src/main/java/de/lasertec/protection/ModProtectionManager.java
Normal file
215
src/main/java/de/lasertec/protection/ModProtectionManager.java
Normal 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═══════════════════════════════════════");
|
||||
}
|
||||
}
|
||||
113
src/main/java/de/lasertec/scoreboard/ScoreboardManager.java
Normal file
113
src/main/java/de/lasertec/scoreboard/ScoreboardManager.java
Normal 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);
|
||||
}
|
||||
}
|
||||
30
src/main/java/de/lasertec/weapon/WeaponType.java
Normal file
30
src/main/java/de/lasertec/weapon/WeaponType.java
Normal 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(); }
|
||||
}
|
||||
52
src/main/java/de/lasertec/weapon/WeaponUtil.java
Normal file
52
src/main/java/de/lasertec/weapon/WeaponUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
206
src/main/resources/config.yml
Normal file
206
src/main/resources/config.yml
Normal 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!"
|
||||
24
src/main/resources/plugin.yml
Normal file
24
src/main/resources/plugin.yml
Normal 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
|
||||
Reference in New Issue
Block a user