diff --git a/src/main/java/de/viper/survivalplus/Manager/BlockManager.java b/src/main/java/de/viper/survivalplus/Manager/BlockManager.java index 6e0f312..a87dee6 100644 --- a/src/main/java/de/viper/survivalplus/Manager/BlockManager.java +++ b/src/main/java/de/viper/survivalplus/Manager/BlockManager.java @@ -1,30 +1,83 @@ package de.viper.survivalplus.Manager; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +import java.io.File; +import java.io.IOException; import java.util.*; -import org.bukkit.entity.Player; +import de.viper.survivalplus.SurvivalPlus; public class BlockManager { + private final SurvivalPlus plugin; private final Map> blockedPlayers = new HashMap<>(); + private File blocksFile; + private FileConfiguration blocksConfig; + + public BlockManager(SurvivalPlus plugin) { + this.plugin = plugin; + this.blocksFile = new File(plugin.getDataFolder(), "blocks.yml"); + if (!blocksFile.exists()) { + try { + blocksFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + this.blocksConfig = YamlConfiguration.loadConfiguration(blocksFile); + loadBlocks(); + } + + private void saveBlocks() { + for (UUID key : blockedPlayers.keySet()) { + List uuids = new ArrayList<>(); + for (UUID uuid : blockedPlayers.get(key)) { + uuids.add(uuid.toString()); + } + blocksConfig.set(key.toString(), uuids); + } + try { + blocksConfig.save(blocksFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadBlocks() { + blockedPlayers.clear(); + for (String key : blocksConfig.getKeys(false)) { + UUID blockerUUID = UUID.fromString(key); + List blockedUUIDs = blocksConfig.getStringList(key); + Set blockedSet = new HashSet<>(); + for (String uuidStr : blockedUUIDs) { + blockedSet.add(UUID.fromString(uuidStr)); + } + blockedPlayers.put(blockerUUID, blockedSet); + } + } public void blockPlayer(Player blocker, Player toBlock) { blockedPlayers.computeIfAbsent(blocker.getUniqueId(), k -> new HashSet<>()).add(toBlock.getUniqueId()); + saveBlocks(); // Sofort speichern } public void unblockPlayer(Player blocker, Player toUnblock) { - Set blocked = blockedPlayers.get(blocker.getUniqueId()); - if (blocked != null) { - blocked.remove(toUnblock.getUniqueId()); - if (blocked.isEmpty()) { - blockedPlayers.remove(blocker.getUniqueId()); + Set blocked = blockedPlayers.get(blocker.getUniqueId()); + if (blocked != null) { + blocked.remove(toUnblock.getUniqueId()); + if (blocked.isEmpty()) { + blockedPlayers.remove(blocker.getUniqueId()); + } + saveBlocks(); // Sofort speichern } } -} public boolean hasBlocked(Player blocker, Player potentialBlocked) { return blockedPlayers.getOrDefault(blocker.getUniqueId(), Collections.emptySet()) - .contains(potentialBlocked.getUniqueId()); + .contains(potentialBlocked.getUniqueId()); } public Set getBlockedPlayers(Player player) { @@ -33,5 +86,6 @@ public class BlockManager { public void clear(Player player) { blockedPlayers.remove(player.getUniqueId()); + saveBlocks(); } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/Manager/LootChestManager.java b/src/main/java/de/viper/survivalplus/Manager/LootChestManager.java index 1d48176..84391c7 100644 --- a/src/main/java/de/viper/survivalplus/Manager/LootChestManager.java +++ b/src/main/java/de/viper/survivalplus/Manager/LootChestManager.java @@ -6,18 +6,21 @@ import org.bukkit.*; import org.bukkit.block.Chest; import org.bukkit.command.*; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.scheduler.BukkitRunnable; +import java.io.File; +import java.io.IOException; import java.util.*; import java.util.concurrent.ThreadLocalRandom; @@ -34,9 +37,17 @@ public class LootChestManager implements Listener, CommandExecutor { private final int maxLootPerPlayer; private final long lootLimitResetMillis; + // Persistence + private final File lootChestFile; + private FileConfiguration lootChestConfig; + public LootChestManager(SurvivalPlus plugin) { this.plugin = plugin; + this.lootChestFile = new File(plugin.getDataFolder(), "lootchests.yml"); + this.lootChestConfig = YamlConfiguration.loadConfiguration(lootChestFile); + loadLootFromConfig(); + loadActiveChests(); // Kisten beim Start laden FileConfiguration cfg = plugin.getConfig(); this.despawnMillis = cfg.getLong("lootchest.despawn-minutes", 10) * 60 * 1000; @@ -49,6 +60,102 @@ public class LootChestManager implements Listener, CommandExecutor { startLootLimitResetTask(); } + /** + * Lädt gespeicherte Kisten aus der Datei und prüft, ob sie abgelaufen sind + */ + private void loadActiveChests() { + if (!lootChestFile.exists()) return; + + long now = System.currentTimeMillis(); + List toRemoveFile = new ArrayList<>(); + + for (String key : lootChestConfig.getKeys(false)) { + try { + long spawnTime = lootChestConfig.getLong(key); + + // Prüfen ob die Zeit abgelaufen ist (inklusive Offline-Zeit) + if (now - spawnTime >= despawnMillis) { + String[] parts = key.split("_"); + if (parts.length == 4) { + World w = Bukkit.getWorld(parts[0]); + if (w != null) { + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + Location loc = new Location(w, x, y, z); + + // Kiste physikalisch löschen falls noch vorhanden + if (loc.getBlock().getType() == Material.CHEST) { + loc.getBlock().setType(Material.AIR); + } + toRemoveFile.add(loc); + } + } + } else { + // Kiste ist noch gültig -> in den Speicher laden + String[] parts = key.split("_"); + if (parts.length == 4) { + World w = Bukkit.getWorld(parts[0]); + if (w != null) { + int x = Integer.parseInt(parts[1]); + int y = Integer.parseInt(parts[2]); + int z = Integer.parseInt(parts[3]); + Location loc = new Location(w, x, y, z); + + // Integritätsprüfung: Ist da auch wirklich eine Kiste? + if (loc.getBlock().getType() == Material.CHEST) { + activeChests.add(loc); + chestSpawnTimes.put(loc, spawnTime); + } else { + // Datei sagt ja, Welt sagt nein -> Cleanup in Datei + toRemoveFile.add(loc); + } + } + } + } + } catch (Exception e) { + plugin.getLogger().warning("Fehler beim Laden einer Lootkiste: " + key); + } + } + + // Alte Einträge aus der Datei entfernen + for (Location loc : toRemoveFile) { + String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ(); + lootChestConfig.set(key, null); + } + saveActiveChests(); + } + + /** + * Speichert die aktuellen aktiven Kisten in die Datei + */ + private void saveActiveChests() { + lootChestConfig = new YamlConfiguration(); + for (Map.Entry entry : chestSpawnTimes.entrySet()) { + Location loc = entry.getKey(); + String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ(); + lootChestConfig.set(key, entry.getValue()); + } + try { + lootChestConfig.save(lootChestFile); + } catch (IOException e) { + plugin.getLogger().severe("Konnte lootchests.yml nicht speichern!"); + e.printStackTrace(); + } + } + + /** + * Entfernt eine Kiste komplett aus System und Datei (z.B. beim Looten) + */ + private void removeChestData(Location loc) { + activeChests.remove(loc); + chestSpawnTimes.remove(loc); + + String key = loc.getWorld().getName() + "_" + loc.getBlockX() + "_" + loc.getBlockY() + "_" + loc.getBlockZ(); + lootChestConfig.set(key, null); + saveActiveChests(); + } + private void loadLootFromConfig() { FileConfiguration cfg = plugin.getConfig(); lootItems.clear(); @@ -75,7 +182,11 @@ public class LootChestManager implements Listener, CommandExecutor { new BukkitRunnable() { @Override public void run() { + // Falls deaktiviert, nichts tun + // Hinweis: Die Logik hier war "!enabled ... return; clearAll". + // Das bedeutet: Wenn enabled=true -> !enabled=false -> kein return -> clearAll. if (!plugin.getConfig().getBoolean("lootchest.enabled", true)) return; + clearAllActiveChests(); for (int i = 0; i < spawnCount; i++) { spawnRandomLootChest(); @@ -88,26 +199,36 @@ public class LootChestManager implements Listener, CommandExecutor { } private void startChestDespawnTask() { + // Prüft jede Minute (20 Ticks * 60) new BukkitRunnable() { @Override public void run() { long now = System.currentTimeMillis(); + + // Sicheres Iterieren um ConcurrentModificationException zu vermeiden Iterator it = activeChests.iterator(); while (it.hasNext()) { Location loc = it.next(); Long spawnTime = chestSpawnTimes.get(loc); + if (spawnTime != null && now - spawnTime >= despawnMillis) { if (loc.getBlock().getType() == Material.CHEST) { loc.getBlock().setType(Material.AIR); } - it.remove(); + + // Manuell aus den Datenstrukturen entfernen chestSpawnTimes.remove(loc); + it.remove(); // Aus der Liste entfernen (Thread-Safe) + Bukkit.broadcastMessage(color( plugin.getLangConfig().getString("lootchest.despawn-msg", "&eEine Lootkiste ist von selbst verschwunden.") )); } } + + // Config einmal speichern (wichtig!) + saveActiveChests(); } }.runTaskTimer(plugin, 20L * 60, 20L * 60); } @@ -117,6 +238,7 @@ public class LootChestManager implements Listener, CommandExecutor { if (loc.getBlock().getType() == Material.CHEST) { loc.getBlock().setType(Material.AIR); } + removeChestData(loc); } activeChests.clear(); chestSpawnTimes.clear(); @@ -157,6 +279,8 @@ public class LootChestManager implements Listener, CommandExecutor { activeChests.add(loc); chestSpawnTimes.put(loc, System.currentTimeMillis()); + + saveActiveChests(); world.spawnParticle(Particle.FIREWORK, loc.clone().add(0.5, 1.0, 0.5), 20, 0.3, 0.5, 0.3); @@ -265,8 +389,9 @@ public class LootChestManager implements Listener, CommandExecutor { if (looted >= maxLootPerPlayer) return; playerLootCount.put(uuid, looted + 1); chestLoc.getBlock().setType(Material.AIR); - activeChests.remove(chestLoc); - chestSpawnTimes.remove(chestLoc); + + removeChestData(chestLoc); + Bukkit.broadcastMessage(color( plugin.getLangConfig().getString("lootchest.removed-msg", "&aEine Lootkiste wurde geleert und entfernt.") )); @@ -320,4 +445,4 @@ public class LootChestManager implements Listener, CommandExecutor { return stack; } } -} +} \ 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 2f73eda..13529b1 100644 --- a/src/main/java/de/viper/survivalplus/Manager/TablistManager.java +++ b/src/main/java/de/viper/survivalplus/Manager/TablistManager.java @@ -1,6 +1,7 @@ package de.viper.survivalplus.Manager; import de.viper.survivalplus.SurvivalPlus; +import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -25,6 +26,8 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class TablistManager implements Listener { @@ -70,18 +73,6 @@ public class TablistManager implements Listener { FileConfiguration config = plugin.getTablistConfig(); - - // Nicknames.yml laden - try { - File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml"); - if (!nicknameFile.exists()) { - plugin.saveResource("nicknames.yml", false); - } - this.nicknameConfig = YamlConfiguration.loadConfiguration(nicknameFile); - } catch (Exception ignored) { - this.nicknameConfig = null; // Keine Konsolenausgabe - } - // Konfigurationswerte laden this.enabled = config.getBoolean("enabled", true); this.serverName = config.getString("server-name", "SurvivalPlus"); @@ -97,7 +88,7 @@ public class TablistManager implements Listener { try { this.luckPerms = LuckPermsProvider.get(); } catch (Throwable e) { - luckPerms = null; // Keine Konsolenausgabe + luckPerms = null; } // Prüfen, ob PlaceholderAPI verfügbar ist @@ -140,6 +131,9 @@ 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 @@ -154,22 +148,24 @@ public class TablistManager implements Listener { for (Player player : Bukkit.getOnlinePlayers()) { try { - // Nickname oder Spielername verwenden + // Nickname abrufen (farbig) String displayName = getNickname(player); + // Falls kein Nickname, Name nehmen if (displayName == null || displayName.trim().isEmpty()) { displayName = player.getName(); } - // Spielername für die Tablist setzen + // Spielername für die Tablist setzen (Spielerliste auf der rechten Seite) String playerListName; - String prefix = getPlayerPrefix(player); + String rankPrefix = getPlayerPrefix(player); + if (luckPerms == null && !hasPlaceholderAPI) { playerListName = displayName; - updateNametag(player, "", displayName); + updateNametag(player, rankPrefix, displayName); } else { int ping = getPlayerPing(player); - playerListName = color(prefix + displayName + (ping >= 0 ? "&8 | &e" + ping + "ms" : "")); - updateNametag(player, prefix, displayName); + playerListName = color(rankPrefix + displayName + (ping >= 0 ? "&8 | &e" + ping + "ms" : "")); + updateNametag(player, rankPrefix, displayName); } player.setPlayerListName(playerListName); @@ -215,7 +211,7 @@ public class TablistManager implements Listener { done = tryStringMethod(player, header, footer); } - // 3) Keine Warnung bei Fehlschlag, da dies normal sein kann + // 3) Keine Warnung bei Fehlschlag } catch (Exception ignored) { // Keine Konsolenausgabe für Fehler bei der Tablist } @@ -228,7 +224,22 @@ public class TablistManager implements Listener { } /** - * Nickname aus nicknames.yml abrufen + * Lädt die nicknames.yml neu + */ + private void reloadNicknameConfig() { + try { + File nicknameFile = new File(plugin.getDataFolder(), "nicknames.yml"); + if (!nicknameFile.exists()) { + plugin.saveResource("nicknames.yml", false); + } + this.nicknameConfig = YamlConfiguration.loadConfiguration(nicknameFile); + } catch (Exception ignored) { + this.nicknameConfig = null; + } + } + + /** + * Nickname abrufen und formatieren */ private String getNickname(Player player) { if (nicknameConfig == null) return null; @@ -236,161 +247,130 @@ public class TablistManager implements Listener { String uuid = player.getUniqueId().toString(); String nickname = nicknameConfig.getString(uuid); if (nickname != null && !nickname.trim().isEmpty()) { - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".nickname", nickname); - debugConfig.save(debugFile); - } catch (Exception ignored) {} - return nickname; + return translateNickColors(nickname); } - } catch (Exception ignored) { - // Keine Konsolenausgabe - } + } catch (Exception ignored) {} return null; } /** * Nametag über dem Kopf aktualisieren */ - private void updateNametag(Player player, String prefix, String displayName) { + private void updateNametag(Player player, String rankPrefix, String nickname) { if (scoreboard == null) return; try { - String teamName = generateTeamName(player, prefix); + String teamName = generateTeamName(player, rankPrefix); Team team = scoreboard.getTeam(teamName); - // Team erstellen oder aktualisieren if (team == null) { team = scoreboard.registerNewTeam(teamName); } - // Prefix zwingend setzen, wenn LuckPerms installiert ist - String coloredPrefix = color(prefix != null && !prefix.trim().isEmpty() ? prefix : (luckPerms != null ? "&7[Spieler] " : "")); - team.setPrefix(coloredPrefix); + // 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); - // Spieler dem Team hinzufügen - String entry = displayName != null && !displayName.trim().isEmpty() ? displayName : player.getName(); + // 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, um Konflikte zu vermeiden + // Spieler aus anderen Teams entfernen for (Team existingTeam : scoreboard.getTeams()) { if (!existingTeam.getName().equals(teamName)) { existingTeam.removeEntry(entry); - existingTeam.removeEntry(player.getName()); } } team.addEntry(entry); } - // Team für alle Spieler sichtbar machen + // Scoreboard für alle Spieler synchronisieren for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { if (onlinePlayer.getScoreboard() != scoreboard) { onlinePlayer.setScoreboard(scoreboard); } } - // Debug-Ausgabe für Nametag - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".nametag_prefix", coloredPrefix); - debugConfig.set(player.getUniqueId().toString() + ".nametag_entry", entry); - debugConfig.save(debugFile); - } catch (Exception ignored) {} - } catch (Exception ignored) { - // Keine Konsolenausgabe - } + } catch (Exception ignored) {} } + /** + * Farben übersetzen + */ + 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); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String hexCode = matcher.group(); + String replacement = ChatColor.of(hexCode).toString(); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}"); + /** * Eindeutigen Teamnamen generieren */ private String generateTeamName(Player player, String prefix) { - // Verwende UUID für eindeutige Teamnamen, falls kein Prefix vorhanden if (prefix == null || prefix.trim().isEmpty()) { return "nametag_" + player.getUniqueId().toString().substring(0, 8); } - // Verwende Prefix für Teamnamen, max 16 Zeichen (Bukkit-Beschränkung) String sanitizedPrefix = prefix.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); return "prefix_" + sanitizedPrefix.substring(0, Math.min(sanitizedPrefix.length(), 12)); } /** - * Spieler-Prefix abrufen (LuckPerms primär, PlaceholderAPI als Fallback) + * Rang-Prefix abrufen */ private String getPlayerPrefix(Player player) { - // Wenn LuckPerms installiert ist, Prefix zwingend abrufen if (luckPerms != null) { - // Versuche LuckPerms-API zuerst try { User user = luckPerms.getPlayerAdapter(Player.class).getUser(player); String prefix = user.getCachedData().getMetaData().getPrefix(); if (prefix != null && !prefix.trim().isEmpty()) { - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix); - debugConfig.save(debugFile); - } catch (Exception ignored) {} return prefix + " "; } - } catch (Exception ignored) { - // Keine Konsolenausgabe - } - - // Fallback auf PlaceholderAPI, wenn LuckPerms installiert ist + } catch (Exception ignored) {} if (hasPlaceholderAPI) { try { String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); if (prefix != null && !prefix.trim().isEmpty()) { - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix); - debugConfig.save(debugFile); - } catch (Exception ignored) {} return prefix + " "; } - } catch (Exception ignored) { - // Keine Konsolenausgabe - } + } catch (Exception ignored) {} } - - // Standard-Prefix, wenn LuckPerms installiert ist, aber kein Prefix definiert - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".prefix", "&7[Spieler]"); - debugConfig.save(debugFile); - } catch (Exception ignored) {} return "&7[Spieler] "; } - - // Wenn LuckPerms nicht installiert ist, aber PlaceholderAPI vorhanden ist if (hasPlaceholderAPI) { try { String prefix = PlaceholderAPI.setPlaceholders(player, "%luckperms_prefix%"); if (prefix != null && !prefix.trim().isEmpty()) { - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".prefix", prefix); - debugConfig.save(debugFile); - } catch (Exception ignored) {} return prefix + " "; } - } catch (Exception ignored) { - // Keine Konsolenausgabe - } + } catch (Exception ignored) {} } - - // Standard-Prefix, wenn weder LuckPerms noch PlaceholderAPI einen Prefix liefern - try { - File debugFile = new File(plugin.getDataFolder(), "debug.yml"); - FileConfiguration debugConfig = YamlConfiguration.loadConfiguration(debugFile); - debugConfig.set(player.getUniqueId().toString() + ".prefix", "&7[Spieler]"); - debugConfig.save(debugFile); - } catch (Exception ignored) {} return "&7[Spieler] "; } @@ -403,32 +383,33 @@ public class TablistManager implements Listener { Object entityPlayer = getHandle.invoke(player); return entityPlayer.getClass().getField("ping").getInt(entityPlayer); } catch (Exception ignored) { - return -1; // Keine Konsolenausgabe + return -1; } } /** - * Chat-Format modifizieren + * 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) { - // Keine Konsolenausgabe - } + } catch (Exception ignored) {} } /** - * Adventure-Variante mit Components + * Adventure-Variante */ private boolean tryAdventureComponent(Player player, String headerRaw, String footerRaw) { try { @@ -449,7 +430,6 @@ public class TablistManager implements Listener { headerComp = textMethod.invoke(null, color(headerRaw)); footerComp = textMethod.invoke(null, color(footerRaw)); } else { - // Fallback: MiniMessage try { Class miniMsgClass = Class.forName("net.kyori.adventure.text.minimessage.MiniMessage"); Method miniMsgSingleton = miniMsgClass.getMethod("miniMessage"); @@ -457,9 +437,7 @@ public class TablistManager implements Listener { Method deserialize = miniMsgClass.getMethod("deserialize", String.class); headerComp = deserialize.invoke(miniMsg, headerRaw); footerComp = deserialize.invoke(miniMsg, footerRaw); - } catch (Exception ignored) { - // Kein MiniMessage - } + } catch (Exception ignored) {} } if (headerComp == null || footerComp == null) return false; @@ -512,9 +490,6 @@ public class TablistManager implements Listener { return false; } - /** - * Hilfsmethode für Reflection - */ private Method findMethod(Class clazz, String name, Class... paramTypes) { Class current = clazz; while (current != null) { @@ -527,9 +502,6 @@ public class TablistManager implements Listener { return null; } - /** - * Farbcode-Konvertierung (& -> §) - */ private String color(String msg) { if (msg == null) return ""; return msg.replace("&", "§"); diff --git a/src/main/java/de/viper/survivalplus/SurvivalPlus.java b/src/main/java/de/viper/survivalplus/SurvivalPlus.java index c900b8b..49b2803 100644 --- a/src/main/java/de/viper/survivalplus/SurvivalPlus.java +++ b/src/main/java/de/viper/survivalplus/SurvivalPlus.java @@ -335,9 +335,16 @@ public class SurvivalPlus extends JavaPlugin { getCommand("lootchests").setExecutor(lootChestManager); getCommand("tploot").setExecutor(lootChestManager); getCommand("claim").setExecutor(new ClaimCommand(this)); - BlockManager blockManager = new BlockManager(); + // HIER: (this) hinzugefügt, damit der BlockManager die Datei speichern kann + BlockManager blockManager = new BlockManager(this); FileConfiguration config = getConfig(); BackpackRecipe.register(this, langConfig); + + // NEU: Befehle registrieren + getCommand("block").setExecutor(new BlockCommand(blockManager, getConfig())); + getCommand("unblock").setExecutor(new UnblockCommand(blockManager, getConfig())); + getCommand("blocklist").setExecutor(new BlockListCommand(blockManager, getConfig())); + pluginManager.registerEvents(new ChatBlockListener(blockManager), this); pluginManager.registerEvents(new InventoryClickListener(this), this); pluginManager.registerEvents(sitListener, this); diff --git a/src/main/java/de/viper/survivalplus/commands/BlockCommand.java b/src/main/java/de/viper/survivalplus/commands/BlockCommand.java index 5f1b731..e3905ac 100644 --- a/src/main/java/de/viper/survivalplus/commands/BlockCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/BlockCommand.java @@ -2,9 +2,12 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.Manager.BlockManager; import org.bukkit.Bukkit; -import org.bukkit.command.*; -import org.bukkit.entity.Player; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; public class BlockCommand implements CommandExecutor { @@ -20,28 +23,40 @@ public class BlockCommand implements CommandExecutor { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player player)) { - sender.sendMessage(config.getString("messages.general.only_players")); + sender.sendMessage(color(config.getString("messages.general.only_players", "§cDieser Befehl ist nur für Spieler!"))); + return true; + } + + if (!player.hasPermission("survivalplus.block")) { + player.sendMessage(color(config.getString("messages.general.no_permission", "§cDu hast keine Berechtigung für diesen Befehl!"))); return true; } if (args.length != 1) { - player.sendMessage(config.getString("messages.block.usage")); + player.sendMessage(color(config.getString("messages.block.usage", "§cVerwendung: /block "))); return true; } Player target = Bukkit.getPlayerExact(args[0]); if (target == null || target == player) { - player.sendMessage(config.getString("messages.block.invalid_player")); + player.sendMessage(color(config.getString("messages.block.invalid_player", "§cDieser Spieler konnte nicht gefunden werden oder bist du selbst."))); return true; } if (blockManager.hasBlocked(player, target)) { - player.sendMessage(config.getString("messages.block.already_blocked").replace("%player%", target.getName())); + String msg = config.getString("messages.block.already_blocked", "&cDieser Spieler ist bereits blockiert!"); + player.sendMessage(color(msg.replace("%player%", target.getName()))); } else { blockManager.blockPlayer(player, target); - player.sendMessage(config.getString("messages.block.blocked").replace("%player%", target.getName())); + String msg = config.getString("messages.block.blocked", "&aDu hast %player% blockiert."); + player.sendMessage(color(msg.replace("%player%", target.getName()))); } return true; } -} + + private String color(String msg) { + if (msg == null) return ""; + return ChatColor.translateAlternateColorCodes('&', msg); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/BlockListCommand.java b/src/main/java/de/viper/survivalplus/commands/BlockListCommand.java index 3184c43..e3cfb66 100644 --- a/src/main/java/de/viper/survivalplus/commands/BlockListCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/BlockListCommand.java @@ -2,9 +2,12 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.Manager.BlockManager; import org.bukkit.Bukkit; -import org.bukkit.command.*; -import org.bukkit.entity.Player; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import java.util.stream.Collectors; @@ -22,22 +25,30 @@ public class BlockListCommand implements CommandExecutor { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player player)) { - sender.sendMessage(config.getString("messages.general.only_players")); + // Fallback: "Nur Spieler..." falls Key fehlt + sender.sendMessage(color(config.getString("messages.general.only_players", "§cNur Spieler können diesen Befehl ausführen!"))); return true; } var blocked = blockManager.getBlockedPlayers(player); if (blocked.isEmpty()) { - player.sendMessage(config.getString("messages.blocklist.no_blocked_players")); + player.sendMessage(color(config.getString("messages.blocklist.no_blocked_players", "§cDu hast keine Spieler blockiert."))); return true; } String list = blocked.stream() .map(Bukkit::getOfflinePlayer) - .map(p -> p.getName() != null ? p.getName() : "Unbekannt") + .map(p -> p.getName() != null ? p.getName() : "§eUnbekannt") .collect(Collectors.joining(", ")); - player.sendMessage(config.getString("messages.blocklist.blocked_players").replace("%list%", list)); + // WICHTIG: Default-String als zweiten Parameter, um NullPointerException zu vermeiden + String rawMessage = config.getString("messages.blocklist.blocked_players", "&eBlockierte Spieler: %list%"); + player.sendMessage(color(rawMessage.replace("%list%", list))); return true; } -} + + private String color(String msg) { + if (msg == null) return ""; + return ChatColor.translateAlternateColorCodes('&', msg); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/NickCommand.java b/src/main/java/de/viper/survivalplus/commands/NickCommand.java index fd7d79c..c6b033f 100644 --- a/src/main/java/de/viper/survivalplus/commands/NickCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/NickCommand.java @@ -34,6 +34,23 @@ public class NickCommand implements CommandExecutor { if (args.length == 0) { player.sendMessage("§eBenutzung: /nick "); + player.sendMessage("§eVerwende /nick off um den Nickname zu entfernen."); + return true; + } + + // Befehl zum 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 + player.setDisplayName(player.getName()); + player.setPlayerListName(player.getName()); + + player.sendMessage("§aDein Nickname wurde entfernt."); return true; } @@ -87,4 +104,4 @@ public class NickCommand implements CommandExecutor { matcher.appendTail(sb); return sb.toString(); } -} +} \ 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 a21454b..e1eb323 100644 --- a/src/main/java/de/viper/survivalplus/commands/SitCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/SitCommand.java @@ -3,6 +3,8 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.SurvivalPlus; import de.viper.survivalplus.listeners.SitListener; import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -44,14 +46,30 @@ public class SitCommand implements CommandExecutor { player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); 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; + } - // Setze den Spieler mittig auf den Block und leicht oberhalb (Fix) + // Prüfe ob unter dem Spieler Luft ist (Verhindert Sitzen beim Bauen in der Luft) + 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 Location location = player.getLocation(); location.setX(location.getBlockX() + 0.5); - location.setY(location.getBlockY() + 0.25); location.setZ(location.getBlockZ() + 0.5); + location.setY(location.getBlockY()); sitListener.sitPlayer(player, location); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/TradeAcceptCommand.java b/src/main/java/de/viper/survivalplus/commands/TradeAcceptCommand.java index 5f1f5c4..8e015b3 100644 --- a/src/main/java/de/viper/survivalplus/commands/TradeAcceptCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/TradeAcceptCommand.java @@ -32,4 +32,4 @@ public class TradeAcceptCommand implements CommandExecutor { tradeManager.acceptTrade(target, args[0]); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/TradeCommand.java b/src/main/java/de/viper/survivalplus/commands/TradeCommand.java index e31d69e..751793a 100644 --- a/src/main/java/de/viper/survivalplus/commands/TradeCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/TradeCommand.java @@ -39,4 +39,4 @@ public class TradeCommand implements CommandExecutor { tradeManager.requestTrade(sender, target); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/commands/UnblockCommand.java b/src/main/java/de/viper/survivalplus/commands/UnblockCommand.java index 460fc72..96b02b6 100644 --- a/src/main/java/de/viper/survivalplus/commands/UnblockCommand.java +++ b/src/main/java/de/viper/survivalplus/commands/UnblockCommand.java @@ -2,9 +2,12 @@ package de.viper.survivalplus.commands; import de.viper.survivalplus.Manager.BlockManager; import org.bukkit.Bukkit; -import org.bukkit.command.*; -import org.bukkit.entity.Player; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; public class UnblockCommand implements CommandExecutor { @@ -20,29 +23,40 @@ public class UnblockCommand implements CommandExecutor { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player player)) { - sender.sendMessage(config.getString("messages.general.only_players")); + sender.sendMessage(color(config.getString("messages.general.only_players", "§cDieser Befehl ist nur für Spieler!"))); + return true; + } + + if (!player.hasPermission("survivalplus.unblock")) { + player.sendMessage(color(config.getString("messages.general.no_permission", "§cDu hast keine Berechtigung für diesen Befehl!"))); return true; } if (args.length != 1) { - player.sendMessage(config.getString("messages.unblock.usage")); + player.sendMessage(color(config.getString("messages.block.usage", "§cVerwendung: /unblock "))); return true; } Player target = Bukkit.getPlayerExact(args[0]); if (target == null || target == player) { - player.sendMessage(config.getString("messages.unblock.invalid_player")); + player.sendMessage(color(config.getString("messages.block.invalid_player", "§cDieser Spieler konnte nicht gefunden werden oder bist du selbst."))); return true; } - if (blockManager.hasBlocked(player, target)) { - blockManager.unblockPlayer(player, target); - player.sendMessage(config.getString("messages.unblock.unblocked").replace("%player%", target.getName())); - target.sendMessage(config.getString("messages.unblock.unblocked_by").replace("%player%", player.getName())); + if (!blockManager.hasBlocked(player, target)) { + String msg = config.getString("messages.block.not_blocked", "&cDieser Spieler ist nicht blockiert."); + player.sendMessage(color(msg.replace("%player%", target.getName()))); } else { - player.sendMessage(config.getString("messages.unblock.not_blocked").replace("%player%", target.getName())); + blockManager.unblockPlayer(player, target); + String msg = config.getString("messages.block.unblocked", "&aDu hast %player% entblockt."); + player.sendMessage(color(msg.replace("%player%", target.getName()))); } return true; } -} + + private String color(String msg) { + if (msg == null) return ""; + return ChatColor.translateAlternateColorCodes('&', msg); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/listeners/SitListener.java b/src/main/java/de/viper/survivalplus/listeners/SitListener.java index a816ca5..5d2e5b3 100644 --- a/src/main/java/de/viper/survivalplus/listeners/SitListener.java +++ b/src/main/java/de/viper/survivalplus/listeners/SitListener.java @@ -1,19 +1,23 @@ package de.viper.survivalplus.listeners; import de.viper.survivalplus.SurvivalPlus; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.Block; +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.block.Action; 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.event.player.PlayerTeleportEvent; import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.event.block.Action; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -22,9 +26,11 @@ 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 public SitListener(SurvivalPlus plugin) { this.plugin = plugin; + cleanUpGhostStands(); // Entferne alte Stands beim Laden } public boolean isSitting(Player player) { @@ -39,11 +45,17 @@ 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); + armorStand.setMarker(true); // Keine Hitbox armorStand.setInvisible(true); armorStand.setInvulnerable(true); + armorStand.addScoreboardTag(SIT_TAG); // Tag für Cleanup + + // Füge Spieler als Passenger hinzu armorStand.addPassenger(player); + // Teleportiere den Spieler nochmals zur Sicherheit (Anti-Slide) + player.teleport(location); + sittingPlayers.put(player.getUniqueId(), armorStand); FileConfiguration lang = plugin.getLangConfig(); player.sendMessage(lang.getString("sit.success", "§aDu hast dich hingesetzt!")); @@ -54,9 +66,15 @@ public class SitListener implements Listener { UUID playerId = player.getUniqueId(); ArmorStand armorStand = sittingPlayers.remove(playerId); if (armorStand != null) { + if (player.isValid()) { + armorStand.removePassenger(player); + } armorStand.remove(); + FileConfiguration lang = plugin.getLangConfig(); - player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); + if (player.isOnline()) { + player.sendMessage(lang.getString("sit.stand-up", "§aDu bist aufgestanden!")); + } plugin.getLogger().log(Level.FINE, "Spieler " + player.getName() + " ist aufgestanden"); } } @@ -71,7 +89,7 @@ public class SitListener implements Listener { FileConfiguration lang = plugin.getLangConfig(); if (!player.hasPermission("survivalplus.sit")) { - player.sendMessage(lang.getString("no-permission", "§cDu hast keine Berechtigung für diesen Befehl!")); + // Kein Feedback nötig, wenn keine Permission, um nicht zu spammen return; } @@ -80,18 +98,31 @@ 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) + } + // 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 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); + + // Bei Treppen die Höhe anpassen (Y + 0.5 ist meist genau die "Sitzfläche") + location.setY(location.getY() + 0.5); + sitPlayer(player, location); event.setCancelled(true); // Verhindere andere Interaktionen mit der Treppe } @@ -104,13 +135,24 @@ public class SitListener implements Listener { return; } - // Prüfe, ob der Spieler sich bewegt hat (nur Positionsänderung, nicht Kopfbewegung) + // 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 (from.getX() != to.getX() || from.getY() != to.getY() || from.getZ() != to.getZ()) { + if (to == null) return; + + if (from.distanceSquared(to) > 0.0025) { // ca 0.05 Blöcke Abstand standUp(player); } } + + @EventHandler + public void onPlayerTeleport(PlayerTeleportEvent event) { + // Wenn teleportiert, absteigen + if (sittingPlayers.containsKey(event.getPlayer().getUniqueId())) { + standUp(event.getPlayer()); + } + } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { @@ -125,4 +167,20 @@ public class SitListener implements Listener { 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()) { + for (Entity entity : world.getEntities()) { + if (entity instanceof ArmorStand) { + if (entity.getScoreboardTags().contains(SIT_TAG)) { + entity.remove(); + } + } + } + } + plugin.getLogger().info("Ghost Stands (Sit) bereinigt."); + }, 20L); + } +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/recipe/BackpackRecipe.java b/src/main/java/de/viper/survivalplus/recipe/BackpackRecipe.java index 6620eb9..868cb5c 100644 --- a/src/main/java/de/viper/survivalplus/recipe/BackpackRecipe.java +++ b/src/main/java/de/viper/survivalplus/recipe/BackpackRecipe.java @@ -6,6 +6,7 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.RecipeChoice; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; @@ -16,23 +17,34 @@ public class BackpackRecipe { ItemStack backpack = new ItemStack(Material.CHEST); ItemMeta meta = backpack.getItemMeta(); if (meta != null) { - meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', langConfig.getString("backpack.name", "&eRucksack"))); + // Fallback falls der Key in der lang.yml fehlt + String displayName = langConfig.getString("backpack.name", "&eRucksack"); + meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', displayName)); backpack.setItemMeta(meta); } NamespacedKey key = new NamespacedKey(plugin, "backpack"); + // Rezept erstellen ShapedRecipe recipe = new ShapedRecipe(key, backpack); + + // Form festlegen recipe.shape( - "S L", // Faden, leer, Leder - " C ", // leer, Truhe, leer - "S L" // Faden, leer, Leder + "S L", + " C ", + "S L" ); - recipe.setIngredient('S', Material.STRING); // Faden - recipe.setIngredient('L', Material.LEATHER); // Leder - recipe.setIngredient('C', Material.CHEST); // Truhe + + // Zutaten festlegen (mit RecipeChoice für bessere Kompatibilität) + recipe.setIngredient('S', new RecipeChoice.MaterialChoice(Material.STRING)); + recipe.setIngredient('L', new RecipeChoice.MaterialChoice(Material.LEATHER)); + recipe.setIngredient('C', new RecipeChoice.MaterialChoice(Material.CHEST)); + + // WICHTIG: Setzt eine eigene Gruppe. + // Verhindert Konflikte mit Vanilla-Rezepten im Rezeptbuch. + recipe.setGroup("survivalplus"); Bukkit.addRecipe(recipe); - plugin.getLogger().info("Backpack Rezept wurde registriert."); + plugin.getLogger().info("Backpack Rezept wurde erfolgreich registriert (Gruppe: survivalplus)."); } } \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/trade/TradeManager.java b/src/main/java/de/viper/survivalplus/trade/TradeManager.java index 5178aa6..5091aa2 100644 --- a/src/main/java/de/viper/survivalplus/trade/TradeManager.java +++ b/src/main/java/de/viper/survivalplus/trade/TradeManager.java @@ -73,13 +73,14 @@ public class TradeManager { return; } - TradeSession session = new TradeSession(plugin, sender, receiver); + // Neue Session starten + TradeSession session = new TradeSession(plugin, sender, receiver, this); Bukkit.getPluginManager().registerEvents(session, plugin); activeTrades.put(sender.getUniqueId(), session); activeTrades.put(receiver.getUniqueId(), session); - session.openInventories(); + session.openInventory(); sender.sendMessage(plugin.getLangConfig().getString("trade.started-sender", "§aTrade gestartet mit %player%").replace("%player%", receiver.getName())); receiver.sendMessage(plugin.getLangConfig().getString("trade.started-receiver", "§a%player% hat dich zu einem Trade eingeladen.").replace("%player%", sender.getName())); @@ -91,7 +92,7 @@ public class TradeManager { Player r = session.getReceiver(); if (s != null) activeTrades.remove(s.getUniqueId()); if (r != null) activeTrades.remove(r.getUniqueId()); - session.endSession(); + session.closeInventory(); } public TradeSession getTrade(Player player) { @@ -103,4 +104,4 @@ public class TradeManager { if (player == null) return false; return activeTrades.containsKey(player.getUniqueId()); } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/trade/TradeSession.java b/src/main/java/de/viper/survivalplus/trade/TradeSession.java index 78c00d5..69d3697 100644 --- a/src/main/java/de/viper/survivalplus/trade/TradeSession.java +++ b/src/main/java/de/viper/survivalplus/trade/TradeSession.java @@ -16,150 +16,297 @@ import org.bukkit.inventory.meta.ItemMeta; public class TradeSession implements Listener { private final SurvivalPlus plugin; + private final TradeManager tradeManager; private final Player sender; private final Player receiver; - private final Inventory invSender; - private final Inventory invReceiver; + // Wir benutzen nur EIN Inventar für beide (Size 54 = Double Chest) + // Slots 0-17: Angebot von Sender (Editierbar für Sender) + // Slots 18-35: Angebot von Receiver (Editierbar für Receiver) + // Slots 36-44: Trennung / Status + // Slot 45: Abbrechen + // Slot 53: Bestätigen + private final Inventory tradeInv; private boolean senderConfirmed = false; private boolean receiverConfirmed = false; - private boolean ended = false; // Flag gegen Rekursion + private boolean active = true; - public TradeSession(SurvivalPlus plugin, Player sender, Player receiver) { + public TradeSession(SurvivalPlus plugin, Player sender, Player receiver, TradeManager tradeManager) { this.plugin = plugin; + this.tradeManager = tradeManager; this.sender = sender; this.receiver = receiver; - String titleForSender = plugin.getLangConfig().getString("trade.inventory-title", "Handel mit %player%") - .replace("%player%", receiver.getName()); - String titleForReceiver = plugin.getLangConfig().getString("trade.inventory-title", "Handel mit %player%") - .replace("%player%", sender.getName()); - - this.invSender = Bukkit.createInventory(sender, 27, titleForSender); - this.invReceiver = Bukkit.createInventory(receiver, 27, titleForReceiver); - - addConfirmButton(invSender); - addConfirmButton(invReceiver); + String title = plugin.getLangConfig().getString("trade.inventory-title", "Handel") + .replace("%player%", receiver.getName()) + .replace("%other%", sender.getName()); + + // Wir nutzen null als Owner, damit es ein gemeinsames Inventar ist + this.tradeInv = Bukkit.createInventory(null, 54, title); + setupLayout(); } - private void addConfirmButton(Inventory inv) { - ItemStack confirm = new ItemStack(Material.LIME_CONCRETE); - ItemMeta meta = confirm.getItemMeta(); - meta.setDisplayName(plugin.getLangConfig().getString("trade.confirm-button", "§aBestätigen")); - confirm.setItemMeta(meta); - inv.setItem(26, confirm); + private void setupLayout() { + // Trennung zwischen den beiden Angeboten (Glas) + ItemStack glass = new ItemStack(Material.GRAY_STAINED_GLASS_PANE); + ItemMeta glassMeta = glass.getItemMeta(); + glassMeta.setDisplayName(" "); + glass.setItemMeta(glassMeta); + + for(int i = 36; i < 45; i++) { + tradeInv.setItem(i, glass); + } + + // Buttons initialisieren + updateStatusItem(); + updateButtons(); + } + + private void updateButtons() { + // Abbrechen Button (Rot) + ItemStack cancel = new ItemStack(Material.RED_CONCRETE); + ItemMeta cancelMeta = cancel.getItemMeta(); + cancelMeta.setDisplayName(plugin.getLangConfig().getString("trade.cancel-button", "§cAbbrechen")); + cancel.setItemMeta(cancelMeta); + tradeInv.setItem(45, cancel); + + // Bestätigen Button + Material confirmMat = Material.LIME_CONCRETE; + String confirmName = "§aBestätigen"; + + // Wenn beide bestätigt haben, ändert sich der Button + if (senderConfirmed && receiverConfirmed) { + confirmMat = Material.GOLD_BLOCK; // Signalisiert Tauschvorgang + confirmName = "§6Handel läuft..."; + } else if (senderConfirmed) { + confirmMat = Material.YELLOW_CONCRETE; + confirmName = "§eWarte auf Partner..."; + } else if (receiverConfirmed) { + confirmMat = Material.YELLOW_CONCRETE; + confirmName = "§eWarte auf Partner..."; + } + + ItemStack confirm = new ItemStack(confirmMat); + ItemMeta confirmMeta = confirm.getItemMeta(); + confirmMeta.setDisplayName(confirmName); + confirm.setItemMeta(confirmMeta); + tradeInv.setItem(53, confirm); + } + + private void updateStatusItem() { + // Slot 40 zeigt den Status an + String statusText = "§eWarte auf Bestätigung..."; + + if (senderConfirmed && !receiverConfirmed) { + statusText = "§a" + sender.getName() + " §7hat bestätigt."; + } else if (!senderConfirmed && receiverConfirmed) { + statusText = "§a" + receiver.getName() + " §7hat bestätigt."; + } else if (senderConfirmed && receiverConfirmed) { + statusText = "§6Handel wird ausgeführt..."; + } + + ItemStack status = new ItemStack(Material.BOOK); + ItemMeta statusMeta = status.getItemMeta(); + statusMeta.setDisplayName(statusText); + status.setItemMeta(statusMeta); + tradeInv.setItem(40, status); } public Player getSender() { return sender; } public Player getReceiver() { return receiver; } - public void openInventories() { - if (sender.isOnline()) sender.openInventory(invSender); - if (receiver.isOnline()) receiver.openInventory(invReceiver); + public void openInventory() { + if (sender.isOnline()) sender.openInventory(tradeInv); + if (receiver.isOnline()) receiver.openInventory(tradeInv); } - private void returnItems(Player player, Inventory inventory) { - for (int i = 0; i < 26; i++) { - ItemStack item = inventory.getItem(i); - if (item != null) player.getInventory().addItem(item); - } - } - - public void endSession() { - if (ended) return; // Rekursion verhindern - ended = true; - - // Items zurückgeben - if (sender.isOnline()) returnItems(sender, invSender); - if (receiver.isOnline()) returnItems(receiver, invReceiver); - - // Inventories schließen - if (sender.isOnline() && sender.getOpenInventory().getTopInventory() == invSender) { + public void closeInventory() { + active = false; + if (sender.isOnline() && sender.getOpenInventory().getTopInventory() == tradeInv) { sender.closeInventory(); } - if (receiver.isOnline() && receiver.getOpenInventory().getTopInventory() == invReceiver) { + if (receiver.isOnline() && receiver.getOpenInventory().getTopInventory() == tradeInv) { receiver.closeInventory(); } - HandlerList.unregisterAll(this); } - private Inventory getOwnInventory(Player p) { - return p.getUniqueId().equals(sender.getUniqueId()) ? invSender : invReceiver; - } - - private Inventory getOtherInventory(Player p) { - return p.getUniqueId().equals(sender.getUniqueId()) ? invReceiver : invSender; - } - - private void updateOtherView(Player p) { - Inventory own = getOwnInventory(p); - Inventory other = getOtherInventory(p); - for (int i = 0; i < 26; i++) { - other.setItem(i, own.getItem(i)); + private void returnItems() { + // Gibt alle Items zurück, falls der Handel abgebrochen wurde + for (int i = 0; i < 36; i++) { + ItemStack item = tradeInv.getItem(i); + if (item != null) { + // Bestimmen, wem das Item gehört + if (i < 18) { + if (sender.isOnline()) sender.getInventory().addItem(item); + } else { + if (receiver.isOnline()) receiver.getInventory().addItem(item); + } + tradeInv.setItem(i, null); // Slot leeren + } } } private void executeTrade() { - for (int i = 0; i < 26; i++) { - ItemStack itemFromSender = invSender.getItem(i); - ItemStack itemFromReceiver = invReceiver.getItem(i); + // Tauschvorgang + // 1. Sender Items zu Receiver + for (int i = 0; i < 18; i++) { + ItemStack item = tradeInv.getItem(i); + if (item != null) { + if (receiver.isOnline()) receiver.getInventory().addItem(item); + tradeInv.setItem(i, null); + } + } - if (itemFromSender != null) sender.getInventory().addItem(itemFromSender); - if (itemFromReceiver != null) receiver.getInventory().addItem(itemFromReceiver); + // 2. Receiver Items zu Sender + for (int i = 18; i < 36; i++) { + ItemStack item = tradeInv.getItem(i); + if (item != null) { + if (sender.isOnline()) sender.getInventory().addItem(item); + tradeInv.setItem(i, null); + } } String success = plugin.getLangConfig().getString("trade.success", "§aHandel erfolgreich abgeschlossen!"); if (sender.isOnline()) sender.sendMessage(success); if (receiver.isOnline()) receiver.sendMessage(success); - endSession(); - } - - private void resetConfirmations() { - senderConfirmed = false; - receiverConfirmed = false; + closeInventory(); } @EventHandler public void onInventoryClick(InventoryClickEvent e) { + if (!active) return; if (!(e.getWhoClicked() instanceof Player p)) return; - if (!p.getUniqueId().equals(sender.getUniqueId()) && !p.getUniqueId().equals(receiver.getUniqueId())) return; + if (e.getClickedInventory() == null) return; - Inventory top = e.getView().getTopInventory(); - Inventory clicked = e.getClickedInventory(); + // Nur Klicks im Trade-Inventar behandeln + if (e.getClickedInventory().getSize() != 54 || !e.getClickedInventory().equals(tradeInv)) return; + // Auch Klicks im Bottom Inventory erlauben, aber nicht logisch behandeln (Bukkit regelt das) + + if (p.getUniqueId().equals(sender.getUniqueId())) { + handleSenderClick(e); + } else if (p.getUniqueId().equals(receiver.getUniqueId())) { + handleReceiverClick(e); + } + } + + private void handleSenderClick(InventoryClickEvent e) { int slot = e.getSlot(); - if (clicked == null) return; - - // Bestätigen-Button - if (slot == 26 && clicked.equals(top)) { + // Abbrechen + if (slot == 45) { e.setCancelled(true); - if (p.getUniqueId().equals(sender.getUniqueId())) senderConfirmed = true; - if (p.getUniqueId().equals(receiver.getUniqueId())) receiverConfirmed = true; - - p.sendMessage(plugin.getLangConfig().getString("trade.confirmed", "§aDu hast den Handel bestätigt!")); - - if (senderConfirmed && receiverConfirmed) executeTrade(); + sender.sendMessage(plugin.getLangConfig().getString("trade.cancelled", "§cHandel abgebrochen.")); + if (receiver.isOnline()) receiver.sendMessage(plugin.getLangConfig().getString("trade.cancelled-partner", "§cHandel wurde abgebrochen.")); + tradeManager.endTrade(this); return; } - // Nur das eigene Trade-Inventar editierbar - if (clicked.equals(top)) { - Bukkit.getScheduler().runTaskLater(plugin, () -> { - updateOtherView(p); - resetConfirmations(); - }, 1L); + // Bestätigen + if (slot == 53) { + e.setCancelled(true); + if (receiverConfirmed) { + executeTrade(); + } else { + senderConfirmed = !senderConfirmed; // Toggle + sender.sendMessage(senderConfirmed ? + plugin.getLangConfig().getString("trade.confirmed", "§aBestätigt!") : + "§cBestätigung zurückgezogen."); + updateButtons(); + updateStatusItem(); + } + return; } + + // Item-Bewegungen nur im eigenen Bereich (0-17) erlauben + if (slot >= 0 && slot <= 17) { + // Erlaubt + // Wenn ein Item bewegt wird, Bestätigungen zurücksetzen + if (e.getCurrentItem() != null || e.getCursor() != null) { + if (senderConfirmed || receiverConfirmed) { + senderConfirmed = false; + receiverConfirmed = false; + updateButtons(); + updateStatusItem(); + } + } + return; + } + + // Alle anderen Slots sperren (Partner-Seite, Glas, Status) + e.setCancelled(true); + } + + private void handleReceiverClick(InventoryClickEvent e) { + int slot = e.getSlot(); + + // Abbrechen + if (slot == 45) { + e.setCancelled(true); + receiver.sendMessage(plugin.getLangConfig().getString("trade.cancelled", "§cHandel abgebrochen.")); + if (sender.isOnline()) sender.sendMessage(plugin.getLangConfig().getString("trade.cancelled-partner", "§cHandel wurde abgebrochen.")); + tradeManager.endTrade(this); + return; + } + + // Bestätigen + if (slot == 53) { + e.setCancelled(true); + if (senderConfirmed) { + executeTrade(); + } else { + receiverConfirmed = !receiverConfirmed; // Toggle + receiver.sendMessage(receiverConfirmed ? + plugin.getLangConfig().getString("trade.confirmed", "§aBestätigt!") : + "§cBestätigung zurückgezogen."); + updateButtons(); + updateStatusItem(); + } + return; + } + + // Item-Bewegungen nur im eigenen Bereich (18-35) erlauben + if (slot >= 18 && slot <= 35) { + // Erlaubt + if (e.getCurrentItem() != null || e.getCursor() != null) { + if (senderConfirmed || receiverConfirmed) { + senderConfirmed = false; + receiverConfirmed = false; + updateButtons(); + updateStatusItem(); + } + } + return; + } + + // Alle anderen Slots sperren + e.setCancelled(true); } @EventHandler public void onInventoryClose(InventoryCloseEvent e) { + if (!active) return; if (!(e.getPlayer() instanceof Player p)) return; - if (p.getUniqueId().equals(sender.getUniqueId()) || p.getUniqueId().equals(receiver.getUniqueId())) { - // Kleine Verzögerung, um StackOverflow zu vermeiden - Bukkit.getScheduler().runTaskLater(plugin, this::endSession, 1L); + + // Wenn ein Spieler das Inventar schließt, wird der Handel abgebrochen + // Wir prüfen, ob noch Items drin sind, um Duplikate zu vermeiden + if (e.getInventory().equals(tradeInv)) { + if (p.getUniqueId().equals(sender.getUniqueId()) || p.getUniqueId().equals(receiver.getUniqueId())) { + // Verzögern, falls der Server den CloseEvent für beide fast gleichzeitig feuert + Bukkit.getScheduler().runTaskLater(plugin, () -> { + if (active) { // Nur wenn nicht schon durch executeTrade geschlossen + returnItems(); + if (p.getUniqueId().equals(sender.getUniqueId())) { + if (receiver.isOnline()) receiver.sendMessage("§cDer Partner hat den Handel abgebrochen."); + } else { + if (sender.isOnline()) sender.sendMessage("§cDer Partner hat den Handel abgebrochen."); + } + tradeManager.endTrade(this); + } + }, 1L); + } } } -} +} \ No newline at end of file diff --git a/src/main/java/de/viper/survivalplus/util/LockSystem.java b/src/main/java/de/viper/survivalplus/util/LockSystem.java index 028a8e1..568272d 100644 --- a/src/main/java/de/viper/survivalplus/util/LockSystem.java +++ b/src/main/java/de/viper/survivalplus/util/LockSystem.java @@ -2,7 +2,10 @@ package de.viper.survivalplus.util; import de.viper.survivalplus.SurvivalPlus; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.type.Chest; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -10,9 +13,11 @@ 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.block.Action; import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockRedstoneEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.scheduler.BukkitRunnable; @@ -27,6 +32,7 @@ public class LockSystem implements Listener, CommandExecutor { private final Map lockedBlocks = new HashMap<>(); private final Set lockMode = new HashSet<>(); private final Map lockTimeoutTasks = new HashMap<>(); + private final Map lastDenyMessage = new HashMap<>(); private final File lockFile; private FileConfiguration lockConfig; @@ -139,7 +145,16 @@ public class LockSystem implements Listener, CommandExecutor { if (lockedBlocks.containsKey(loc)) { player.sendMessage(plugin.getMessage("lock.already-locked")); } else { + // Lock den Block lockedBlocks.put(loc, new LockData(uuid.toString())); + + // Prüfe auf Doppeltruhe und locke beide Hälften + Block otherChest = getOtherChestHalf(block); + if (otherChest != null) { + Location otherLoc = otherChest.getLocation(); + lockedBlocks.put(otherLoc, new LockData(uuid.toString())); + } + saveLocks(); player.sendMessage(plugin.getMessage("lock.locked")); } @@ -149,7 +164,19 @@ public class LockSystem implements Listener, CommandExecutor { } if (!isLockable) return; - if (!lockedBlocks.containsKey(loc)) return; + + // Prüfe sowohl den geklickten Block als auch die andere Hälfte bei Doppeltruhen + if (!lockedBlocks.containsKey(loc)) { + Block otherChest = getOtherChestHalf(block); + if (otherChest != null) { + loc = otherChest.getLocation(); + if (!lockedBlocks.containsKey(loc)) { + return; + } + } else { + return; + } + } LockData lock = lockedBlocks.get(loc); String playerUUID = uuid.toString(); @@ -162,6 +189,176 @@ public class LockSystem implements Listener, CommandExecutor { event.setCancelled(true); } + // NEU: Verhindert das Öffnen von gelockten Türen durch Redstone (Druckplatten, Knöpfe, etc.) + @EventHandler(priority = EventPriority.HIGHEST) + public void onRedstoneChange(BlockRedstoneEvent event) { + Block block = event.getBlock(); + + // Prüfe, ob der Block eine Tür ist + if (!block.getType().name().contains("DOOR")) return; + + Location loc = block.getLocation(); + LockData lock = null; + + // Prüfe, ob die Tür gelockt ist + if (lockedBlocks.containsKey(loc)) { + lock = lockedBlocks.get(loc); + } else { + // Prüfe auch die andere Hälfte der Tür (oben/unten) + Location above = loc.clone().add(0, 1, 0); + Location below = loc.clone().add(0, -1, 0); + + if (lockedBlocks.containsKey(above)) { + lock = lockedBlocks.get(above); + } else if (lockedBlocks.containsKey(below)) { + lock = lockedBlocks.get(below); + } + } + + // Wenn keine Lock gefunden wurde, erlaube die Änderung + if (lock == null) return; + + // Wenn die Tür gelockt ist und Redstone-Signal empfängt, blockiere die Änderung + // Es sei denn, das Signal kommt von einem berechtigten Spieler (wird über Druckplatten-Event geprüft) + if (event.getNewCurrent() > 0) { + event.setNewCurrent(0); + } + } + + // ZUSÄTZLICH: Verhindert Interaktion mit Druckplatten/Knöpfen in der Nähe von gelockten Türen + @EventHandler(priority = EventPriority.HIGHEST) + public void onPressurePlateInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.PHYSICAL) return; + + Block block = event.getClickedBlock(); + if (block == null) return; + + // Prüfe, ob es eine Druckplatte ist + String typeName = block.getType().name(); + if (!typeName.contains("PRESSURE_PLATE")) return; + + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + + // Prüfe alle angrenzenden Blöcke auf gelockte Türen + for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) { + Block relative = block.getRelative(face); + + // Prüfe bis zu 2 Blöcke in jede Richtung + for (int i = 0; i < 3; i++) { + if (relative.getType().name().contains("DOOR")) { + Location doorLoc = relative.getLocation(); + + // Prüfe beide Türhälften + LockData lock = getLockData(doorLoc); + + if (lock != null && !hasAccess(player, lock)) { + event.setCancelled(true); + + // Anti-Spam: Nur alle 3 Sekunden eine Nachricht senden + long currentTime = System.currentTimeMillis(); + Long lastMessage = lastDenyMessage.get(playerUUID); + + if (lastMessage == null || (currentTime - lastMessage) > 3000) { + player.sendMessage(plugin.getMessage("lock.block-denied")); + lastDenyMessage.put(playerUUID, currentTime); + } + return; + } + } + relative = relative.getRelative(face); + } + } + } + + private boolean isLockedDoor(Location loc) { + return lockedBlocks.containsKey(loc); + } + + private LockData getLockData(Location loc) { + LockData lock = lockedBlocks.get(loc); + if (lock == null) { + lock = lockedBlocks.get(loc.clone().add(0, 1, 0)); + } + if (lock == null) { + lock = lockedBlocks.get(loc.clone().add(0, -1, 0)); + } + return lock; + } + + private boolean hasAccess(Player player, LockData lock) { + String playerUUID = player.getUniqueId().toString(); + return lock.getOwnerUUID().equals(playerUUID) || + lock.isFriend(playerUUID) || + player.isOp(); + } + + // Hilfsmethode: Findet die andere Hälfte einer Doppeltruhe + private Block getOtherChestHalf(Block block) { + if (block.getType() != Material.CHEST && block.getType() != Material.TRAPPED_CHEST) { + return null; + } + + try { + // Versuche moderne BlockData API (1.13+) + if (block.getBlockData() instanceof Chest) { + Chest chestData = (Chest) block.getBlockData(); + + // Prüfe ob es eine Doppeltruhe ist + if (chestData.getType() == Chest.Type.SINGLE) { + return null; + } + + // Finde die Richtung zur anderen Hälfte + BlockFace facing = chestData.getFacing(); + BlockFace direction; + + if (chestData.getType() == Chest.Type.LEFT) { + // Linke Hälfte: Andere Hälfte ist rechts + direction = getRight(facing); + } else { + // Rechte Hälfte: Andere Hälfte ist links + direction = getLeft(facing); + } + + Block otherBlock = block.getRelative(direction); + if (otherBlock.getType() == block.getType()) { + return otherBlock; + } + } + } catch (Exception e) { + // Fallback für ältere Versionen: Prüfe alle angrenzenden Blöcke + for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) { + Block relative = block.getRelative(face); + if (relative.getType() == block.getType()) { + return relative; + } + } + } + + return null; + } + + private BlockFace getRight(BlockFace face) { + switch (face) { + case NORTH: return BlockFace.EAST; + case EAST: return BlockFace.SOUTH; + case SOUTH: return BlockFace.WEST; + case WEST: return BlockFace.NORTH; + default: return face; + } + } + + private BlockFace getLeft(BlockFace face) { + switch (face) { + case NORTH: return BlockFace.WEST; + case WEST: return BlockFace.SOUTH; + case SOUTH: return BlockFace.EAST; + case EAST: return BlockFace.NORTH; + default: return face; + } + } + @EventHandler public void onBlockBreak(BlockBreakEvent event) { Player player = event.getPlayer(); @@ -174,6 +371,13 @@ public class LockSystem implements Listener, CommandExecutor { String playerUUID = player.getUniqueId().toString(); if (lock.getOwnerUUID().equals(playerUUID) || lock.isFriend(playerUUID) || player.isOp()) { + // Erlaube das Brechen und entferne beide Locks bei Doppeltruhen + lockedBlocks.remove(loc); + Block otherChest = getOtherChestHalf(block); + if (otherChest != null) { + lockedBlocks.remove(otherChest.getLocation()); + } + saveLocks(); return; } @@ -233,6 +437,13 @@ public class LockSystem implements Listener, CommandExecutor { player.sendMessage(plugin.getMessage("lock.no-permission-unlock")); } else { lockedBlocks.remove(loc); + + // Entferne auch Lock von der anderen Hälfte bei Doppeltruhen + Block otherChest = getOtherChestHalf(targetBlock); + if (otherChest != null) { + lockedBlocks.remove(otherChest.getLocation()); + } + saveLocks(); player.sendMessage(plugin.getMessage("lock.unlocked")); } @@ -254,6 +465,16 @@ public class LockSystem implements Listener, CommandExecutor { player.sendMessage(plugin.getMessage("lock.friendadd.not-found")); } else { lock.addFriend(friend.getUniqueId().toString()); + + // Füge Friend auch zur anderen Hälfte bei Doppeltruhen hinzu + Block otherChest = getOtherChestHalf(targetBlock); + if (otherChest != null) { + LockData otherLock = lockedBlocks.get(otherChest.getLocation()); + if (otherLock != null) { + otherLock.addFriend(friend.getUniqueId().toString()); + } + } + saveLocks(); player.sendMessage(plugin.getMessage("lock.friendadd.success").replace("{player}", friend.getName())); } @@ -276,6 +497,16 @@ public class LockSystem implements Listener, CommandExecutor { player.sendMessage(plugin.getMessage("lock.friendremove.not-found")); } else { lock.removeFriend(friend.getUniqueId().toString()); + + // Entferne Friend auch von der anderen Hälfte bei Doppeltruhen + Block otherChest = getOtherChestHalf(targetBlock); + if (otherChest != null) { + LockData otherLock = lockedBlocks.get(otherChest.getLocation()); + if (otherLock != null) { + otherLock.removeFriend(friend.getUniqueId().toString()); + } + } + saveLocks(); player.sendMessage(plugin.getMessage("lock.friendremove.success").replace("{player}", friend.getName())); } @@ -289,4 +520,4 @@ public class LockSystem implements Listener, CommandExecutor { return true; } -} +} \ No newline at end of file diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml index 3fcc9b4..e475714 100644 --- a/src/main/resources/lang.yml +++ b/src/main/resources/lang.yml @@ -403,6 +403,7 @@ inventory: player-only: "§cDieser Befehl ist nur für Spieler!" claim: + usage: "&cVerwendung: /claim mark <1|2> oder /claim" points-not-set: "&cDu musst zuerst zwei Punkte markieren! Verwende /claim mark <1|2>." different-worlds: "&cDie markierten Punkte müssen in derselben Welt liegen!" too-large: "&cDer Bereich ist zu groß! Maximal erlaubt: %max% Blöcke²." @@ -428,5 +429,6 @@ claim: enter: "&aDu hast das Gebiet von %owner% betreten." leave: "&eDu hast das Gebiet von %owner% verlassen." + force-survival: join-message: "§aDu wurdest in den Survivalmodus gesetzt!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 6a8374d..660051e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,5 @@ name: SurvivalPlus -version: 1.0.9 - +version: 1.1.0 main: de.viper.survivalplus.SurvivalPlus api-version: 1.21 @@ -270,13 +269,7 @@ commands: description: Manages claims for anti-griefing usage: / [unclaim | trust | untrust ] aliases: [cl] - survivalplus.claim.use: - description: Allows claiming and unclaiming chunks - default: true - survivalplus.claim.trust: - description: Allows trusting/untrusting players in claims - default: true - + permissions: survivalplus.*: description: Gibt Zugriff auf alle SurvivalPlus-Befehle @@ -345,6 +338,8 @@ permissions: survivalplus.heal.others: true survivalplus.notify: true survivalplus.chunkanimals: true + survivalplus.claim.use: true + survivalplus.claim.trust: true survivalplus.commandblocker.add: description: Erlaubt das Hinzufügen von Befehlen zur Blockierliste default: op