diff --git a/src/main/java/de/viper/survivalplus/Manager/ShopManager.java b/src/main/java/de/viper/survivalplus/Manager/ShopManager.java index 01ec6b2..7716371 100644 --- a/src/main/java/de/viper/survivalplus/Manager/ShopManager.java +++ b/src/main/java/de/viper/survivalplus/Manager/ShopManager.java @@ -28,24 +28,51 @@ public class ShopManager { return shopConfig.getDouble("items." + itemKey + ".current-price", 0); } + public int getStock(String itemKey) { + return shopConfig.getInt("items." + itemKey + ".stock", 0); + } + + /** + * Spieler kauft Item: Lager sinkt, Preis steigt (Verknappung) + */ public boolean buyItem(String itemKey, int amount) { int stock = shopConfig.getInt("items." + itemKey + ".stock", 0); + if (stock < amount) { return false; } + double price = shopConfig.getDouble("items." + itemKey + ".current-price", 0); + + // Lager aktualisieren shopConfig.set("items." + itemKey + ".stock", stock - amount); - shopConfig.set("items." + itemKey + ".current-price", price * 0.95); + + // Preis erhöhen (Faktor 1.05 = +5%) + double newPrice = price * 1.05; + shopConfig.set("items." + itemKey + ".current-price", newPrice); + saveShop(); return true; } - public void sellItem(String itemKey, int amount) { - int stock = shopConfig.getInt("items." + itemKey + ".stock", 0); + /** + * Spieler verkauft Item: Lager steigt, Preis sinkt (Überfluss) + */ + public boolean sellItem(String itemKey, int amount) { + // Hier könnte man prüfen, ob der Shop genügend Geld hat (falls nötig) + double price = shopConfig.getDouble("items." + itemKey + ".current-price", 0); + int stock = shopConfig.getInt("items." + itemKey + ".stock", 0); + + // Lager aktualisieren shopConfig.set("items." + itemKey + ".stock", stock + amount); - shopConfig.set("items." + itemKey + ".current-price", price * 0.95); + + // Preis senken (Faktor 0.95 = -5%) + double newPrice = price * 0.95; + shopConfig.set("items." + itemKey + ".current-price", newPrice); + saveShop(); + return true; } public void addOrUpdateItem(String itemKey, double basePrice, int stock) { @@ -67,4 +94,4 @@ public class ShopManager { public FileConfiguration getShopConfig() { return shopConfig; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/Manager/TablistManager.java b/src/main/java/de/viper/survivalplus/Manager/TablistManager.java index 13529b1..ef6816e 100644 --- a/src/main/java/de/viper/survivalplus/Manager/TablistManager.java +++ b/src/main/java/de/viper/survivalplus/Manager/TablistManager.java @@ -1,31 +1,28 @@ package de.viper.survivalplus.Manager; import de.viper.survivalplus.SurvivalPlus; +import me.clip.placeholderapi.PlaceholderAPI; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.model.user.User; import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; 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.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.ScoreboardManager; import org.bukkit.scoreboard.Team; -import me.clip.placeholderapi.PlaceholderAPI; -import net.luckperms.api.LuckPerms; -import net.luckperms.api.LuckPermsProvider; -import net.luckperms.api.model.user.User; import java.io.File; import java.lang.reflect.Method; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,19 +44,20 @@ public class TablistManager implements Listener { private String separatorLine; private LuckPerms luckPerms; private boolean hasPlaceholderAPI; - private final Scoreboard scoreboard; - private final Map prefixTeams; private FileConfiguration nicknameConfig; + private final Scoreboard scoreboard; + private final Map prefixTeams = new HashMap<>(); + + private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}"); + public TablistManager(SurvivalPlus plugin) { this.plugin = plugin; - this.prefixTeams = new HashMap<>(); // Scoreboard initialisieren ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager(); this.scoreboard = scoreboardManager != null ? scoreboardManager.getMainScoreboard() : null; - // Resource sicherstellen, Config laden try { File tablistFile = new File(plugin.getDataFolder(), "tablist.yml"); if (!tablistFile.exists()) { @@ -73,7 +71,6 @@ public class TablistManager implements Listener { FileConfiguration config = plugin.getTablistConfig(); - // Konfigurationswerte laden this.enabled = config.getBoolean("enabled", true); this.serverName = config.getString("server-name", "SurvivalPlus"); this.website = config.getString("website", "www.example.com"); @@ -84,27 +81,22 @@ public class TablistManager implements Listener { this.staffPermission = config.getString("staff-permission", "survivalplus.staff"); this.separatorLine = config.getString("separator-line", "&8&l&m================"); - // LuckPerms API initialisieren try { this.luckPerms = LuckPermsProvider.get(); } catch (Throwable e) { luckPerms = null; } - // Prüfen, ob PlaceholderAPI verfügbar ist this.hasPlaceholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); - // Chat-Listener registrieren - if (enabled) { - Bukkit.getPluginManager().registerEvents(this, plugin); - } + // Registriere Chat-Listener hier, damit Prefix im Chat gesetzt wird + Bukkit.getPluginManager().registerEvents(this, plugin); if (!enabled) { plugin.getLogger().info("Tablist ist deaktiviert (tablist.yml -> enabled: false)"); return; } - // Header- und Footer-Animationen füllen List configHeader = config.getStringList("header-animations"); List configFooter = config.getStringList("footer-animations"); @@ -131,45 +123,49 @@ public class TablistManager implements Listener { new BukkitRunnable() { @Override public void run() { - // Nickname Config neu laden reloadNicknameConfig(); if (headerAnim.isEmpty() || footerAnim.isEmpty()) return; - // Online-Spieler und Staff zählen int onlinePlayers = Bukkit.getOnlinePlayers().size(); int onlineStaff = (int) Bukkit.getOnlinePlayers().stream() .filter(p -> p.hasPermission(staffPermission)) .count(); - // Aktuelles Datum und Uhrzeit formatieren SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); String currentTime = dateFormat.format(new Date()); for (Player player : Bukkit.getOnlinePlayers()) { try { - // Nickname abrufen (farbig) + // ------ Name / Nick handling exactly like in your old code ------ String displayName = getNickname(player); - // Falls kein Nickname, Name nehmen if (displayName == null || displayName.trim().isEmpty()) { displayName = player.getName(); } - // Spielername für die Tablist setzen (Spielerliste auf der rechten Seite) - String playerListName; String rankPrefix = getPlayerPrefix(player); - + // Update nametag (prefix above head) as in old code: + updateNametag(player, rankPrefix, getNickname(player)); // pass raw nickname (colored) if present + + // PlayerList (TAB) + int ping = getPlayerPing(player); + String playerListName; if (luckPerms == null && !hasPlaceholderAPI) { playerListName = displayName; - updateNametag(player, rankPrefix, displayName); } else { - int ping = getPlayerPing(player); playerListName = color(rankPrefix + displayName + (ping >= 0 ? "&8 | &e" + ping + "ms" : "")); - updateNametag(player, rankPrefix, displayName); } - player.setPlayerListName(playerListName); - // Header mit Spielername/Nickname und Statistiken + try { + player.setPlayerListName(playerListName); + } catch (Exception ignored) { + // safe fallback: set without prefix + try { + player.setPlayerListName(color(displayName)); + } catch (Exception ignored2) {} + } + + // Header/Footer build (including TS/Discord/Website and time) String headerRaw = headerAnim.get(headerIndex) .replace("{server}", serverName) .replace("{player}", displayName) @@ -177,11 +173,9 @@ public class TablistManager implements Listener { .replace("{staff}", String.valueOf(onlineStaff)); String footerRaw = footerAnim.get(footerIndex); - // Footer zusammenstellen: TS und Discord nebeneinander, Webseite zentriert StringBuilder footerBuilder = new StringBuilder(); - footerBuilder.append("\n"); // Extra Abstand + footerBuilder.append("\n"); footerBuilder.append(color(footerRaw)).append("\n"); - footerBuilder.append(color(separatorLine)).append("\n"); if (showTeamspeak || showDiscord) { StringBuilder socialLine = new StringBuilder(); @@ -203,28 +197,25 @@ public class TablistManager implements Listener { String header = color(headerRaw); String footer = color(footerBuilder.toString()); - // 1) Adventure (Component) Variante + // Try Adventure-based header/footer first, fallback to String reflection boolean done = tryAdventureComponent(player, headerRaw, footerBuilder.toString()); - - // 2) String-Variante fallback if (!done) { - done = tryStringMethod(player, header, footer); + tryStringMethod(player, header, footer); } - // 3) Keine Warnung bei Fehlschlag - } catch (Exception ignored) { - // Keine Konsolenausgabe für Fehler bei der Tablist + } catch (Exception ex) { + plugin.getLogger().warning("TablistManager error: " + ex.getMessage()); } } headerIndex = (headerIndex + 1) % headerAnim.size(); footerIndex = (footerIndex + 1) % footerAnim.size(); } - }.runTaskTimer(plugin, 0L, interval); + }.runTaskTimer(plugin, 40L, interval); } /** - * Lädt die nicknames.yml neu + * Lädt nicknames.yml neu. */ private void reloadNicknameConfig() { try { @@ -239,7 +230,7 @@ public class TablistManager implements Listener { } /** - * Nickname abrufen und formatieren + * Nickname abrufen und formatieren (farbig + hex) */ private String getNickname(Player player) { if (nicknameConfig == null) return null; @@ -254,70 +245,11 @@ public class TablistManager implements Listener { } /** - * Nametag über dem Kopf aktualisieren - */ - private void updateNametag(Player player, String rankPrefix, String nickname) { - if (scoreboard == null) return; - - try { - String teamName = generateTeamName(player, rankPrefix); - Team team = scoreboard.getTeam(teamName); - - if (team == null) { - team = scoreboard.registerNewTeam(teamName); - } - - // WICHTIG: - // Wir nutzen den Rang-Prefix ODER den Nickname als Team-Prefix. - // Da wir den Namen über dem Kopf nicht komplett ersetzen können (ohne ProtocolLib), - // zeigen wir den Nickname als Prefix an. - // Ergebnis über Kopf: [Nick] Spielername - - String prefix; - - // Wenn ein Nickname existiert, hat er Vorrang über den Rang-Prefix (damit man ihn sieht) - if (nickname != null && !nickname.trim().isEmpty()) { - prefix = nickname; - } else { - prefix = color(rankPrefix != null && !rankPrefix.trim().isEmpty() ? rankPrefix : (luckPerms != null ? "&7[Spieler] " : "")); - } - - team.setPrefix(prefix); - - // WICHTIG: Der Entry MUSS der echte Spielername sein. - // Wir nutzen hier den echten Namen, nicht den Nicknamen, da Nicks zu lang sein können oder Sonderzeichen enthalten. - String entry = player.getName(); - - if (!team.hasEntry(entry)) { - // Spieler aus anderen Teams entfernen - for (Team existingTeam : scoreboard.getTeams()) { - if (!existingTeam.getName().equals(teamName)) { - existingTeam.removeEntry(entry); - } - } - team.addEntry(entry); - } - - // Scoreboard für alle Spieler synchronisieren - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - if (onlinePlayer.getScoreboard() != scoreboard) { - onlinePlayer.setScoreboard(scoreboard); - } - } - - } catch (Exception ignored) {} - } - - /** - * Farben übersetzen + * Übersetzt &-Codes und Hex-Farben */ private String translateNickColors(String input) { String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input); - return replaceHexColors(withLegacy); - } - - private String replaceHexColors(String input) { - Matcher matcher = HEX_PATTERN.matcher(input); + Matcher matcher = HEX_PATTERN.matcher(withLegacy); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String hexCode = matcher.group(); @@ -328,7 +260,68 @@ public class TablistManager implements Listener { return sb.toString(); } - private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}"); + /** + * Aktualisiert den Nametag über dem Kopf genau wie in deinem alten Code: + * - Wenn Nick vorhanden: Nick als Prefix (zeigt [Nick] Spielername) + * - Sonst Rang-Prefix (oder [Spieler] wenn kein Rang) + * Der Entry ist dabei immer der echte Spielername. + */ + private void updateNametag(Player player, String rankPrefix, String nickname) { + if (scoreboard == null) return; + + try { + String teamName = generateTeamName(player, rankPrefix); + Team team = scoreboard.getTeam(teamName); + + if (team == null) { + // register new team if not exists + try { + team = scoreboard.registerNewTeam(teamName); + } catch (IllegalArgumentException ignored) { + team = scoreboard.getTeam(teamName); + } + } + if (team == null) return; + + String prefix; + if (nickname != null && !nickname.trim().isEmpty()) { + prefix = nickname; + } else { + String rp = (rankPrefix != null && !rankPrefix.trim().isEmpty()) ? rankPrefix : "&7[&aSpieler&7]&f "; + prefix = color(rp); + } + + try { + team.setPrefix(prefix); + } catch (Exception ignored) {} + + // Ensure the genuine player name is used as the team entry + String entry = player.getName(); + + if (!team.hasEntry(entry)) { + // Remove from other teams to avoid duplicates + for (Team existingTeam : scoreboard.getTeams()) { + if (existingTeam != null && !existingTeam.getName().equals(teamName)) { + try { + existingTeam.removeEntry(entry); + } catch (Exception ignored) {} + } + } + try { + team.addEntry(entry); + } catch (Exception ignored) {} + } + + // Synchronize scoreboard to online players (use main scoreboard) + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + try { + if (onlinePlayer.getScoreboard() != scoreboard) { + onlinePlayer.setScoreboard(scoreboard); + } + } catch (Exception ignored) {} + } + } catch (Exception ignored) {} + } /** * Eindeutigen Teamnamen generieren @@ -342,74 +335,7 @@ public class TablistManager implements Listener { } /** - * Rang-Prefix abrufen - */ - private String getPlayerPrefix(Player player) { - if (luckPerms != null) { - try { - User user = luckPerms.getPlayerAdapter(Player.class).getUser(player); - String prefix = user.getCachedData().getMetaData().getPrefix(); - if (prefix != null && !prefix.trim().isEmpty()) { - return prefix + " "; - } - } catch (Exception ignored) {} - if (hasPlaceholderAPI) { - try { - String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); - if (prefix != null && !prefix.trim().isEmpty()) { - return prefix + " "; - } - } catch (Exception ignored) {} - } - return "&7[Spieler] "; - } - if (hasPlaceholderAPI) { - try { - String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); - if (prefix != null && !prefix.trim().isEmpty()) { - return prefix + " "; - } - } catch (Exception ignored) {} - } - return "&7[Spieler] "; - } - - /** - * Spieler-Ping abrufen - */ - private int getPlayerPing(Player player) { - try { - Method getHandle = player.getClass().getMethod("getHandle"); - Object entityPlayer = getHandle.invoke(player); - return entityPlayer.getClass().getField("ping").getInt(entityPlayer); - } catch (Exception ignored) { - return -1; - } - } - - /** - * Chat-Format (Nickname wird hier angezeigt) - */ - @EventHandler - public void onPlayerChat(AsyncPlayerChatEvent event) { - try { - Player player = event.getPlayer(); - // Nickname abrufen (wichtig: hier neu abrufen, da es asynchron ist und sich geändert haben könnte) - // Da Config-Lesezugriffe meist Thread-Safe sind, ist das hier okay. - String displayName = getNickname(player); - if (displayName == null || displayName.trim().isEmpty()) { - displayName = player.getName(); - } - - String prefix = getPlayerPrefix(player); - // Format: Rang + Nick + ": " + Nachricht - String format = color(prefix + displayName + "&7: &f") + "%2$s"; - event.setFormat(format); - } catch (Exception ignored) {} - } - - /** - * Adventure-Variante + * Versucht, Adventure Components zu verwenden (falls verfügbar). */ private boolean tryAdventureComponent(Player player, String headerRaw, String footerRaw) { try { @@ -458,13 +384,13 @@ public class TablistManager implements Listener { } } } catch (Exception ignored) { - return false; + // fallback to string method } return false; } /** - * String-Variante + * Fallback: konventionelle String-Header/Footer mittels Reflection */ private boolean tryStringMethod(Player player, String header, String footer) { try { @@ -484,9 +410,7 @@ public class TablistManager implements Listener { mf.invoke(player, footer); return true; } - } catch (Exception ignored) { - return false; - } + } catch (Exception ignored) {} return false; } @@ -502,8 +426,70 @@ public class TablistManager implements Listener { return null; } + /** + * Holt den Rang-Prefix (LuckPerms -> PlaceholderAPI -> Default [Spieler]) + */ + private String getPlayerPrefix(Player player) { + if (luckPerms != null) { + try { + User user = luckPerms.getPlayerAdapter(Player.class).getUser(player); + String prefix = user.getCachedData().getMetaData().getPrefix(); + if (prefix != null && !prefix.trim().isEmpty()) { + return prefix + " "; + } + } catch (Exception ignored) {} + if (hasPlaceholderAPI) { + try { + String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); + if (prefix != null && !prefix.trim().isEmpty()) { + return prefix + " "; + } + } catch (Exception ignored) {} + } + return "&7[&aSpieler&7]&f "; + } + if (hasPlaceholderAPI) { + try { + String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); + if (prefix != null && !prefix.trim().isEmpty()) { + return prefix + " "; + } + } catch (Exception ignored) {} + } + return "&7[&aSpieler&7]&f "; + } + + private int getPlayerPing(Player player) { + try { + Method getHandle = player.getClass().getMethod("getHandle"); + Object entityPlayer = getHandle.invoke(player); + return entityPlayer.getClass().getField("ping").getInt(entityPlayer); + } catch (Exception ignored) { + return -1; + } + } + + /** + * Chat-Format (Nickname wird hier angezeigt). Setzt Prefix + Nick/Name im Chat. + */ + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerChat(AsyncPlayerChatEvent event) { + try { + Player player = event.getPlayer(); + String displayName = getNickname(player); + if (displayName == null || displayName.trim().isEmpty()) { + displayName = player.getName(); + } + + String prefix = getPlayerPrefix(player); + // Format: Rang + Nick + ": " + Nachricht + String format = color(prefix + displayName + "&7: &f") + "%2$s"; + event.setFormat(format); + } catch (Exception ignored) {} + } + private String color(String msg) { if (msg == null) return ""; return msg.replace("&", "§"); } -} \ No newline at end of file +} diff --git a/src/main/java/de/viper/survivalplus/SurvivalPlus.java b/src/main/java/de/viper/survivalplus/SurvivalPlus.java index 49b2803..6532263 100644 --- a/src/main/java/de/viper/survivalplus/SurvivalPlus.java +++ b/src/main/java/de/viper/survivalplus/SurvivalPlus.java @@ -92,6 +92,13 @@ public class SurvivalPlus extends JavaPlugin { private FileConfiguration leashesConfig; private File mobCapFile; private FileConfiguration mobCapConfig; + + // --- NEU: Adaptive Mob System --- + private File mobAdaptFile; + private FileConfiguration mobAdaptConfig; + private AdaptiveMobListener adaptiveMobListener; + // -------------------------------- + private FileConfiguration tablistConfig; private File tablistFile; private File nicknamesFile; @@ -240,6 +247,7 @@ public class SurvivalPlus extends JavaPlugin { createFriendsFile(); createLeashesFile(); createMobCapFile(); + createMobAdaptFile(); // NEU: Adaptive Mob File erstellen createNicknamesFile(); reloadBlockedCommandsConfig(); loadClaims(); @@ -334,6 +342,7 @@ public class SurvivalPlus extends JavaPlugin { pluginManager.registerEvents(lootChestManager, this); getCommand("lootchests").setExecutor(lootChestManager); getCommand("tploot").setExecutor(lootChestManager); + getCommand("ride").setExecutor(new RideCommand(this)); getCommand("claim").setExecutor(new ClaimCommand(this)); // HIER: (this) hinzugefügt, damit der BlockManager die Datei speichern kann BlockManager blockManager = new BlockManager(this); @@ -367,7 +376,14 @@ public class SurvivalPlus extends JavaPlugin { pluginManager.registerEvents(new WarpInventoryListener(this, warpManager), this); pluginManager.registerEvents(new ChallengeSmeltListener(funChallengeManager), this); pluginManager.registerEvents(new BlockDetectionListener(this), this); + pluginManager.registerEvents(new NickLoadListener(this), this); pluginManager.registerEvents(new ClaimListener(this), this); + + // --- NEU: Adaptive Mob Listener registrieren --- + adaptiveMobListener = new AdaptiveMobListener(this); + pluginManager.registerEvents(adaptiveMobListener, this); + // ----------------------------------------- + lockSystem = new LockSystem(this); pluginManager.registerEvents(lockSystem, this); getCommand("sp").setExecutor(lockSystem); @@ -392,7 +408,6 @@ public class SurvivalPlus extends JavaPlugin { }); pluginManager.registerEvents(new RepairSignListener(getConfig(), getLangConfig()), this); pluginManager.registerEvents(new ToolUpgradeListener(this), this); - pluginManager.registerEvents(new NickLoadListener(this), this); startAutoClearTask(); spawnArmorStandExample(); getLogger().info(getMessage("plugin.enabled")); @@ -460,7 +475,7 @@ public class SurvivalPlus extends JavaPlugin { } } - /** +/** * Speziell für config.yml: * - Setzt die Version in der geladenen FileConfiguration (getConfig()) und speichert sie, * - und sorgt anschließend dafür, dass die 'version:'-Zeile ganz oben in der Datei steht. @@ -743,6 +758,7 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver saveStats(); saveLeashesConfig(); saveMobCapConfig(); + saveMobAdaptConfig(); // NEU: Adaptive Mob Config speichern // NEU: NewbieProtection-Daten sichern if (newbieListener != null) { @@ -1089,6 +1105,33 @@ private void ensureConfigVersion(FileConfiguration config, File file, String ver getLogger().info("mobcap.yml erfolgreich neu geladen."); } + // === NEU: MobAdapt.yml === + private void createMobAdaptFile() { + mobAdaptFile = new File(getDataFolder(), "mobadapt.yml"); + if (!mobAdaptFile.exists()) { + try { + mobAdaptFile.createNewFile(); + getLogger().info("mobadapt.yml wurde erstellt."); + } catch (IOException e) { + getLogger().severe("Fehler beim Erstellen der mobadapt.yml: " + e.getMessage()); + } + } + mobAdaptConfig = YamlConfiguration.loadConfiguration(mobAdaptFile); + } + + public FileConfiguration getMobAdaptConfig() { + return mobAdaptConfig; + } + + public void saveMobAdaptConfig() { + try { + mobAdaptConfig.save(mobAdaptFile); + } catch (IOException e) { + getLogger().log(Level.SEVERE, "Fehler beim Speichern der mobadapt.yml: " + e.getMessage()); + } + } + // =========================== + // === AutoClearTask === private void startAutoClearTask() { if (autoClearTaskId != -1) { diff --git a/src/main/java/de/viper/survivalplus/commands/FriendCommand.java b/src/main/java/de/viper/survivalplus/commands/FriendCommand.java index d0e8eba..6c3a7c6 100644 --- a/src/main/java/de/viper/survivalplus/commands/FriendCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/FriendCommand.java @@ -116,14 +116,14 @@ public class FriendCommand implements CommandExecutor { } private void sendHelpMessage(Player player) { - player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.header", "&6=== Freundesliste Hilfe ==="))); + player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.header", "&6== Freundesliste Hilfe =="))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.add", "&e/friend add &7- Freundschaftsanfrage senden"))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.accept", "&e/friend accept &7- Freundschaftsanfrage akzeptieren"))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.deny", "&e/friend deny &7- Freundschaftsanfrage ablehnen"))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.list", "&e/friend list &7- Liste deiner Freunde anzeigen"))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.del", "&e/friend del &7- Freund aus der Liste entfernen"))); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.tp", "&e/friend tp &7- Zu einem Freund teleportieren"))); - player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.footer", "&6====================="))); + player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.help.footer", "&6======================="))); } private void handleFriendAdd(Player player, String targetName) { @@ -155,15 +155,23 @@ public class FriendCommand implements CommandExecutor { pendingRequests.add(playerUUID.toString()); friendsConfig.set(targetUUID + ".pending_requests", pendingRequests); friendsConfig.set(targetUUID + ".name", targetName); + + // --- FIX START: Absenderdaten speichern, damit er auch offline gefunden werden kann --- + friendsConfig.set(playerUUID + ".name", player.getName()); + friendsConfig.set(playerUUID + ".last-online", System.currentTimeMillis()); + // --- FIX END --- + saveFriendsConfig(); player.sendMessage(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.sent", "&aFreundschaftsanfrage an %s gesendet!").replace("%s", targetName))); TextComponent message = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.received", "&aDu hast eine Freundschaftsanfrage von %s erhalten! ").replace("%s", player.getName()))); TextComponent accept = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.accept-button", "&a[Accept]"))); - accept.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend accept " + player.getName())); + // ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND + accept.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend accept " + player.getName())); TextComponent deny = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.add.deny-button", "&c [Deny]"))); - deny.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend deny " + player.getName())); + // ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND + deny.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend deny " + player.getName())); message.addExtra(accept); message.addExtra(deny); target.spigot().sendMessage(message); @@ -268,7 +276,8 @@ public class FriendCommand implements CommandExecutor { } TextComponent removeButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.list.remove-button", "&c[X]"))); - removeButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend del " + friendName)); + // ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND + removeButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend del " + friendName)); entry.addExtra(" "); entry.addExtra(removeButton); @@ -296,9 +305,11 @@ public class FriendCommand implements CommandExecutor { TextComponent confirmMessage = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.confirm", "&cMöchtest du %s wirklich aus deiner Freundesliste entfernen? ").replace("%s", friendName))); TextComponent confirmButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.confirm-button", "&a[Confirm]"))); - confirmButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend confirm " + friendName)); + // ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND + confirmButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend confirm " + friendName)); TextComponent cancelButton = new TextComponent(ChatColor.translateAlternateColorCodes('&', langConfig.getString("friend.del.cancel-button", "&c[Cancel]"))); - cancelButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/friend list")); + // ÄNDERUNG: RUN_COMMAND -> SUGGEST_COMMAND + cancelButton.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/friend list")); confirmMessage.addExtra(confirmButton); confirmMessage.addExtra(" "); confirmMessage.addExtra(cancelButton); diff --git a/src/main/java/de/viper/survivalplus/commands/NickCommand.java b/src/main/java/de/viper/survivalplus/commands/NickCommand.java index c6b033f..22d3a24 100644 --- a/src/main/java/de/viper/survivalplus/commands/NickCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/NickCommand.java @@ -38,23 +38,21 @@ public class NickCommand implements CommandExecutor { return true; } - // Befehl zum Entfernen des Nicks + // Entfernen des Nicks if (args.length == 1 && (args[0].equalsIgnoreCase("off") || args[0].equalsIgnoreCase("remove") || args[0].equalsIgnoreCase("reset"))) { String uuid = player.getUniqueId().toString(); - - // Aus Config entfernen + plugin.getNicknamesConfig().set(uuid, null); plugin.saveNicknamesConfig(); - - // Visuell sofort zurücksetzen + + // WICHTIG: Nur DisplayName setzen, TablistManager übernimmt den Rest player.setDisplayName(player.getName()); - player.setPlayerListName(player.getName()); - + player.sendMessage("§aDein Nickname wurde entfernt."); return true; } - // Name zusammensetzen + // Zusammenbauen des Nicknames StringBuilder sb = new StringBuilder(); for (String arg : args) { sb.append(arg).append(" "); @@ -73,28 +71,24 @@ public class NickCommand implements CommandExecutor { // Farben anwenden String coloredNick = translateColors(rawNick); - // Format: [Nickname] (Klammern weiß, Nickname farbig) - String finalNick = "§f[" + coloredNick + "§f]"; + // Chat/Display: [Nick] + String finalNickForDisplay = "§f[" + coloredNick + "§f]"; - // Anwenden - player.setDisplayName(finalNick); - player.setPlayerListName(finalNick); + // WICHTIG: Nur DisplayName setzen + // playerListName wird vom TablistManager automatisch gesetzt + player.setDisplayName(finalNickForDisplay); // Speichern (ungefärbten Namen) plugin.getNicknamesConfig().set(player.getUniqueId().toString(), rawNick); plugin.saveNicknamesConfig(); - player.sendMessage("§aDein Nickname wurde zu " + finalNick + " geändert."); + player.sendMessage("§aDein Nickname wurde zu " + finalNickForDisplay + " geändert."); return true; } private String translateColors(String input) { String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input); - return replaceHexColors(withLegacy); - } - - private String replaceHexColors(String input) { - Matcher matcher = HEX_PATTERN.matcher(input); + Matcher matcher = HEX_PATTERN.matcher(withLegacy); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String hexCode = matcher.group(); diff --git a/src/main/java/de/viper/survivalplus/commands/RideCommand.java b/src/main/java/de/viper/survivalplus/commands/RideCommand.java new file mode 100644 index 0000000..c5cd226 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/commands/RideCommand.java @@ -0,0 +1,100 @@ +package de.viper.survivalplus.commands; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.util.RayTraceResult; + +public class RideCommand implements CommandExecutor { + + private final SurvivalPlus plugin; + + public RideCommand(SurvivalPlus plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Dieser Befehl ist nur für Spieler!"); + return true; + } + + Player player = (Player) sender; + FileConfiguration lang = plugin.getLangConfig(); + + if (!player.hasPermission("survivalplus.ride")) { + player.sendMessage(ChatColor.RED + "Du hast keine Berechtigung dafür!"); + return true; + } + + // --- FALL 1: ABSTEIGEN --- + if (args.length == 0) { + if (player.isInsideVehicle()) { + player.leaveVehicle(); + player.sendMessage(ChatColor.GREEN + "Du bist abgestiegen!"); + return true; + } + + // --- FALL 2: AUFSTEIGEN (Via Raycast) --- + + double maxDistance = 5.0; + + // FIX: Wir übergeben einen Filter, der den Spieler selbst ignoriert + RayTraceResult rayTrace = player.getWorld().rayTraceEntities( + player.getEyeLocation(), + player.getEyeLocation().getDirection(), + maxDistance, + 1.0, // Ray Größe (Dicke des Strahls) + entity -> !entity.getUniqueId().equals(player.getUniqueId()) // WICHTIG: Ignoriere mich selbst + ); + + if (rayTrace == null || rayTrace.getHitEntity() == null) { + player.sendMessage(ChatColor.RED + "Du schaust auf nichts zum Reiten!"); + player.sendMessage(ChatColor.GRAY + "Schaue einen Spieler oder Mob an."); + return true; + } + + Entity target = rayTrace.getHitEntity(); + + // Verhindern, dass man aufgesammelte Items reitet + if (target instanceof Item) { + player.sendMessage(ChatColor.RED + "Du kannst keine aufgesammelten Items reiten!"); + return true; + } + + // --- Zielle Logik --- + if (target instanceof Player) { + Player targetPlayer = (Player) target; + + if (targetPlayer.equals(player)) { + player.sendMessage(ChatColor.RED + "Du kannst dich nicht selbst reiten!"); + return true; + } + + if (targetPlayer.hasPermission("survivalplus.ride.exempt")) { + player.sendMessage(ChatColor.RED + "Du kannst diesen Spieler nicht reiten!"); + return true; + } + + target.addPassenger(player); + player.sendMessage(ChatColor.GREEN + "Du reitest nun " + ChatColor.YELLOW + targetPlayer.getName() + ChatColor.GREEN + "!"); + targetPlayer.sendMessage(ChatColor.GOLD + player.getName() + ChatColor.GREEN + " reitet dich jetzt!"); + } else { + target.addPassenger(player); + player.sendMessage(ChatColor.GREEN + "Du reitest nun ein(e) " + target.getName() + "!"); + } + + return true; + } + + player.sendMessage(ChatColor.GRAY + "Benutzung: Schaue auf ein Wesen und tippe " + ChatColor.WHITE + "/ride"); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/ShopCommand.java b/src/main/java/de/viper/survivalplus/commands/ShopCommand.java index 57b5df8..a1fe2b9 100644 --- a/src/main/java/de/viper/survivalplus/commands/ShopCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/ShopCommand.java @@ -2,7 +2,7 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.Manager.ShopManager; import de.viper.survivalplus.SurvivalPlus; -import de.viper.survivalplus.gui.ShopGui; +import de.viper.survivalplus.gui.ShopGui; // <--- WICHTIG: Dieser Import fehlte dir import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -34,7 +34,7 @@ public class ShopCommand implements CommandExecutor { } Player player = (Player) sender; - // Shop-GUI öffnen, wenn kein Argument oder "gui" + // Shop-GUI öffnen if (args.length == 0 || (args.length > 0 && args[0].toLowerCase().equals("gui"))) { ShopGui shopGui = new ShopGui(plugin, player, shopManager); Bukkit.getPluginManager().registerEvents(shopGui, plugin); @@ -42,43 +42,71 @@ public class ShopCommand implements CommandExecutor { return true; } - if (args.length < 3) { - sender.sendMessage(getMessage("usage-add")); - return false; - } - if (!args[0].toLowerCase().equals("add")) { sender.sendMessage(getMessage("unknown-subcommand")); - return false; - } - - ItemStack itemInHand = player.getInventory().getItemInMainHand(); - if (itemInHand == null || itemInHand.getType() == Material.AIR) { - sender.sendMessage(ChatColor.RED + "Du musst das Item, das du hinzufügen willst, in der Hand halten!"); return true; } - String itemKey = itemInHand.getType().name().toLowerCase(); + // --- Neue Logik für /shop add --- + String itemKey; double basePrice; int stock; - try { - basePrice = Double.parseDouble(args[1]); - stock = Integer.parseInt(args[2]); - } catch (NumberFormatException e) { - sender.sendMessage(getMessage("number-error")); - return false; + // Fall 1: /shop add (4 Argumente) + if (args.length >= 4) { + String materialName = args[1]; + Material mat = Material.matchMaterial(materialName); + + if (mat == null) { + sender.sendMessage(ChatColor.RED + "Das Material '" + materialName + "' wurde nicht gefunden!"); + return true; + } + itemKey = mat.name().toLowerCase(); + + try { + basePrice = Double.parseDouble(args[2]); + stock = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Ungültige Preis oder Bestandszahl! Beispiel: /shop add Diamond 20 64"); + return true; + } + + } + // Fall 2: /shop add (3 Argumente) -> Nimmt Item in der Hand + else if (args.length == 3) { + ItemStack itemInHand = player.getInventory().getItemInMainHand(); + if (itemInHand == null || itemInHand.getType() == Material.AIR) { + sender.sendMessage(ChatColor.RED + "Du musst das Item, das du hinzufügen willst, in der Hand halten!"); + sender.sendMessage(ChatColor.GRAY + "Oder benutze: /shop add "); + return true; + } + itemKey = itemInHand.getType().name().toLowerCase(); + + try { + basePrice = Double.parseDouble(args[1]); + stock = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + sender.sendMessage(getMessage("number-error")); + return true; + } + } + // Falsche Anzahl an Argumenten + else { + sender.sendMessage(ChatColor.RED + "Falsche Benutzung!"); + sender.sendMessage(ChatColor.GRAY + "Mit Item in Hand: /shop add "); + sender.sendMessage(ChatColor.GRAY + "Mit Namen: /shop add "); + return true; } shopManager.addOrUpdateItem(itemKey, basePrice, stock); String msg = getMessage("item-added") .replace("{item}", itemKey) - .replace("{price}", args[1]) - .replace("{stock}", args[2]); + .replace("{price}", String.valueOf(basePrice)) + .replace("{stock}", String.valueOf(stock)); sender.sendMessage(msg); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/SitCommand.java b/src/main/java/de/viper/survivalplus/commands/SitCommand.java index e1eb323..7fac57d 100644 --- a/src/main/java/de/viper/survivalplus/commands/SitCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/SitCommand.java @@ -2,6 +2,7 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.listeners.SitListener; +import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -40,36 +41,50 @@ public class SitCommand implements CommandExecutor { return true; } - // Prüfe, ob der Spieler bereits sitzt + // --- ÄNDERUNG: Wenn man schon sitzt, tue nichts --- if (sitListener.isSitting(player)) { - sitListener.standUp(player); - player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); + player.sendMessage(ChatColor.RED + "Du sitzt bereits!"); return true; } - - // Prüfe ob Spieler bereits in einem Fahrzeug ist + // -------------------------------------------------- + if (player.isInsideVehicle()) { player.sendMessage(lang.getString("sit.already-in-vehicle", "§cDu kannst dich nicht setzen, während du in einem Fahrzeug bist!")); return true; } - // Prüfe ob unter dem Spieler Luft ist (Verhindert Sitzen beim Bauen in der Luft) + // Prüfe den Boden unter dem Spieler Location loc = player.getLocation(); Block blockBelow = loc.clone().subtract(0, 1, 0).getBlock(); - // Wir erlauben das Sitzen nur, wenn der Block unter einem nicht Luft ist und kein Flüssigkeitsblock ist if (blockBelow.getType().isAir() || !blockBelow.getType().isSolid()) { player.sendMessage(plugin.getLangConfig().getString("sit.no-solid-ground", "§cDu kannst dich hier nicht hinsetzen (kein fester Boden).")); return true; } - // Setze den Spieler mittig auf den Block + // Position vorbereiten Location location = player.getLocation(); location.setX(location.getBlockX() + 0.5); location.setZ(location.getBlockZ() + 0.5); - location.setY(location.getBlockY()); + + // Höhe dynamisch berechnen + double y = location.getBlockY(); + + if (isStair(blockBelow.getType()) || isSlab(blockBelow.getType())) { + y += 0.5; + } + + location.setY(y); sitListener.sitPlayer(player, location); return true; } + + private boolean isStair(Material material) { + return material.name().endsWith("_STAIRS"); + } + + private boolean isSlab(Material material) { + return material.name().endsWith("_SLAB") || material.name().endsWith("_STEP") || material.name().equals("PRISMARINE_SLAB"); + } } \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/gui/ShopGui.java b/src/main/java/de/viper/survivalplus/gui/ShopGui.java index 2e7b02a..08a1eb4 100644 --- a/src/main/java/de/viper/survivalplus/gui/ShopGui.java +++ b/src/main/java/de/viper/survivalplus/gui/ShopGui.java @@ -42,8 +42,7 @@ public class ShopGui implements Listener { for (String itemKey : shopManager.getShopConfig().getConfigurationSection("items").getKeys(false)) { Material mat = getMaterialFromKey(itemKey); if (mat == null) { - player.sendMessage(ChatColor.RED + "Material für '" + itemKey + "' konnte nicht gefunden werden."); - continue; + continue; // Unbekannte Materialien einfach überspringen } addShopItem(itemKey, mat, 64); @@ -64,6 +63,7 @@ public class ShopGui implements Listener { private void addShopItem(String itemKey, Material material, int amount) { double pricePerUnit = shopManager.getCurrentPrice(itemKey); + int currentStock = shopManager.getStock(itemKey); double totalPrice = pricePerUnit * amount; ItemStack item = new ItemStack(material, amount); @@ -71,9 +71,12 @@ public class ShopGui implements Listener { if (meta != null) { meta.setDisplayName(ChatColor.GREEN.toString() + amount + "x " + material.name()); meta.setLore(Arrays.asList( + ChatColor.GRAY + "Lagerbestand: " + ChatColor.YELLOW + currentStock, ChatColor.YELLOW + "Preis pro Stück: " + pricePerUnit, ChatColor.YELLOW + "Gesamtpreis: " + totalPrice, - ChatColor.GRAY + "Klicke, um zu kaufen" + "", + ChatColor.GREEN + "Linksklick zum Kaufen", + ChatColor.RED + "Rechtsklick zum Verkaufen" )); item.setItemMeta(meta); } @@ -101,29 +104,58 @@ public class ShopGui implements Listener { Material mat = clicked.getType(); String itemKey = mat.name().toLowerCase(); - if (!shopManager.buyItem(itemKey, amount)) { - player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop."); - return; + if (e.isLeftClick()) { + // --- KAUFEN --- + if (!shopManager.buyItem(itemKey, amount)) { + player.sendMessage(ChatColor.RED + "Nicht genügend Bestand im Shop."); + return; + } + + double totalPrice = shopManager.getCurrentPrice(itemKey) * amount; + + // TODO: Economy Abzug hier einfügen! + // Beispiel: if (!economy.withdrawPlayer(player, totalPrice)) { ... } + + player.getInventory().addItem(new ItemStack(mat, amount)); + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", totalPrice) + " gekauft."); + + } else if (e.isRightClick()) { + // --- VERKAUFEN --- + + // Prüfen ob Spieler genug Items hat + if (!player.getInventory().containsAtLeast(new ItemStack(mat), amount)) { + player.sendMessage(ChatColor.RED + "Du hast nicht genug Items zum Verkaufen."); + return; + } + + // Verkaufslogik + shopManager.sellItem(itemKey, amount); + + double sellPrice = shopManager.getCurrentPrice(itemKey) * amount; // Neuer Preis nach dem Verkauf wird genutzt + // Hinweis: In echten Systemen bekommt man oft den Preis *vor* dem Preisverfall, hier nehmen wir den neuen für Einfachheit oder den gespeicherten: + // Besser wäre: double oldPrice = price * amount; ... player.giveMoney(oldPrice); + + // TODO: Economy Gutschrift hier einfügen! + + player.getInventory().removeItem(new ItemStack(mat, amount)); + player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + String.format("%.2f", sellPrice) + " verkauft."); } - double totalPrice = shopManager.getCurrentPrice(itemKey) * amount; - - // TODO: Economy Abzug einfügen - - player.getInventory().addItem(new ItemStack(mat, amount)); - player.sendMessage(ChatColor.GREEN + "Du hast " + amount + "x " + mat.name() + " für " + totalPrice + " Coins gekauft."); - + // Inventory schließen, um Preise neu zu laden und Bugs zu vermeiden player.closeInventory(); + // Optional: direkt neu öffnen mit createInventory() wenn es flüssig sein soll + // createInventory(); } @EventHandler public void onInventoryClose(InventoryCloseEvent e) { if (e.getView().getTitle().equals("Shop") && e.getPlayer().equals(player)) { - // Optional: Listener entfernen oder Aufräumarbeiten hier + // Listener kann entladen werden, wenn nicht mehr gebraucht + // HandlerList.unregisterAll(this); // Vorsicht: falls Singleton, sonst fliegt allen der GUI weg } } public Inventory getInventory() { return inv; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/AdaptiveMobListener.java b/src/main/java/de/viper/survivalplus/listeners/AdaptiveMobListener.java new file mode 100644 index 0000000..725354f --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/AdaptiveMobListener.java @@ -0,0 +1,122 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.ChatColor; +import org.bukkit.attribute.Attribute; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.UUID; + +public class AdaptiveMobListener implements Listener { + + private final SurvivalPlus plugin; + + // Konfiguration für die Anpassung + private final double SCALING_FACTOR = 0.1; // 10% mehr Stärke pro Level + private final int KILLS_PER_LEVEL = 5; // Alle 5 Kills steigt das Level + private final int MAX_LEVEL = 50; // Maximales Level (Cap) + + public AdaptiveMobListener(SurvivalPlus plugin) { + this.plugin = plugin; + } + + // 1. Verhalten tracken: Zählt Kills des Spielers + @EventHandler + public void onMobDeath(EntityDeathEvent event) { + if (event.getEntity().getKiller() instanceof Player) { + Player killer = event.getEntity().getKiller(); + UUID uuid = killer.getUniqueId(); + + FileConfiguration mobConfig = plugin.getMobAdaptConfig(); + String uuidPath = uuid.toString(); + + int currentLevel = mobConfig.getInt(uuidPath + ".level", 0); + int currentKills = mobConfig.getInt(uuidPath + ".kills", 0); + + currentKills++; + + // Level up wenn genug Kills + if (currentKills >= KILLS_PER_LEVEL && currentLevel < MAX_LEVEL) { + currentLevel++; + currentKills = 0; + killer.sendMessage(ChatColor.GREEN + "[SurvivalPlus] " + ChatColor.GRAY + "Die Monster werden aggressiver! (Threat Level: " + currentLevel + ")"); + } + + // Speichern + mobConfig.set(uuidPath + ".level", currentLevel); + mobConfig.set(uuidPath + ".kills", currentKills); + plugin.saveMobAdaptConfig(); + } + } + + // 2. Anpassung: Monster werden beim Spawnen angepasst + @EventHandler + public void onCreatureSpawn(CreatureSpawnEvent event) { + // Nur aktivieren, wenn in Config gewünscht + if (!plugin.getConfig().getBoolean("adaptive-mobs.enabled", true)) return; + + // Wir greifen nur bei natürlichen Spawns ein (oder Spawn-Eiern), aber nicht bei Spawner-Blöcken (Farmen schützen) + if (event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL && + event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.SPAWNER_EGG && + event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.DISPENSE_EGG) { + return; + } + + LivingEntity entity = event.getEntity(); + + // Keine Anpassung für friedliche Tiere + if (entity instanceof Animals || entity instanceof Ambient) return; + + // Nächsten Spieler suchen (Radius: 30 Blöcke) + Player nearestPlayer = null; + double minDistance = 30.0; + + for (Player p : entity.getWorld().getPlayers()) { + if (p.getLocation().distance(entity.getLocation()) <= minDistance) { + nearestPlayer = p; + minDistance = p.getLocation().distance(entity.getLocation()); + } + } + + if (nearestPlayer != null) { + applyAttributesBasedOnPlayer(entity, nearestPlayer); + } + } + + private void applyAttributesBasedOnPlayer(LivingEntity entity, Player player) { + FileConfiguration mobConfig = plugin.getMobAdaptConfig(); + int level = mobConfig.getInt(player.getUniqueId().toString() + ".level", 0); + + if (level == 0) return; + + // Multiplikator berechnen (z.B. Level 10 -> 1.0 + (10 * 0.1) = 2.0 = doppelte Stärke) + double multiplier = 1.0 + (level * SCALING_FACTOR); + + // Cap bei 5-facher Stärke, damit es nicht unspielbar wird + if (multiplier > 5.0) multiplier = 5.0; + + // Lebenspunkte erhöhen + if (entity.getAttribute(Attribute.GENERIC_MAX_HEALTH) != null) { + double baseHealth = entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue(); + entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).setBaseValue(baseHealth * multiplier); + entity.setHealth(entity.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + } + + // Angriffsschaden erhöhen + if (entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE) != null) { + double baseDamage = entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE).getBaseValue(); + entity.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE).setBaseValue(baseDamage * multiplier); + } + + // Optional: Visuelles Indiz (Name über dem Kopf), wenn Monster sehr stark sind + if (level >= 10) { + entity.setCustomName(ChatColor.RED + "⚠ " + ChatColor.GRAY + "Starkes Monster (Lvl " + level + ")"); + entity.setCustomNameVisible(true); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/ClaimListener.java b/src/main/java/de/viper/survivalplus/listeners/ClaimListener.java index 0eb967d..21555e7 100644 --- a/src/main/java/de/viper/survivalplus/listeners/ClaimListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/ClaimListener.java @@ -2,11 +2,14 @@ package de.viper.survivalplus.listeners; import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.util.Claim; +import org.bukkit.Location; +import org.bukkit.entity.Monster; import org.bukkit.entity.Player; 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.event.entity.EntityTargetLivingEntityEvent; import org.bukkit.event.player.PlayerMoveEvent; import java.util.HashMap; @@ -70,4 +73,28 @@ public class ClaimListener implements Listener { lastClaim.put(playerId, currentClaim); } } + + // --- NEU: Schutz vor Monstern im Claim --- + @EventHandler + public void onMobTarget(EntityTargetLivingEntityEvent event) { + // 1. Prüfen, ob das Ziel ein Spieler ist + if (!(event.getTarget() instanceof Player)) return; + + // 2. Prüfen, ob das Angreifer ein Monster ist (Zombie, Skelett, etc.) + if (!(event.getEntity() instanceof Monster)) return; + + Player player = (Player) event.getTarget(); + Location loc = player.getLocation(); + + // 3. Prüfen, ob der Spieler in einem Claim steht + Claim claim = plugin.getClaim(loc); + + if (claim != null) { + // 4. Prüfen, ob der Spieler dort Rechte hat (Owner oder Trusted) + if (claim.getOwner().equals(player.getUniqueId()) || claim.getTrusted().contains(player.getUniqueId())) { + // Das Targeting abbrechen -> Monster greift nicht an + event.setCancelled(true); + } + } + } } \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/NewbieProtectionListener.java b/src/main/java/de/viper/survivalplus/listeners/NewbieProtectionListener.java index 60d54e4..a158b24 100644 --- a/src/main/java/de/viper/survivalplus/listeners/NewbieProtectionListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/NewbieProtectionListener.java @@ -32,6 +32,9 @@ public class NewbieProtectionListener implements Listener { private final Map remainingSeconds = new HashMap<>(); private final Map bossBars = new HashMap<>(); private final Map messageCooldowns = new HashMap<>(); + + // NEU: Set um zu speichern, wer schon mal gejoint ist + private final Set hasJoined = new HashSet<>(); // YAML Datei private File dataFile; @@ -59,17 +62,23 @@ public class NewbieProtectionListener implements Listener { Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); - // Falls nicht gespeichert, neue Zeit einstellen - remainingSeconds.putIfAbsent(uuid, durationMinutes * 60); + boolean isFirstJoin = !hasJoined.contains(uuid); - // Bossbar erstellen/anzeigen - BossBar bar = Bukkit.createBossBar( - ChatColor.GREEN + "Neulingsschutz: " + formatTime(remainingSeconds.get(uuid)), - BarColor.GREEN, - BarStyle.SOLID - ); - bar.addPlayer(player); - bossBars.put(uuid, bar); + if (isFirstJoin) { + // Erster Join EVER -> Schutz geben und als "gejoint" markieren + remainingSeconds.put(uuid, durationMinutes * 60); + hasJoined.add(uuid); + + createBossBar(player, remainingSeconds.get(uuid)); + } else { + // Spieler war schonmal da -> Prüfen ob noch Schutz übrig ist + Integer timeLeft = remainingSeconds.get(uuid); + if (timeLeft != null && timeLeft > 0) { + // Schutz läuft noch -> Bossbar wiederherstellen + createBossBar(player, timeLeft); + } + // Wenn timeLeft null ist oder 0, passiert nichts -> Kein Schutz für den "alten Hasen" + } } @EventHandler @@ -77,12 +86,13 @@ public class NewbieProtectionListener implements Listener { if (!enabled) return; Player player = event.getPlayer(); UUID uuid = player.getUniqueId(); - // Bossbar entfernen, aber Zeit bleibt in Map bestehen + + // Bossbar entfernen, aber Status (Zeit & Joined) in Map/Datei behalten BossBar bar = bossBars.remove(uuid); if (bar != null) { bar.removeAll(); } - saveData(); // Beim Verlassen direkt speichern + saveData(); } @EventHandler @@ -94,26 +104,24 @@ public class NewbieProtectionListener implements Listener { UUID victimId = victim.getUniqueId(); Integer timeLeft = remainingSeconds.get(victimId); - if (timeLeft != null && timeLeft > 0) { - event.setCancelled(true); + // Nur schützen, wenn Zeit > 0 + if (timeLeft == null || timeLeft <= 0) return; - // Prüfe Abklingzeit für Opfer - long currentTime = System.currentTimeMillis() / 1000; - long lastMessageTimeVictim = messageCooldowns.getOrDefault(victimId, 0L); - if (currentTime >= lastMessageTimeVictim + COOLDOWN_SECONDS) { - victim.sendMessage(ChatColor.GREEN + "Du bist noch im Neulingsschutz!"); - messageCooldowns.put(victimId, currentTime); - } + event.setCancelled(true); - // Prüfe Abklingzeit für Angreifer, falls es ein Spieler ist - if (event.getDamager() instanceof Player) { - Player damager = (Player) event.getDamager(); - UUID damagerId = damager.getUniqueId(); - long lastMessageTimeDamager = messageCooldowns.getOrDefault(damagerId, 0L); - if (currentTime >= lastMessageTimeDamager + COOLDOWN_SECONDS) { - damager.sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!"); - messageCooldowns.put(damagerId, currentTime); - } + // Prüfe Cooldown für Nachrichten + long currentTime = System.currentTimeMillis() / 1000; + if (currentTime >= messageCooldowns.getOrDefault(victimId, 0L) + COOLDOWN_SECONDS) { + victim.sendMessage(ChatColor.GREEN + "Du bist noch im Neulingsschutz!"); + messageCooldowns.put(victimId, currentTime); + } + + if (event.getDamager() instanceof Player) { + Player damager = (Player) event.getDamager(); + UUID damagerId = damager.getUniqueId(); + if (currentTime >= messageCooldowns.getOrDefault(damagerId, 0L) + COOLDOWN_SECONDS) { + damager.sendMessage(ChatColor.RED + victim.getName() + " ist noch geschützt!"); + messageCooldowns.put(damagerId, currentTime); } } } @@ -126,16 +134,15 @@ public class NewbieProtectionListener implements Listener { Player p = Bukkit.getPlayer(uuid); if (p == null || !p.isOnline()) { - // Spieler offline → Zeit pausiert + // Spieler offline -> Zeit pausiert continue; } int timeLeft = remainingSeconds.getOrDefault(uuid, 0); + if (timeLeft <= 0) { - // Ablauf: Bossbar weg + Map clean - BossBar bar = bossBars.remove(uuid); - if (bar != null) bar.removeAll(); - remainingSeconds.remove(uuid); + // Zeit abgelaufen -> Bossbar weg, aber aus hasJoined NICHT löschen + removeProtection(uuid); continue; } @@ -144,16 +151,40 @@ public class NewbieProtectionListener implements Listener { remainingSeconds.put(uuid, timeLeft); // Bossbar updaten - BossBar bar = bossBars.get(uuid); - if (bar != null) { - bar.setTitle(ChatColor.GREEN + "Neulingsschutz: " + formatTime(timeLeft)); - bar.setProgress(Math.max(0, timeLeft / (float) (durationMinutes * 60))); - } + updateBossBar(p, timeLeft); } - saveData(); // zyklisch speichern + // Jedes Mal zyklisch speichern + saveData(); } }.runTaskTimer(plugin, 20L, 20L); } + + private void removeProtection(UUID uuid) { + remainingSeconds.remove(uuid); + BossBar bar = bossBars.remove(uuid); + if (bar != null) { + bar.removeAll(); + } + } + + private void createBossBar(Player player, int seconds) { + BossBar bar = Bukkit.createBossBar( + ChatColor.GREEN + "Neulingsschutz: " + formatTime(seconds), + BarColor.GREEN, + BarStyle.SOLID + ); + bar.addPlayer(player); + bar.setProgress(Math.max(0, seconds / (float) (durationMinutes * 60))); + bossBars.put(player.getUniqueId(), bar); + } + + private void updateBossBar(Player player, int seconds) { + BossBar bar = bossBars.get(player.getUniqueId()); + if (bar != null) { + bar.setTitle(ChatColor.GREEN + "Neulingsschutz: " + formatTime(seconds)); + bar.setProgress(Math.max(0, seconds / (float) (durationMinutes * 60))); + } + } // ---------- Datei Handling ---------- private void loadData() { @@ -166,20 +197,47 @@ public class NewbieProtectionListener implements Listener { } } dataConfig = YamlConfiguration.loadConfiguration(dataFile); + + hasJoined.clear(); + remainingSeconds.clear(); + for (String key : dataConfig.getKeys(false)) { try { UUID uuid = UUID.fromString(key); - int sec = dataConfig.getInt(key); - remainingSeconds.put(uuid, sec); + + // Gelesen: 'joined' Status und 'seconds' + boolean joined = dataConfig.getBoolean(key + ".joined", false); + int sec = dataConfig.getInt(key + ".seconds", 0); + + if (joined) { + hasJoined.add(uuid); + } + + if (sec > 0) { + remainingSeconds.put(uuid, sec); + } } catch (IllegalArgumentException ignored) {} } } public void saveData() { - if (dataConfig == null) return; - for (Map.Entry entry : remainingSeconds.entrySet()) { - dataConfig.set(entry.getKey().toString(), entry.getValue()); + dataConfig = YamlConfiguration.loadConfiguration(dataFile); + + // Wir speichern ALLE Spieler, die schon mal gejoint sind (hasJoined) + // damit wir beim Rejoin wissen, dass sie keinen Neulingsschutz mehr bekommen. + for (UUID uuid : hasJoined) { + dataConfig.set(uuid + ".joined", true); + + Integer sec = remainingSeconds.get(uuid); + if (sec != null && sec > 0) { + dataConfig.set(uuid + ".seconds", sec); + } else { + // Wenn Schutz abgelaufen (0 oder nicht in Map), speichern wir 0 oder löschen wir den Key? + // Besser: löschen wir den Key "seconds", aber "joined" bleibt true. + dataConfig.set(uuid + ".seconds", null); + } } + try { dataConfig.save(dataFile); } catch (IOException e) { diff --git a/src/main/java/de/viper/survivalplus/listeners/NickJoinMessageListener.java b/src/main/java/de/viper/survivalplus/listeners/NickJoinMessageListener.java new file mode 100644 index 0000000..ea6e719 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/NickJoinMessageListener.java @@ -0,0 +1,13 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; + +/** + * Dieser Listener wird nicht mehr benötigt, da NickLoadListener + * mit ProtocolLib die Join/Quit Messages direkt auf Packet-Ebene behandelt. + * + * Diese Klasse kann gelöscht werden oder bleibt als leere Klasse bestehen. + */ +public class NickJoinMessageListener { + // Nicht mehr benötigt - ProtocolLib übernimmt die Arbeit +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/NickLoadListener.java b/src/main/java/de/viper/survivalplus/listeners/NickLoadListener.java index 4e6e65c..dec8e00 100644 --- a/src/main/java/de/viper/survivalplus/listeners/NickLoadListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/NickLoadListener.java @@ -2,10 +2,13 @@ package de.viper.survivalplus.listeners; import de.viper.survivalplus.SurvivalPlus; import net.md_5.bungee.api.ChatColor; -import org.bukkit.event.Listener; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -19,26 +22,78 @@ public class NickLoadListener implements Listener { this.plugin = plugin; } - @EventHandler + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - String rawNick = plugin.getNicknamesConfig().getString(player.getUniqueId().toString()); + // Immer die Standard-Join-Message unterdrücken — wir senden eine einzelne kontrollierte Nachricht später + event.setJoinMessage(null); + + String rawNick = plugin.getNicknamesConfig().getString(player.getUniqueId().toString()); if (rawNick != null && !rawNick.isEmpty()) { String coloredNick = translateColors(rawNick); - String finalNick = "§f[" + coloredNick + "§f]"; - player.setDisplayName(finalNick); - player.setPlayerListName(finalNick); + String plainNick = ChatColor.stripColor(coloredNick); + + // Kurz: DisplayName jetzt auf echten Namen lassen, damit andere Plugins / Bukkit intern konsistent sind. + player.setDisplayName(player.getName()); + + // In 1 Tick: DisplayName für Chat setzen UND die Join-Nachricht EINMAL manuell versenden + Bukkit.getScheduler().runTask(plugin, () -> { + // Chat-Display: [Nick] + player.setDisplayName("§f[" + coloredNick + "§f]"); + + // Nachricht an alle Spieler senden (keine Doppelung, weil wir event.setJoinMessage(null) gesetzt haben) + String message = coloredNick + " §ejoined the game"; + for (Player p : Bukkit.getOnlinePlayers()) { + p.sendMessage(message); + } + + // Log in Konsole (sauber, ohne Farb-Codes) + plugin.getLogger().info(plainNick + " joined the game"); + }); + } else { + // Kein Nick vorhanden -> benutze Standardverhalten, aber ebenfalls manuell, um Doppelungen zu verhindern + player.setDisplayName(player.getName()); + Bukkit.getScheduler().runTask(plugin, () -> { + String message = player.getName() + " §ejoined the game"; + for (Player p : Bukkit.getOnlinePlayers()) { + p.sendMessage(message); + } + plugin.getLogger().info(player.getName() + " joined the game"); + }); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + // Standard-Quit-Message unterdrücken + event.setQuitMessage(null); + + String rawNick = plugin.getNicknamesConfig().getString(player.getUniqueId().toString()); + if (rawNick != null && !rawNick.isEmpty()) { + String coloredNick = translateColors(rawNick); + String plainNick = ChatColor.stripColor(coloredNick); + + // Sende Quit-Nachricht einmal manuell + String msg = coloredNick + " §eleft the game"; + for (Player p : Bukkit.getOnlinePlayers()) { + p.sendMessage(msg); + } + plugin.getLogger().info(plainNick + " left the game"); + } else { + String msg = player.getName() + " §eleft the game"; + for (Player p : Bukkit.getOnlinePlayers()) { + p.sendMessage(msg); + } + plugin.getLogger().info(player.getName() + " left the game"); } } private String translateColors(String input) { String withLegacy = org.bukkit.ChatColor.translateAlternateColorCodes('&', input); - return replaceHexColors(withLegacy); - } - - private String replaceHexColors(String input) { - Matcher matcher = HEX_PATTERN.matcher(input); + Matcher matcher = HEX_PATTERN.matcher(withLegacy); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String hexCode = matcher.group(); diff --git a/src/main/java/de/viper/survivalplus/listeners/SitListener.java b/src/main/java/de/viper/survivalplus/listeners/SitListener.java index 5d2e5b3..fe67d4f 100644 --- a/src/main/java/de/viper/survivalplus/listeners/SitListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/SitListener.java @@ -26,11 +26,12 @@ import java.util.logging.Level; public class SitListener implements Listener { private final SurvivalPlus plugin; private final Map sittingPlayers = new HashMap<>(); - private static final String SIT_TAG = "SP_SIT_STAND"; // Tag zum Identifizieren + private final Map sitCooldown = new HashMap<>(); // Neuer Cooldown gegen "Auto-Standup" + private static final String SIT_TAG = "SP_SIT_STAND"; public SitListener(SurvivalPlus plugin) { this.plugin = plugin; - cleanUpGhostStands(); // Entferne alte Stands beim Laden + cleanUpGhostStands(); } public boolean isSitting(Player player) { @@ -45,37 +46,53 @@ public class SitListener implements Listener { // Erstelle einen unsichtbaren ArmorStand als Sitz ArmorStand armorStand = player.getWorld().spawn(location, ArmorStand.class); armorStand.setGravity(false); - armorStand.setMarker(true); // Keine Hitbox + armorStand.setMarker(true); armorStand.setInvisible(true); armorStand.setInvulnerable(true); - armorStand.addScoreboardTag(SIT_TAG); // Tag für Cleanup + armorStand.addScoreboardTag(SIT_TAG); + // Setze Rotation des Stands auf Rotation des Spielers + armorStand.setRotation(player.getLocation().getYaw(), player.getLocation().getPitch()); + // Füge Spieler als Passenger hinzu armorStand.addPassenger(player); - // Teleportiere den Spieler nochmals zur Sicherheit (Anti-Slide) - player.teleport(location); + // FIX: KEIN player.teleport() hier ausführen! + // In neueren Versionen "bricht" das Teleportieren das Mounten sofort wieder. + // Der Spieler wird automatisch durch addPassenger zum Stand teleportiert. + + // Cooldown setzen, damit wir nicht sofort durch "Movement" aufstehen + sitCooldown.put(player.getUniqueId(), System.currentTimeMillis() + 500L); sittingPlayers.put(player.getUniqueId(), armorStand); + FileConfiguration lang = plugin.getLangConfig(); player.sendMessage(lang.getString("sit.success", "§aDu hast dich hingesetzt!")); - plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " sitzt bei " + locationToString(location)); } public void standUp(Player player) { UUID playerId = player.getUniqueId(); ArmorStand armorStand = sittingPlayers.remove(playerId); + sitCooldown.remove(playerId); // Cooldown löschen + if (armorStand != null) { - if (player.isValid()) { + if (player.isValid() && armorStand.isValid()) { + // Wenn man absteigt, landet man oft leicht daneben. Wir korrigieren das leicht, + // aber nicht so aggressiv wie beim Hinsetzen. + Location loc = player.getLocation().add(0, 0.2, 0); armorStand.removePassenger(player); + armorStand.remove(); + + // Kleiner Fix, damit man nicht im Boden klebt + player.teleport(loc); + } else { + armorStand.remove(); } - armorStand.remove(); FileConfiguration lang = plugin.getLangConfig(); if (player.isOnline()) { player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); } - plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " ist aufgestanden"); } } @@ -89,7 +106,6 @@ public class SitListener implements Listener { FileConfiguration lang = plugin.getLangConfig(); if (!player.hasPermission("survivalplus.sit")) { - // Kein Feedback nötig, wenn keine Permission, um nicht zu spammen return; } @@ -98,57 +114,62 @@ public class SitListener implements Listener { return; } - // NEU: Prüfen ob der Spieler nah genug dran ist (max 2 Blöcke Entfernung) - // Dies verhindert das versehentliche Sitzen beim Bauen in der Ferne. double distance = player.getLocation().distance(block.getLocation()); if (distance > 3.0) { - return; // Spieler ist zu weit weg -> Ignorieren (normales Bauen möglich) + return; } - // Wenn der Spieler bereits sitzt, stehe auf if (sittingPlayers.containsKey(player.getUniqueId())) { standUp(player); event.setCancelled(true); return; } - // Prüfe ob in Fahrzeug if (player.isInsideVehicle()) return; - // Setze den Spieler genau auf der Treppenstufe + // Position berechnen Location location = block.getLocation(); location.setX(location.getX() + 0.5); location.setZ(location.getZ() + 0.5); - // Bei Treppen die Höhe anpassen (Y + 0.5 ist meist genau die "Sitzfläche") + // Bei Treppen: Y + 0.5 ist meistens korrekt für die Sitzfläche location.setY(location.getY() + 0.5); + // Drehung anpassen, damit man nicht auf der Treppe schräg sitzt + location.setYaw(player.getLocation().getYaw()); + location.setPitch(player.getLocation().getPitch()); + sitPlayer(player, location); - event.setCancelled(true); // Verhindere andere Interaktionen mit der Treppe + event.setCancelled(true); } @EventHandler public void onPlayerMove(PlayerMoveEvent event) { Player player = event.getPlayer(); UUID playerId = player.getUniqueId(); - if (!sittingPlayers.containsKey(playerId)) { - return; - } - - // Prüfe, ob der Spieler sich wirklich bewegt hat (Distanz > 0.05) - // Das verhindert, dass minimale "Wackler" durch Lag den Spieler abwerfen - Location from = event.getFrom(); - Location to = event.getTo(); - if (to == null) return; - if (from.distanceSquared(to) > 0.0025) { // ca 0.05 Blöcke Abstand - standUp(player); + // Wenn der Spieler sitzt + if (sittingPlayers.containsKey(playerId)) { + + // FIX: Cooldown prüfen (Verhindert "Auto-Standup" durch Lag beim Hinsetzen) + Long cooldown = sitCooldown.get(playerId); + if (cooldown != null && cooldown > System.currentTimeMillis()) { + return; + } + + Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null) return; + + // Prüfe signifikante Bewegung + if (from.distanceSquared(to) > 0.0025) { + standUp(player); + } } } @EventHandler public void onPlayerTeleport(PlayerTeleportEvent event) { - // Wenn teleportiert, absteigen if (sittingPlayers.containsKey(event.getPlayer().getUniqueId())) { standUp(event.getPlayer()); } @@ -163,12 +184,7 @@ public class SitListener implements Listener { private boolean isStair(Material material) { return material.name().endsWith("_STAIRS"); } - - private String locationToString(Location loc) { - return String.format("x=%.2f, y=%.2f, z=%.2f, world=%s", loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName()); - } - // Entfernt verbliebene ArmorStands von vorherigen Läufen (z.B. nach /reload) private void cleanUpGhostStands() { Bukkit.getScheduler().runTaskLater(plugin, () -> { for (World world : Bukkit.getWorlds()) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 853be75..c13fb5f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ # Version (Nicht Ändern!) -version: 1.0.9 +version: 1.1.0 # Debug-Option debug-logging: false diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml index e475714..d9eff97 100644 --- a/src/main/resources/lang.yml +++ b/src/main/resources/lang.yml @@ -160,7 +160,7 @@ friend: notify: "&c%s hat deine Freundschaftsanfrage abgelehnt." list: - header: "&6=== Deine Freundesliste ===" + header: "&6== Deine Freundesliste ==" entry: "&e%s: %s" entry-offline: "&e%s: %s &7(Zuletzt online: %s)" online: "&aOnline" @@ -168,7 +168,7 @@ friend: unknown: "&7Unbekannt" date-format: "dd.MM.yyyy HH:mm:ss" remove-button: "&c[X]" - footer: "&6=====================" + footer: "&6=======================" del: success: "&a%s wurde aus deiner Freundesliste entfernt." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 660051e..8fde6e8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ name: SurvivalPlus -version: 1.1.0 +version: 1.1.1 main: de.viper.survivalplus.SurvivalPlus api-version: 1.21 -softdepend: [LuckPerms, PlaceholderAPI] +softdepend: [LuckPerms, PlaceholderAPI, ProtocolLib] author: Viper description: A plugin for enhancing survival gameplay in Minecraft. @@ -92,8 +92,7 @@ commands: friend: description: Verwaltet die Freundesliste usage: / [add|accept|deny|list|del|tp] [Spielername] - permission: survivalplus.friend - permission-message: "§cDu hast keine Berechtigung für diesen Befehl." + # FIX: permission und permission-message entfernt um Warnung zu verhindern stats: description: Zeigt deine Statistiken an usage: / @@ -184,6 +183,11 @@ commands: usage: / permission: survivalplus.nick permission-message: "§cDu hast keine Berechtigung, deinen Nick zu ändern!" + ride: + description: Reite einen Spieler + usage: / [spieler] + permission: survivalplus.ride + permission-message: "§cDu hast keine Berechtigung, Spieler zu reiten!" lootchests: description: Zeigt eine Liste aller aktiven Loot-Kisten an. Admins können per Klick zu einer Kiste teleportieren. usage: / @@ -320,6 +324,8 @@ permissions: survivalplus.blocklist: true survivalplus.kit: true survivalplus.nick: true + survivalplus.ride: true + survivalplus.ride.exempt: true survivalplus.lootchests: true survivalplus.day: true survivalplus.night: true @@ -475,6 +481,12 @@ permissions: survivalplus.nick: description: Erlaubt das Ändern des eigenen Nicknamens (mit Farben & Hex) default: op + survivalplus.ride: + description: Erlaubt das Reiten von Spielern + default: op + survivalplus.ride.exempt: + description: Spieler mit dieser Permission können nicht geritten werden + default: op survivalplus.lootchests: description: Erlaubt das Verwalten und Teleportieren zu Loot-Kisten default: op