From 6f8f7c2d393502070e3e898d5b9ef4fd3644f57a Mon Sep 17 00:00:00 2001 From: M_Viper Date: Sun, 3 Aug 2025 15:46:12 +0000 Subject: [PATCH] Dateien nach "src/main/java/de/viper/survivalplus/listeners" hochladen --- .../survivalplus/listeners/AFKListener.java | 120 +++++++++ .../listeners/ArmorStandDestroyListener.java | 27 ++ .../listeners/BackpackListener.java | 111 ++++++++ .../listeners/DebugArmorStandListener.java | 46 ++++ .../survivalplus/listeners/GraveListener.java | 174 ++++++++++++ .../listeners/InventoryClickListener.java | 69 +++++ .../survivalplus/listeners/LoginListener.java | 53 ++++ .../listeners/MobCapListener.java | 181 +++++++++++++ .../listeners/MobLeashLimitListener.java | 253 ++++++++++++++++++ .../listeners/OreAlarmListener.java | 139 ++++++++++ .../listeners/SignColorListener.java | 85 ++++++ .../survivalplus/listeners/SitListener.java | 128 +++++++++ .../survivalplus/listeners/SleepListener.java | 68 +++++ .../listeners/SpawnProtectionListener.java | 75 ++++++ .../survivalplus/listeners/StatsListener.java | 81 ++++++ 15 files changed, 1610 insertions(+) create mode 100644 src/main/java/de/viper/survivalplus/listeners/AFKListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/ArmorStandDestroyListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/BackpackListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/DebugArmorStandListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/GraveListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/InventoryClickListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/LoginListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/MobCapListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/MobLeashLimitListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/OreAlarmListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/SignColorListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/SitListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/SleepListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/SpawnProtectionListener.java create mode 100644 src/main/java/de/viper/survivalplus/listeners/StatsListener.java diff --git a/src/main/java/de/viper/survivalplus/listeners/AFKListener.java b/src/main/java/de/viper/survivalplus/listeners/AFKListener.java new file mode 100644 index 0000000..47f8b01 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/AFKListener.java @@ -0,0 +1,120 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import de.viper.survivalplus.tasks.AFKManager; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.event.player.*; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.scheduler.BukkitRunnable; + +public class AFKListener implements Listener { + + private final SurvivalPlus plugin; + private AFKManager afkManager; + private boolean afkEnabled; + private FileConfiguration config; + + private BukkitRunnable afkTask; + + public AFKListener(SurvivalPlus plugin) { + this.plugin = plugin; + this.config = plugin.getConfig(); + loadSettings(); + + if (afkEnabled) { + startTask(); + } + } + + private void loadSettings() { + this.afkEnabled = config.getBoolean("afk.enabled", true); + + long afkAfter = config.getLong("afk.afk-after-seconds", 300); + long kickAfter = config.getLong("afk.kick-after-seconds", 0); + this.afkManager = new AFKManager(plugin, afkAfter, kickAfter); + } + + private void startTask() { + if (afkTask != null) { + afkTask.cancel(); + } + afkTask = new BukkitRunnable() { + @Override + public void run() { + for (Player player : Bukkit.getOnlinePlayers()) { + afkManager.checkAFKStatus(player); + } + } + }; + afkTask.runTaskTimer(plugin, 20L, 100L); + } + + private void handleActivity(Player player) { + if (!afkEnabled) return; + + afkManager.updateActivity(player); + } + + @org.bukkit.event.EventHandler + public void onMove(PlayerMoveEvent event) { + if (!event.getFrom().toVector().equals(event.getTo().toVector())) { + handleActivity(event.getPlayer()); + } + } + + @org.bukkit.event.EventHandler + public void onChat(AsyncPlayerChatEvent event) { + handleActivity(event.getPlayer()); + } + + @org.bukkit.event.EventHandler + public void onInteract(PlayerInteractEvent event) { + handleActivity(event.getPlayer()); + } + + @org.bukkit.event.EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getWhoClicked() instanceof Player player) { + handleActivity(player); + } + } + + @org.bukkit.event.EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + handleActivity(event.getPlayer()); + } + + @org.bukkit.event.EventHandler + public void onBlockBreak(BlockBreakEvent event) { + handleActivity(event.getPlayer()); + } + + @org.bukkit.event.EventHandler + public void onJoin(PlayerJoinEvent event) { + afkManager.updateActivity(event.getPlayer()); + } + + @org.bukkit.event.EventHandler + public void onQuit(PlayerQuitEvent event) { + afkManager.reset(event.getPlayer()); + } + + public void reloadConfig(FileConfiguration config) { + this.config = config; + loadSettings(); + + if (afkTask != null) { + afkTask.cancel(); + afkTask = null; + } + + if (afkEnabled) { + startTask(); + } + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/ArmorStandDestroyListener.java b/src/main/java/de/viper/survivalplus/listeners/ArmorStandDestroyListener.java new file mode 100644 index 0000000..ec8939c --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/ArmorStandDestroyListener.java @@ -0,0 +1,27 @@ +package de.viper.survivalplus.listeners; + +import org.bukkit.ChatColor; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; + +public class ArmorStandDestroyListener implements Listener { + + @EventHandler + public void onArmorStandInteract(PlayerInteractAtEntityEvent event) { + if (!(event.getRightClicked() instanceof ArmorStand armorStand)) return; + + Player player = event.getPlayer(); + + if (!player.hasPermission("survivalplus.armorstand.destroy")) { + player.sendMessage(ChatColor.RED + "Du hast keine Berechtigung, diesen ArmorStand zu entfernen."); + return; + } + + armorStand.remove(); + player.sendMessage(ChatColor.YELLOW + "ArmorStand wurde erfolgreich entfernt."); + event.setCancelled(true); + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/BackpackListener.java b/src/main/java/de/viper/survivalplus/listeners/BackpackListener.java new file mode 100644 index 0000000..a051124 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/BackpackListener.java @@ -0,0 +1,111 @@ +package de.viper.survivalplus.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Logger; + +public class BackpackListener implements Listener { + + private final FileConfiguration backpackConfig; + private final FileConfiguration langConfig; + private final Logger logger; + private final File backpackFile; + + public BackpackListener(FileConfiguration backpackConfig, FileConfiguration langConfig, Logger logger, File backpackFile) { + this.backpackConfig = backpackConfig; + this.langConfig = langConfig; + this.logger = logger; + this.backpackFile = backpackFile; + } + + @EventHandler + public void onRightClick(PlayerInteractEvent event) { + if (!event.hasItem() || !event.getAction().toString().contains("RIGHT")) return; + + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + + if (item == null || item.getType() != Material.CHEST) { + logger.fine("Kein Rucksack-Item: " + (item != null ? item.getType() : "null")); + return; + } + if (!item.hasItemMeta()) { + logger.fine("Item hat kein ItemMeta: " + item.getType()); + return; + } + + ItemMeta meta = item.getItemMeta(); + if (!meta.hasDisplayName()) { + logger.fine("Item hat keinen Display-Namen: " + item.getType()); + return; + } + + String expectedName = ChatColor.translateAlternateColorCodes('&', langConfig.getString("backpack.name", "&eRucksack")); + if (!meta.getDisplayName().equals(expectedName)) { + logger.fine("Item hat falschen Namen: " + meta.getDisplayName() + ", erwartet: " + expectedName); + return; + } + + event.setCancelled(true); + + UUID uuid = player.getUniqueId(); + String inventoryTitle = ChatColor.translateAlternateColorCodes('&', langConfig.getString("backpack.inventory-title", "&eDein Rucksack")); + + Inventory backpackInv = Bukkit.createInventory(null, 27, inventoryTitle); + logger.info("Öffne Rucksack-Inventar für Spieler " + player.getName() + " (" + uuid + ")"); + + for (int i = 0; i < 27; i++) { + String path = uuid + ".slot" + i; + if (backpackConfig.contains(path)) { + backpackInv.setItem(i, backpackConfig.getItemStack(path)); + } + } + + player.openInventory(backpackInv); + } + + @EventHandler + public void onClose(InventoryCloseEvent event) { + String expectedTitle = ChatColor.translateAlternateColorCodes('&', langConfig.getString("backpack.inventory-title", "&eDein Rucksack")); + if (!event.getView().getTitle().equals(expectedTitle)) return; + + Player player = (Player) event.getPlayer(); + UUID uuid = player.getUniqueId(); + Inventory inv = event.getInventory(); + + logger.info("Speichere Rucksack-Inventar für Spieler " + player.getName() + " (" + uuid + ")"); + + for (int i = 0; i < inv.getSize(); i++) { + String path = uuid + ".slot" + i; + ItemStack item = inv.getItem(i); + backpackConfig.set(path, item); + } + + try { + if (!backpackFile.exists()) { + backpackFile.createNewFile(); + logger.info("backpacks.yml wurde erstellt."); + } + backpackConfig.save(backpackFile); + logger.info("Rucksack-Inventar gespeichert für " + player.getName()); + } catch (IOException e) { + player.sendMessage(ChatColor.RED + "Fehler beim Speichern deines Rucksacks."); + logger.severe("Fehler beim Speichern der backpacks.yml: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/DebugArmorStandListener.java b/src/main/java/de/viper/survivalplus/listeners/DebugArmorStandListener.java new file mode 100644 index 0000000..c45507a --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/DebugArmorStandListener.java @@ -0,0 +1,46 @@ +package de.viper.survivalplus.listeners; + +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class DebugArmorStandListener implements Listener { + + private static final double MAX_DISTANCE = 3.0; + + @EventHandler + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event) { + if (event.getRightClicked() instanceof ArmorStand armorStand) { + Player player = event.getPlayer(); + if (armorStand.getScoreboardTags().contains("debugArmorStand")) { + if (armorStand.getLocation().distance(player.getLocation()) <= MAX_DISTANCE) { + armorStand.remove(); + player.sendMessage("§aDebug-ArmorStand wurde entfernt."); + event.setCancelled(true); + } else { + player.sendMessage("§cDieser Debug-ArmorStand ist zu weit entfernt."); + } + } + } + } + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (event.getEntity() instanceof ArmorStand armorStand) { + if (armorStand.getScoreboardTags().contains("debugArmorStand")) { + if (event.getDamager() instanceof Player player) { + if (armorStand.getLocation().distance(player.getLocation()) <= MAX_DISTANCE) { + armorStand.remove(); + player.sendMessage("§aDebug-ArmorStand wurde zerstört."); + event.setCancelled(true); + } else { + player.sendMessage("§cDieser Debug-ArmorStand ist zu weit entfernt."); + } + } + } + } + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/GraveListener.java b/src/main/java/de/viper/survivalplus/listeners/GraveListener.java new file mode 100644 index 0000000..190cc99 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/GraveListener.java @@ -0,0 +1,174 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.Chest; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +public class GraveListener implements Listener { + + private final SurvivalPlus plugin; + private final HashMap graveOwners = new HashMap<>(); + private long despawnTime; + + public GraveListener(SurvivalPlus plugin) { + this.plugin = plugin; + reloadConfig(plugin.getConfig()); + } + + public void reloadConfig(FileConfiguration config) { + this.despawnTime = config.getLong("graves.despawn-time", 1800); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + if (event.getKeepInventory()) return; + + Player player = event.getEntity(); + UUID playerId = player.getUniqueId(); + FileConfiguration gravesConfig = plugin.getGravesConfig(); + FileConfiguration lang = plugin.getLangConfig(); + + Location deathLocation = player.getLocation().clone(); + Block block = deathLocation.getBlock(); + + // Save to graves.yml + gravesConfig.set("graves." + playerId + ".world", deathLocation.getWorld().getName()); + gravesConfig.set("graves." + playerId + ".x", deathLocation.getX()); + gravesConfig.set("graves." + playerId + ".y", deathLocation.getY()); + gravesConfig.set("graves." + playerId + ".z", deathLocation.getZ()); + plugin.saveGravesConfig(); + + // Suche Platz für Truhe + if (block.isEmpty() || block.getType().isAir()) { + block.setType(Material.CHEST); + } else { + for (int i = 1; i <= 10; i++) { + Block above = block.getRelative(0, i, 0); + if (above.isEmpty() || above.getType().isAir()) { + above.setType(Material.CHEST); + block = above; + deathLocation = block.getLocation(); + break; + } + } + } + + if (block.getType() == Material.CHEST) { + Chest chest = (Chest) block.getState(); + for (ItemStack item : event.getDrops()) { + if (item != null && item.getType() != Material.AIR) { + chest.getInventory().addItem(item); + } + } + event.getDrops().clear(); + + graveOwners.put(block.getLocation(), playerId); + + player.sendMessage(String.format( + lang.getString("graves.created", "§aDein Grab wurde bei x=%.2f, y=%.2f, z=%.2f erstellt!"), + deathLocation.getX(), deathLocation.getY(), deathLocation.getZ() + )); + + // ArmorStand mit "Grab von " in Rot über der Truhe + Location standLocation = deathLocation.clone().add(0.5, 1.2, 0.5); + ArmorStand stand = deathLocation.getWorld().spawn(standLocation, ArmorStand.class); + stand.setVisible(false); + stand.setCustomName("§cGrab von " + player.getName()); + stand.setCustomNameVisible(true); + stand.setMarker(true); + stand.setGravity(false); + stand.setInvulnerable(true); + stand.setSilent(true); + stand.setSmall(true); + + // Entfernen nach Zeit + if (despawnTime >= 0) { + final Block graveBlock = block; + final Location graveLocation = graveBlock.getLocation(); + final Player deadPlayer = player; + final FileConfiguration langConfigFinal = lang; + + new BukkitRunnable() { + @Override + public void run() { + if (graveBlock.getType() == Material.CHEST) { + graveBlock.setType(Material.AIR); + graveOwners.remove(graveLocation); + + removeArmorStand(graveLocation); + + deadPlayer.sendMessage(String.format( + langConfigFinal.getString("graves.despawned", "§cDein Grab bei x=%.2f, y=%.2f, z=%.2f ist verschwunden!"), + graveLocation.getX(), graveLocation.getY(), graveLocation.getZ() + )); + } + } + }.runTaskLater(plugin, despawnTime * 20L); + } + + // Überwache, ob Truhe geleert wurde + final Block chestBlockFinal = block; + final Player playerFinal = player; + final FileConfiguration langFinal = lang; + + new BukkitRunnable() { + @Override + public void run() { + if (chestBlockFinal.getType() != Material.CHEST) { + cancel(); + return; + } + + Chest c = (Chest) chestBlockFinal.getState(); + boolean empty = true; + for (ItemStack item : c.getInventory().getContents()) { + if (item != null && item.getType() != Material.AIR) { + empty = false; + break; + } + } + + if (empty) { + c.getBlock().setType(Material.AIR); + graveOwners.remove(c.getLocation()); + removeArmorStand(c.getLocation()); + + playerFinal.sendMessage(String.format( + langFinal.getString("graves.emptied", "§7Dein Grab bei x=%.2f, y=%.2f, z=%.2f wurde geleert."), + c.getX(), c.getY(), c.getZ() + )); + + cancel(); + } + } + }.runTaskTimer(plugin, 20L, 60L); + } + } + + private void removeArmorStand(Location chestLocation) { + List entities = chestLocation.getWorld().getNearbyEntities(chestLocation.clone().add(0.5, 1.2, 0.5), 0.5, 1, 0.5) + .stream().filter(e -> e instanceof ArmorStand).toList(); + for (Entity entity : entities) { + entity.remove(); + } + } + + public boolean isOwner(Player player, Location location) { + return graveOwners.getOrDefault(location, null) != null && + graveOwners.get(location).equals(player.getUniqueId()); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/InventoryClickListener.java b/src/main/java/de/viper/survivalplus/listeners/InventoryClickListener.java new file mode 100644 index 0000000..a564548 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/InventoryClickListener.java @@ -0,0 +1,69 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.configuration.file.FileConfiguration; +import java.util.logging.Level; + +public class InventoryClickListener implements Listener { + private final SurvivalPlus plugin; + + public InventoryClickListener(SurvivalPlus plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + FileConfiguration lang = plugin.getLangConfig(); + String guiTitle = event.getView().getTitle(); + String homeGuiTitle = lang.getString("homelist.gui-title", "Deine Homes"); + if (!guiTitle.equals(homeGuiTitle)) { + plugin.getLogger().log(Level.FINE, "Ignoriere Klick in GUI mit Titel: " + guiTitle); + return; + } + + plugin.getLogger().log(Level.FINE, "Verarbeite Klick in Home-Liste GUI für Spieler: " + event.getWhoClicked().getName()); + event.setCancelled(true); + if (!(event.getWhoClicked() instanceof Player) || event.getCurrentItem() == null) { + return; + } + + Player player = (Player) event.getWhoClicked(); + String homeName = event.getCurrentItem().getItemMeta().getDisplayName().replace("§a", ""); + String path = "homes." + player.getUniqueId() + "." + homeName.toLowerCase(); + + FileConfiguration homes = plugin.getHomesConfig(); + if (!homes.contains(path)) { + player.sendMessage(lang.getString("homelist.home-deleted", "§cDieses Home existiert nicht mehr!")); + player.closeInventory(); + return; + } + + String worldName = homes.getString(path + ".world"); + World world = plugin.getServer().getWorld(worldName); + if (world == null) { + player.sendMessage(lang.getString("homelist.world-not-found", "§cDie Welt dieses Homes existiert nicht!")); + player.closeInventory(); + return; + } + + Location loc = new Location( + world, + homes.getDouble(path + ".x"), + homes.getDouble(path + ".y"), + homes.getDouble(path + ".z"), + (float) homes.getDouble(path + ".yaw"), + (float) homes.getDouble(path + ".pitch") + ); + + player.teleport(loc); + player.sendMessage(lang.getString("homelist.teleported", "§aZum Home %name% teleportiert!") + .replace("%name%", homeName)); + player.closeInventory(); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/LoginListener.java b/src/main/java/de/viper/survivalplus/listeners/LoginListener.java new file mode 100644 index 0000000..acf5bf9 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/LoginListener.java @@ -0,0 +1,53 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.ChatColor; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class LoginListener implements Listener { + + private final SurvivalPlus plugin; + + public LoginListener(SurvivalPlus plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + if (!player.hasPlayedBefore()) { + // Erster Login + sendWelcomeMessage(player, true); + } else { + // Wiederkehrer + sendWelcomeMessage(player, false); + } + } + + private void sendWelcomeMessage(Player player, boolean firstJoin) { + String messageKey = firstJoin ? "welcome.first-join-message" : "welcome.return-message"; + String soundKey = firstJoin ? "welcome.first-join-sound" : "welcome.return-sound"; + String volumeKey = firstJoin ? "welcome.first-join-sound-volume" : "welcome.return-sound-volume"; + String pitchKey = firstJoin ? "welcome.first-join-sound-pitch" : "welcome.return-sound-pitch"; + + String rawMessage = plugin.getLangConfig().getString(messageKey, "&6Willkommen, &e{player}&6!"); + String message = ChatColor.translateAlternateColorCodes('&', rawMessage.replace("{player}", player.getName())); + player.sendMessage(message); + + String soundName = plugin.getLangConfig().getString(soundKey, "ENTITY_PLAYER_LEVELUP"); + float volume = (float) plugin.getLangConfig().getDouble(volumeKey, 1.0); + float pitch = (float) plugin.getLangConfig().getDouble(pitchKey, 1.0); + + try { + Sound sound = Sound.valueOf(soundName); + player.playSound(player.getLocation(), sound, volume, pitch); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Ungültiger Sound-Name in lang.yml: " + soundName); + } + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/MobCapListener.java b/src/main/java/de/viper/survivalplus/listeners/MobCapListener.java new file mode 100644 index 0000000..bc95d0a --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/MobCapListener.java @@ -0,0 +1,181 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Chunk; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Animals; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class MobCapListener implements Listener { + + private final SurvivalPlus plugin; + private boolean enabled; + private int maxAnimalsPerChunk; + private final Map> chunkAnimalMap = new HashMap<>(); // Chunk -> Tier-UUID -> Spieler-UUID + + public MobCapListener(SurvivalPlus plugin, FileConfiguration config) { + this.plugin = plugin; + reloadConfig(config); + } + + public void reloadConfig(FileConfiguration config) { + this.enabled = config.getBoolean("mob-cap.enabled", true); + this.maxAnimalsPerChunk = config.getInt("mob-cap.max-animals-per-chunk", 10); + plugin.getLogger().info("MobCapListener: enabled=" + enabled + ", maxAnimalsPerChunk=" + maxAnimalsPerChunk); + + // Bereinige bestehende Daten + chunkAnimalMap.clear(); + + if (!enabled) { + plugin.getMobCapConfig().set("mobcap", null); // Bereinige mobcap.yml + plugin.saveMobCapConfig(); + plugin.getLogger().info("MobCapListener: Daten bereinigt, da enabled=false"); + return; + } + + // Lade Zuordnungen aus mobcap.yml + FileConfiguration mobCapConfig = plugin.getMobCapConfig(); + if (mobCapConfig.contains("mobcap")) { + for (String chunkKey : mobCapConfig.getConfigurationSection("mobcap").getKeys(false)) { + Map animalToPlayer = new HashMap<>(); + for (String animalIdStr : mobCapConfig.getConfigurationSection("mobcap." + chunkKey).getKeys(false)) { + try { + UUID animalId = UUID.fromString(animalIdStr); + String playerIdStr = mobCapConfig.getString("mobcap." + chunkKey + "." + animalIdStr); + if (playerIdStr != null) { + UUID playerId = UUID.fromString(playerIdStr); + Entity entity = plugin.getServer().getEntity(animalId); + if (entity instanceof Animals) { + animalToPlayer.put(animalId, playerId); + } else { + // Entferne ungültige Einträge + mobCapConfig.set("mobcap." + chunkKey + "." + animalIdStr, null); + } + } + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Ungültige UUID in mobcap.yml: " + animalIdStr); + } + } + chunkAnimalMap.put(chunkKey, animalToPlayer); + } + } + + // Scanne alle Welten nach Tieren + for (org.bukkit.World world : plugin.getServer().getWorlds()) { + for (Chunk chunk : world.getLoadedChunks()) { + String chunkKey = getChunkKey(chunk); + Map animalToPlayer = chunkAnimalMap.computeIfAbsent(chunkKey, k -> new HashMap<>()); + for (Entity entity : chunk.getEntities()) { + if (entity instanceof Animals) { + UUID id = entity.getUniqueId(); + if (!animalToPlayer.containsKey(id)) { + animalToPlayer.put(id, null); // Kein Spieler zugeordnet + } + } + } + } + } + + // Speichere aktualisierte mobcap.yml + saveChunkAnimalMap(); + plugin.getLogger().info("MobCapListener: Nach Reload " + chunkAnimalMap.size() + " Chunks mit Tieren gefunden."); + } + + @EventHandler + public void onCreatureSpawn(CreatureSpawnEvent event) { + if (!enabled) return; + + LivingEntity entity = event.getEntity(); + if (!(entity instanceof Animals)) return; + + Chunk chunk = entity.getLocation().getChunk(); + String chunkKey = getChunkKey(chunk); + Map animalToPlayer = chunkAnimalMap.computeIfAbsent(chunkKey, k -> new HashMap<>()); + + int animalCount = animalToPlayer.size(); + if (animalCount >= maxAnimalsPerChunk) { + event.setCancelled(true); + plugin.getLogger().info("Spawn von " + entity.getType() + " in Chunk " + chunkKey + " verhindert: Limit von " + maxAnimalsPerChunk + " erreicht."); + + // HINWEIS: Wenn du dem Züchter eine Nachricht schicken willst, nutze EntityBreedEvent + // Hier ist das nicht möglich, weil man beim Spawn keinen Spielerzugriff hat + } else { + animalToPlayer.put(entity.getUniqueId(), null); // Kein Spieler zugeordnet + saveChunkAnimalMap(); + } + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!enabled) return; + + if (!(event.getRightClicked() instanceof Animals)) return; + + Player player = event.getPlayer(); + Entity entity = event.getRightClicked(); + Chunk chunk = entity.getLocation().getChunk(); + String chunkKey = getChunkKey(chunk); + Map animalToPlayer = chunkAnimalMap.computeIfAbsent(chunkKey, k -> new HashMap<>()); + + int animalCount = animalToPlayer.size(); + if (animalCount >= maxAnimalsPerChunk) { + event.setCancelled(true); + player.sendMessage(plugin.getMessage("mob-cap.limit-reached").replace("%max%", String.valueOf(maxAnimalsPerChunk))); + plugin.getLogger().info("Interaktion mit " + entity.getType() + " in Chunk " + chunkKey + " verhindert: Limit von " + maxAnimalsPerChunk + " erreicht."); + } else { + animalToPlayer.put(entity.getUniqueId(), player.getUniqueId()); + saveChunkAnimalMap(); + } + } + + @EventHandler + public void onEntityDeath(EntityDeathEvent event) { + if (!enabled) return; + + if (!(event.getEntity() instanceof Animals)) return; + + LivingEntity entity = event.getEntity(); + Chunk chunk = entity.getLocation().getChunk(); + String chunkKey = getChunkKey(chunk); + Map animalToPlayer = chunkAnimalMap.get(chunkKey); + + if (animalToPlayer != null) { + UUID animalId = entity.getUniqueId(); + animalToPlayer.remove(animalId); + if (animalToPlayer.isEmpty()) { + chunkAnimalMap.remove(chunkKey); + } + saveChunkAnimalMap(); + plugin.getLogger().info("Tier " + entity.getType() + " in Chunk " + chunkKey + " gestorben. Verbleibende Tiere: " + animalToPlayer.size()); + } + } + + private String getChunkKey(Chunk chunk) { + return chunk.getWorld().getName() + "," + chunk.getX() + "," + chunk.getZ(); + } + + private void saveChunkAnimalMap() { + FileConfiguration mobCapConfig = plugin.getMobCapConfig(); + mobCapConfig.set("mobcap", null); // Bereinige vorherige Daten + for (Map.Entry> entry : chunkAnimalMap.entrySet()) { + String chunkKey = entry.getKey(); + Map animalToPlayer = entry.getValue(); + for (Map.Entry animalEntry : animalToPlayer.entrySet()) { + mobCapConfig.set("mobcap." + chunkKey + "." + animalEntry.getKey().toString(), + animalEntry.getValue() != null ? animalEntry.getValue().toString() : null); + } + } + plugin.saveMobCapConfig(); + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/MobLeashLimitListener.java b/src/main/java/de/viper/survivalplus/listeners/MobLeashLimitListener.java new file mode 100644 index 0000000..5c81e27 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/MobLeashLimitListener.java @@ -0,0 +1,253 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; + +import java.util.*; + +public class MobLeashLimitListener implements Listener { + + private final SurvivalPlus plugin; + private boolean enabled; + private int maxLeashCount; + private final Map> leashedEntities = new HashMap<>(); + private final Map leashCounts = new HashMap<>(); + private final Map entityToPlayer = new HashMap<>(); + + public MobLeashLimitListener(SurvivalPlus plugin, FileConfiguration config) { + this.plugin = plugin; + reloadConfig(config); + } + + public void reloadConfig(FileConfiguration config) { + this.enabled = config.getBoolean("mob-leash-limit.enabled", true); + this.maxLeashCount = config.getInt("mob-leash-limit.max-leash-count", 5); + plugin.getLogger().info("MobLeashLimitListener: enabled=" + enabled + ", maxLeashCount=" + maxLeashCount); + + // Bereinige bestehende Daten + leashedEntities.clear(); + leashCounts.clear(); + entityToPlayer.clear(); + + if (!enabled) { + plugin.getLeashesConfig().set("leashes", null); // Bereinige leashes.yml + plugin.saveLeashesConfig(); + plugin.getLogger().info("MobLeashLimitListener: Daten bereinigt, da enabled=false"); + return; + } + + // Lade Zuordnungen aus leashes.yml + FileConfiguration leashesConfig = plugin.getLeashesConfig(); + if (leashesConfig.contains("leashes")) { + for (String entityIdStr : leashesConfig.getConfigurationSection("leashes").getKeys(false)) { + UUID entityId; + try { + entityId = UUID.fromString(entityIdStr); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Ungültige UUID in leashes.yml: " + entityIdStr); + continue; + } + String playerIdStr = leashesConfig.getString("leashes." + entityIdStr); + if (playerIdStr != null) { + try { + UUID playerId = UUID.fromString(playerIdStr); + LivingEntity entity = (LivingEntity) plugin.getServer().getEntity(entityId); + if (entity != null && entity.isLeashed()) { + Set playerLeashedEntities = leashedEntities.computeIfAbsent(playerId, k -> new HashSet<>()); + playerLeashedEntities.add(entityId); + leashCounts.put(playerId, playerLeashedEntities.size()); + entityToPlayer.put(entityId, playerId); + } else { + // Entferne ungültige Einträge aus leashes.yml + leashesConfig.set("leashes." + entityIdStr, null); + } + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Ungültige Spieler-UUID in leashes.yml für Entity " + entityIdStr + ": " + playerIdStr); + } + } + } + } + + // Scanne alle Welten nach angeleinten Tieren, die noch nicht in leashes.yml sind + for (org.bukkit.World world : plugin.getServer().getWorlds()) { + for (LivingEntity entity : world.getLivingEntities()) { + if (entity.isLeashed() && !entityToPlayer.containsKey(entity.getUniqueId())) { + // Nur Tiere zählen, die von einem Spieler gehalten werden (für neue Leashes) + if (entity.getLeashHolder() instanceof Player) { + Player player = (Player) entity.getLeashHolder(); + UUID playerId = player.getUniqueId(); + UUID entityId = entity.getUniqueId(); + + Set playerLeashedEntities = leashedEntities.computeIfAbsent(playerId, k -> new HashSet<>()); + playerLeashedEntities.add(entityId); + leashCounts.put(playerId, playerLeashedEntities.size()); + entityToPlayer.put(entityId, playerId); + + // Speichere in leashes.yml + leashesConfig.set("leashes." + entityId.toString(), playerId.toString()); + } + } + } + } + + // Prüfe, ob das neue Limit überschritten wird + for (Map.Entry> entry : leashedEntities.entrySet()) { + UUID playerId = entry.getKey(); + Set playerLeashedEntities = entry.getValue(); + if (playerLeashedEntities.size() > maxLeashCount) { + Player player = plugin.getServer().getPlayer(playerId); + if (player != null && player.isOnline()) { + player.sendMessage(plugin.getMessage("mob-leash-limit.limit-adjusted") + .replace("%count%", String.valueOf(playerLeashedEntities.size())) + .replace("%max%", String.valueOf(maxLeashCount))); + } + } + } + + // Entferne leere Einträge + leashedEntities.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + leashCounts.entrySet().removeIf(entry -> entry.getValue() == 0); + + // Speichere aktualisierte leashes.yml + plugin.saveLeashesConfig(); + + plugin.getLogger().info("MobLeashLimitListener: Nach Reload " + leashedEntities.size() + " Spieler mit angeleinten Tieren gefunden."); + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!enabled) return; + + Player player = event.getPlayer(); + ItemStack itemInHand = player.getInventory().getItemInMainHand(); + UUID playerId = player.getUniqueId(); + LivingEntity entity = (LivingEntity) event.getRightClicked(); + UUID entityId = entity.getUniqueId(); + + if (itemInHand != null && itemInHand.getType() == Material.LEAD) { + Set playerLeashedEntities = leashedEntities.computeIfAbsent(playerId, k -> new HashSet<>()); + int currentCount = playerLeashedEntities.size(); + plugin.getLogger().info("PlayerInteractEntity: player=" + player.getName() + ", currentCount=" + currentCount + ", maxLeashCount=" + maxLeashCount); + + if (playerLeashedEntities.contains(entityId)) { + // Ableinen + playerLeashedEntities.remove(entityId); + leashCounts.put(playerId, currentCount - 1); + entityToPlayer.remove(entityId); + plugin.getLeashesConfig().set("leashes." + entityId.toString(), null); + plugin.saveLeashesConfig(); + player.sendMessage(plugin.getMessage("mob-leash-limit.unleashed") + .replace("%count%", String.valueOf(currentCount - 1))); + + // Entferne leere Einträge + if (playerLeashedEntities.isEmpty()) { + leashedEntities.remove(playerId); + leashCounts.remove(playerId); + } + } else { + // Anleinen + if (currentCount >= maxLeashCount) { + player.sendMessage(plugin.getMessage("mob-leash-limit.max-reached") + .replace("%count%", String.valueOf(maxLeashCount))); + event.setCancelled(true); + } else { + playerLeashedEntities.add(entityId); + leashCounts.put(playerId, currentCount + 1); + entityToPlayer.put(entityId, playerId); + plugin.getLeashesConfig().set("leashes." + entityId.toString(), playerId.toString()); + plugin.saveLeashesConfig(); + player.sendMessage(plugin.getMessage("mob-leash-limit.leashed") + .replace("%count%", String.valueOf(currentCount + 1))); + } + } + } + } + + @EventHandler + public void onEntityDeath(EntityDeathEvent event) { + if (!enabled) { + UUID entityId = event.getEntity().getUniqueId(); + entityToPlayer.remove(entityId); + plugin.getLeashesConfig().set("leashes." + entityId.toString(), null); + plugin.saveLeashesConfig(); + return; + } + + LivingEntity entity = event.getEntity(); + UUID entityId = entity.getUniqueId(); + UUID playerId = entityToPlayer.remove(entityId); + + if (playerId != null) { + Set playerLeashedEntities = leashedEntities.getOrDefault(playerId, new HashSet<>()); + playerLeashedEntities.remove(entityId); + int newCount = playerLeashedEntities.size(); + leashCounts.put(playerId, newCount); + + // Entferne aus leashes.yml + plugin.getLeashesConfig().set("leashes." + entityId.toString(), null); + plugin.saveLeashesConfig(); + + // Entferne leere Einträge + if (playerLeashedEntities.isEmpty()) { + leashedEntities.remove(playerId); + leashCounts.remove(playerId); + } + + Player player = plugin.getServer().getPlayer(playerId); + if (player != null && player.isOnline()) { + player.sendMessage(plugin.getMessage("mob-leash-limit.entity-died") + .replace("%count%", String.valueOf(newCount))); + } + } + } + + public void updateLeashCount(Player player) { + UUID playerId = player.getUniqueId(); + Set playerLeashedEntities = leashedEntities.getOrDefault(playerId, new HashSet<>()); + // Entferne nicht existierende oder nicht angeleinte Entities + playerLeashedEntities.removeIf(entityId -> { + LivingEntity entity = (LivingEntity) plugin.getServer().getEntity(entityId); + if (entity == null || !entity.isLeashed()) { + plugin.getLeashesConfig().set("leashes." + entityId.toString(), null); + return true; + } + return false; + }); + int currentCount = playerLeashedEntities.size(); + leashCounts.put(playerId, currentCount); + + // Aktualisiere entityToPlayer-Map + entityToPlayer.entrySet().removeIf(entry -> entry.getValue().equals(playerId) && !playerLeashedEntities.contains(entry.getKey())); + + // Entferne leere Einträge + if (playerLeashedEntities.isEmpty()) { + leashedEntities.remove(playerId); + leashCounts.remove(playerId); + } + + // Falls disabled, bereinige die Daten für diesen Spieler + if (!enabled) { + playerLeashedEntities.clear(); + leashedEntities.remove(playerId); + leashCounts.remove(playerId); + entityToPlayer.entrySet().removeIf(entry -> entry.getValue().equals(playerId)); + plugin.getLeashesConfig().set("leashes", null); + plugin.saveLeashesConfig(); + } + + plugin.getLogger().info("updateLeashCount: player=" + player.getName() + ", currentCount=" + currentCount); + } + + public int getLeashCount(Player player) { + updateLeashCount(player); + return leashCounts.getOrDefault(player.getUniqueId(), 0); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/OreAlarmListener.java b/src/main/java/de/viper/survivalplus/listeners/OreAlarmListener.java new file mode 100644 index 0000000..9bc8200 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/OreAlarmListener.java @@ -0,0 +1,139 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class OreAlarmListener implements Listener { + + private static final long TIME_FRAME_MILLIS = 30 * 1000; + + private final SurvivalPlus plugin; + + private final Map> playerOreData = new HashMap<>(); + + private FileConfiguration config; + + // Schwellenwert wird jetzt aus der Config geladen (Fallback DEFAULT_THRESHOLD) + private int threshold; + + public OreAlarmListener(SurvivalPlus plugin) { + this.plugin = plugin; + this.config = plugin.getConfig(); + loadSettings(); + } + + private void loadSettings() { + this.threshold = config.getInt("ore-alarm.threshold", 10); + } + + /** + * Reloadet die Config, lädt die Einstellungen neu und leert gespeicherte Daten. + */ + public void reloadConfig(FileConfiguration newConfig) { + this.config = newConfig; + loadSettings(); + playerOreData.clear(); + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + Material blockType = event.getBlock().getType(); + + if (!isMonitoredOre(blockType)) { + return; + } + + UUID playerId = player.getUniqueId(); + long now = System.currentTimeMillis(); + + playerOreData.putIfAbsent(playerId, new HashMap<>()); + Map oreMap = playerOreData.get(playerId); + + oreMap.putIfAbsent(blockType, new OreData(0, now)); + OreData data = oreMap.get(blockType); + + if (now - data.startTime > TIME_FRAME_MILLIS) { + data.count = 1; + data.startTime = now; + } else { + data.count++; + } + + oreMap.put(blockType, data); + + int threshold = calculateDynamicThreshold(playerId, blockType); + if (data.count >= threshold) { + String oreName = prettifyMaterialName(blockType); + + String rawMessage = plugin.getLangConfig().getString("orealarm.message", + "§c[Erz-Alarm] Spieler §e%player% §chat in %seconds% Sekunden ungewöhnlich viele %ore% abgebaut: §e%count%"); + + String message = rawMessage + .replace("%player%", player.getName()) + .replace("%ore%", oreName) + .replace("%count%", Integer.toString(data.count)) + .replace("%seconds%", Long.toString(TIME_FRAME_MILLIS / 1000)); + + Bukkit.getOnlinePlayers().stream() + .filter(p -> p.isOp() || p.hasPermission("survivalplus.orealarm")) + .forEach(p -> p.sendMessage(message)); + + data.count = 0; + data.startTime = now; + } + } + + private boolean isMonitoredOre(Material material) { + switch (material) { + case DIAMOND_ORE: + case DEEPSLATE_DIAMOND_ORE: + case REDSTONE_ORE: + case DEEPSLATE_REDSTONE_ORE: + case GOLD_ORE: + case DEEPSLATE_GOLD_ORE: + case IRON_ORE: + case DEEPSLATE_IRON_ORE: + case LAPIS_ORE: + case DEEPSLATE_LAPIS_ORE: + case EMERALD_ORE: + case DEEPSLATE_EMERALD_ORE: + return true; + default: + return false; + } + } + + private int calculateDynamicThreshold(UUID playerId, Material ore) { + // Nutzt jetzt das geladene threshold, kann später erweitert werden + return threshold; + } + + private String prettifyMaterialName(Material material) { + String name = material.name().toLowerCase(); + name = name.replace("_ore", " Erz"); + name = name.replace("deepslate ", "Tiefgeschichtetes "); + name = Character.toUpperCase(name.charAt(0)) + name.substring(1); + return name; + } + + private static class OreData { + int count; + long startTime; + + OreData(int count, long startTime) { + this.count = count; + this.startTime = startTime; + } + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/SignColorListener.java b/src/main/java/de/viper/survivalplus/listeners/SignColorListener.java new file mode 100644 index 0000000..e24dc5b --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/SignColorListener.java @@ -0,0 +1,85 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import net.md_5.bungee.api.ChatColor; // Bungee ChatColor für RGB/Hex Unterstützung +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.SignChangeEvent; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SignColorListener implements Listener { + + private final SurvivalPlus plugin; + + // Muster für Hex-Farbcodes (#FFFFFF) + private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}"); + // Muster für RGB-Farben rgb(255,255,255) + private static final Pattern RGB_PATTERN = Pattern.compile("rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)"); + + public SignColorListener(SurvivalPlus plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onSignChange(SignChangeEvent event) { + for (int i = 0; i < event.getLines().length; i++) { + String line = event.getLine(i); + if (line != null && !line.isEmpty()) { + // 1. &-Codes übersetzen (z.B. &a -> §a) + String translated = ChatColor.translateAlternateColorCodes('&', line); + + // 2. Hex-Farben ersetzen + translated = replaceHexColors(translated); + + // 3. RGB-Farben ersetzen + translated = replaceRgbColors(translated); + + event.setLine(i, translated); + } + } + } + + private String replaceHexColors(String input) { + Matcher matcher = HEX_PATTERN.matcher(input); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String hexCode = matcher.group(); + // ChatColor.of akzeptiert Hex-Farbcode mit # + String color = ChatColor.of(hexCode).toString(); + matcher.appendReplacement(sb, color); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private String replaceRgbColors(String input) { + Matcher matcher = RGB_PATTERN.matcher(input); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + int r = clampColorValue(matcher.group(1)); + int g = clampColorValue(matcher.group(2)); + int b = clampColorValue(matcher.group(3)); + + String hex = String.format("#%02X%02X%02X", r, g, b); + String color = ChatColor.of(hex).toString(); + + matcher.appendReplacement(sb, color); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private int clampColorValue(String val) { + int v; + try { + v = Integer.parseInt(val); + } catch (NumberFormatException e) { + v = 0; + } + return Math.min(255, Math.max(0, v)); + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/SitListener.java b/src/main/java/de/viper/survivalplus/listeners/SitListener.java new file mode 100644 index 0000000..66c232d --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/SitListener.java @@ -0,0 +1,128 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.event.block.Action; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +public class SitListener implements Listener { + private final SurvivalPlus plugin; + private final Map sittingPlayers = new HashMap<>(); + + public SitListener(SurvivalPlus plugin) { + this.plugin = plugin; + } + + public boolean isSitting(Player player) { + return sittingPlayers.containsKey(player.getUniqueId()); + } + + public void sitPlayer(Player player, Location location) { + if (sittingPlayers.containsKey(player.getUniqueId())) { + return; + } + + // Erstelle einen unsichtbaren ArmorStand als Sitz + ArmorStand armorStand = player.getWorld().spawn(location, ArmorStand.class); + armorStand.setGravity(false); + armorStand.setMarker(true); + armorStand.setInvisible(true); + armorStand.setInvulnerable(true); + armorStand.addPassenger(player); + + 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); + if (armorStand != null) { + armorStand.remove(); + FileConfiguration lang = plugin.getLangConfig(); + player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); + plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " ist aufgestanden"); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + FileConfiguration lang = plugin.getLangConfig(); + + if (!player.hasPermission("survivalplus.sit")) { + player.sendMessage(lang.getString("no-permission", "§cDu hast keine Berechtigung für diesen Befehl!")); + return; + } + + Block block = event.getClickedBlock(); + if (block == null || !isStair(block.getType())) { + return; + } + + // Wenn der Spieler bereits sitzt, stehe auf + if (sittingPlayers.containsKey(player.getUniqueId())) { + standUp(player); + event.setCancelled(true); + return; + } + + // Setze den Spieler genau auf der Treppenstufe + Location location = block.getLocation(); + location.setX(location.getX() + 0.5); + location.setY(location.getY() + 0.5); // Genau auf der Treppenstufe (halbe Blockhöhe) + location.setZ(location.getZ() + 0.5); + sitPlayer(player, location); + event.setCancelled(true); // Verhindere andere Interaktionen mit der Treppe + } + + @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 bewegt hat (nur Positionsänderung, nicht Kopfbewegung) + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()) { + standUp(player); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + standUp(player); + } + + 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()); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/SleepListener.java b/src/main/java/de/viper/survivalplus/listeners/SleepListener.java new file mode 100644 index 0000000..b1abeef --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/SleepListener.java @@ -0,0 +1,68 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.bukkit.event.player.PlayerBedLeaveEvent; + +public class SleepListener implements Listener { + + private final SurvivalPlus plugin; + private double requiredPercentage; + + public SleepListener(SurvivalPlus plugin) { + this.plugin = plugin; + reloadConfig(plugin.getConfig()); + } + + public void reloadConfig(FileConfiguration config) { + this.requiredPercentage = config.getDouble("sleep.required-percentage", 50.0); + } + + @EventHandler + public void onPlayerBedEnter(PlayerBedEnterEvent event) { + Bukkit.getScheduler().runTaskLater(plugin, () -> checkSleepProgress(event.getPlayer().getWorld()), 1L); + } + + @EventHandler + public void onPlayerBedLeave(PlayerBedLeaveEvent event) { + Bukkit.getScheduler().runTaskLater(plugin, () -> checkSleepProgress(event.getPlayer().getWorld()), 1L); + } + + private void checkSleepProgress(World world) { + long time = world.getTime(); + if (time < 12541 || time > 23458) return; // Nur bei Nacht prüfen + + int sleeping = 0; + int total = 0; + + for (Player player : world.getPlayers()) { + if (player.isSleepingIgnored()) continue; + total++; + if (player.isSleeping()) sleeping++; + } + + if (total == 0) return; + + double percent = (sleeping * 100.0) / total; + + if (percent >= requiredPercentage) { + world.setTime(0); + world.setStorm(false); + world.setThundering(false); + Bukkit.broadcastMessage(plugin.getMessage("sleep.skipped") + .replace("%sleeping%", String.valueOf(sleeping)) + .replace("%total%", String.valueOf(total))); + } else { + Bukkit.broadcastMessage(plugin.getMessage("sleep.progress") + .replace("%sleeping%", String.valueOf(sleeping)) + .replace("%total%", String.valueOf(total)) + .replace("%required%", String.valueOf(requiredPercentage))); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/SpawnProtectionListener.java b/src/main/java/de/viper/survivalplus/listeners/SpawnProtectionListener.java new file mode 100644 index 0000000..fddcd73 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/SpawnProtectionListener.java @@ -0,0 +1,75 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Location; +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.EntityDamageByEntityEvent; + +public class SpawnProtectionListener implements Listener { + + private final SurvivalPlus plugin; + private final int radius; + private final boolean protectBlockBreak; + private final boolean protectBlockPlace; + private final boolean protectPvP; + private final String bypassPermission; + + public SpawnProtectionListener(SurvivalPlus plugin) { + this.plugin = plugin; + this.radius = plugin.getConfig().getInt("spawnprotection.radius", 20); + this.protectBlockBreak = plugin.getConfig().getBoolean("spawnprotection.protect-block-break", true); + this.protectBlockPlace = plugin.getConfig().getBoolean("spawnprotection.protect-block-place", true); + this.protectPvP = plugin.getConfig().getBoolean("spawnprotection.protect-pvp", true); + this.bypassPermission = plugin.getConfig().getString("spawnprotection.bypass-permission", "survivalplus.spawnprotection.bypass"); + } + + private boolean isInSpawnProtection(Location loc) { + if (loc == null) return false; + Location spawn = loc.getWorld().getSpawnLocation(); + return spawn.distance(loc) <= radius; + } + + private boolean canBypass(Player player) { + // Nur OPs oder Spieler mit spezieller Berechtigung dürfen bypassen + return player.isOp() || player.hasPermission(bypassPermission); + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + if (!protectBlockBreak) return; + Player player = event.getPlayer(); + if (isInSpawnProtection(event.getBlock().getLocation()) && !canBypass(player)) { + player.sendMessage(plugin.getMessage("spawnprotection.blockbreak-denied")); + event.setCancelled(true); + } + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + if (!protectBlockPlace) return; + Player player = event.getPlayer(); + if (isInSpawnProtection(event.getBlock().getLocation()) && !canBypass(player)) { + player.sendMessage(plugin.getMessage("spawnprotection.blockplace-denied")); + event.setCancelled(true); + } + } + + @EventHandler + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (!protectPvP) return; + if (!(event.getEntity() instanceof Player)) return; + if (!(event.getDamager() instanceof Player)) return; + + Player damaged = (Player) event.getEntity(); + Player damager = (Player) event.getDamager(); + + if (isInSpawnProtection(damaged.getLocation()) && !canBypass(damager)) { + damager.sendMessage(plugin.getMessage("spawnprotection.pvp-denied")); + event.setCancelled(true); + } + } +} diff --git a/src/main/java/de/viper/survivalplus/listeners/StatsListener.java b/src/main/java/de/viper/survivalplus/listeners/StatsListener.java new file mode 100644 index 0000000..8698d72 --- /dev/null +++ b/src/main/java/de/viper/survivalplus/listeners/StatsListener.java @@ -0,0 +1,81 @@ +package de.viper.survivalplus.listeners; + +import de.viper.survivalplus.Manager.StatsManager; +import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Bukkit; +import org.bukkit.Material; +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.PlayerDeathEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class StatsListener implements Listener { + + private final SurvivalPlus plugin; + private final StatsManager statsManager; + + // Spielzeit-Tracking + private final Map joinTimes = new HashMap<>(); + + public StatsListener(SurvivalPlus plugin, StatsManager statsManager) { + this.plugin = plugin; + this.statsManager = statsManager; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + joinTimes.put(event.getPlayer().getUniqueId(), System.currentTimeMillis()); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + long joinTime = joinTimes.getOrDefault(uuid, System.currentTimeMillis()); + long now = System.currentTimeMillis(); + long secondsPlayed = (now - joinTime) / 1000; + + statsManager.addPlayTime(uuid, secondsPlayed); + statsManager.saveStats(); + + joinTimes.remove(uuid); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + UUID uuid = player.getUniqueId(); + statsManager.addDeaths(uuid, 1); + statsManager.saveStats(); + + // Optional: Kill-Stat beim Killer erhöhen + if (player.getKiller() != null) { + Player killer = player.getKiller(); + statsManager.addKills(killer.getUniqueId(), 1); + statsManager.saveStats(); + } + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + statsManager.addBlocksBroken(uuid, 1); + statsManager.saveStats(); + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + statsManager.addBlocksPlaced(uuid, 1); + statsManager.saveStats(); + } +}