From df4ee6c2e981fdcefe70db904a2e71fb64da5f4c Mon Sep 17 00:00:00 2001 From: M_Viper Date: Tue, 24 Mar 2026 16:32:52 +0100 Subject: [PATCH] Update from Git Manager GUI --- src/main/java/me/viper/teamplugin/Main.java | 6 +- .../teamplugin/commands/TeamCommand.java | 39 ++++- .../me/viper/teamplugin/gui/SettingsGUI.java | 60 ++++++++ .../java/me/viper/teamplugin/gui/TeamGUI.java | 40 +++++- .../viper/teamplugin/manager/DataManager.java | 16 +++ .../viper/teamplugin/manager/LangManager.java | 58 ++++++++ .../teamplugin/manager/PresenceManager.java | 64 +++++++++ .../viper/teamplugin/util/ConfigUpdater.java | 135 ++++++++++++++++++ src/main/resources/config.yml | 48 ++++++- src/main/resources/lang_de.yml | 14 ++ src/main/resources/lang_en.yml | 14 ++ src/main/resources/plugin.yml | 4 +- 12 files changed, 488 insertions(+), 10 deletions(-) create mode 100644 src/main/java/me/viper/teamplugin/manager/PresenceManager.java create mode 100644 src/main/java/me/viper/teamplugin/util/ConfigUpdater.java diff --git a/src/main/java/me/viper/teamplugin/Main.java b/src/main/java/me/viper/teamplugin/Main.java index 0ff2a35..6545823 100644 --- a/src/main/java/me/viper/teamplugin/Main.java +++ b/src/main/java/me/viper/teamplugin/Main.java @@ -4,6 +4,7 @@ import me.viper.teamplugin.commands.TeamCommand; import me.viper.teamplugin.listener.InventoryListener; import me.viper.teamplugin.manager.DataManager; import me.viper.teamplugin.manager.LangManager; +import me.viper.teamplugin.util.ConfigUpdater; import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; @@ -16,8 +17,9 @@ public class Main extends JavaPlugin { instance = this; saveDefaultConfig(); // config.yml - saveResource("lang_de.yml", false); // German lang file (only if absent) - saveResource("lang_en.yml", false); // English lang file (only if absent) + ConfigUpdater.update(); // fehlende Keys automatisch ergänzen + if (!new java.io.File(getDataFolder(), "lang_de.yml").exists()) saveResource("lang_de.yml", false); + if (!new java.io.File(getDataFolder(), "lang_en.yml").exists()) saveResource("lang_en.yml", false); LangManager.setup(); DataManager.setup(); diff --git a/src/main/java/me/viper/teamplugin/commands/TeamCommand.java b/src/main/java/me/viper/teamplugin/commands/TeamCommand.java index a828912..2c80d24 100644 --- a/src/main/java/me/viper/teamplugin/commands/TeamCommand.java +++ b/src/main/java/me/viper/teamplugin/commands/TeamCommand.java @@ -7,6 +7,7 @@ import me.viper.teamplugin.gui.MailboxGUI; import me.viper.teamplugin.gui.SettingsGUI; import me.viper.teamplugin.gui.TeamGUI; import me.viper.teamplugin.manager.*; +import me.viper.teamplugin.util.ConfigUpdater; import me.viper.teamplugin.util.Utils; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -54,6 +55,7 @@ public class TeamCommand implements CommandExecutor, TabCompleter { private static final String C_APPLY = "apply"; private static final String C_LOG = "log"; private static final String C_MAILBOX = "mailbox"; + private static final String C_STATUS = "status"; // apply sub-sub-commands private static final String C_LIST = "list"; @@ -74,7 +76,9 @@ public class TeamCommand implements CommandExecutor, TabCompleter { Map.entry("suchen", C_SEARCH), Map.entry("bewerben", C_APPLY), Map.entry("protokoll", C_LOG), - Map.entry("postfach", C_MAILBOX) + Map.entry("postfach", C_MAILBOX), + Map.entry("status", C_STATUS), + Map.entry("offline", C_STATUS) ); // apply sub-sub DE aliases @@ -120,7 +124,8 @@ public class TeamCommand implements CommandExecutor, TabCompleter { LangManager.getCmd("cmd_search"), LangManager.getCmd("cmd_apply"), LangManager.getCmd("cmd_log"), - LangManager.getCmd("cmd_mailbox") + LangManager.getCmd("cmd_mailbox"), + LangManager.getCmd("cmd_status") ); } @@ -239,6 +244,7 @@ public class TeamCommand implements CommandExecutor, TabCompleter { case C_RELOAD -> { if (!sender.hasPermission("teamplugin.admin")) { sender.sendMessage(LangManager.get("no_permission")); return true; } Main.getInstance().reloadConfig(); + ConfigUpdater.update(); LangManager.setup(); DataManager.reloadData(); MailboxManager.reload(); @@ -255,7 +261,7 @@ public class TeamCommand implements CommandExecutor, TabCompleter { String rankDisplay = Main.getInstance().getConfig().getString("rank-settings." + infoRank + ".display", infoRank); String iso = DataManager.getJoinDate(infoName); String joinDate = (iso != null && !iso.isEmpty()) ? Utils.prettifyIso(iso) : "\u00a77\u2014"; - boolean isOnline = Bukkit.getOfflinePlayer(infoName).isOnline(); + boolean isOnline = PresenceManager.isShownAsOnline(infoName); String statusOn = Main.getInstance().getConfig().getString("status.online", "&a\uD83D\uDFE2 Online"); String statusOff = Main.getInstance().getConfig().getString("status.offline", "&c\uD83D\uDD34 Offline"); sender.sendMessage(Utils.color(Utils.replace(LangManager.get("info_header"), "%player%", infoName))); @@ -264,6 +270,32 @@ public class TeamCommand implements CommandExecutor, TabCompleter { sender.sendMessage(Utils.color(Utils.replace(LangManager.get("info_status"), "%status%", isOnline ? statusOn : statusOff))); } + // ── status ──────────────────────────────────────────────── + case C_STATUS -> { + if (!(sender instanceof Player p)) { sender.sendMessage(LangManager.get("only_player")); return true; } + + if (findRank(p.getName()) == null) { + p.sendMessage(Utils.color(LangManager.get("status_only_team"))); + return true; + } + + String mode = (args.length >= 2) ? args[1].toLowerCase() : "toggle"; + if (mode.equals("status")) { + boolean forced = PresenceManager.isForcedOffline(p.getName()); + p.sendMessage(Utils.color(forced + ? LangManager.get("status_state_forced") + : LangManager.get("status_state_normal"))); + return true; + } + + boolean newForced = !PresenceManager.isForcedOffline(p.getName()); + + PresenceManager.setForcedOffline(p.getName(), newForced); + p.sendMessage(Utils.color(newForced + ? LangManager.get("status_enabled") + : LangManager.get("status_disabled"))); + } + // ── search ──────────────────────────────────────────────── case C_SEARCH -> { if (args.length < 2) { sender.sendMessage(Utils.color(LangManager.get("prefix")) + "\u00a7cUsage: /team " + LangManager.getCmd("cmd_search") + " "); return true; } @@ -427,6 +459,7 @@ public class TeamCommand implements CommandExecutor, TabCompleter { case C_SEARCH -> filter(allTeamMembers(), args[1]); case C_LOG -> List.of("10", "25", "50"); case C_APPLY -> filter(activeApplySubs(sender.hasPermission("teamplugin.admin")), args[1]); + case C_STATUS -> filter(List.of("toggle", "status"), args[1]); default -> new ArrayList<>(); }; diff --git a/src/main/java/me/viper/teamplugin/gui/SettingsGUI.java b/src/main/java/me/viper/teamplugin/gui/SettingsGUI.java index 440b972..1043b76 100644 --- a/src/main/java/me/viper/teamplugin/gui/SettingsGUI.java +++ b/src/main/java/me/viper/teamplugin/gui/SettingsGUI.java @@ -4,6 +4,8 @@ import me.viper.teamplugin.Main; import me.viper.teamplugin.manager.BackupManager; import me.viper.teamplugin.manager.DataManager; import me.viper.teamplugin.manager.LangManager; +import me.viper.teamplugin.manager.PresenceManager; +import me.viper.teamplugin.util.ConfigUpdater; import me.viper.teamplugin.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -59,6 +61,11 @@ public class SettingsGUI { } } + // Fallback: show status toggle even if not configured in admin-buttons. + if (!hasButtonKey(buttons, "status_toggle")) { + inv.setItem(24, createStatusToggleItem(player)); + } + player.openInventory(inv); } @@ -89,6 +96,7 @@ public class SettingsGUI { } case "reload" -> { Main.getInstance().reloadConfig(); + ConfigUpdater.update(); LangManager.setup(); DataManager.reloadData(); LangManager.save(); @@ -113,13 +121,32 @@ public class SettingsGUI { else p.sendMessage(Utils.color(LangManager.get("prefix")) + "§cBackup fehlgeschlagen."); p.closeInventory(); } + case "status_toggle" -> { + boolean next = !PresenceManager.isForcedOffline(p.getName()); + PresenceManager.setForcedOffline(p.getName(), next); + p.sendMessage(Utils.color(next + ? LangManager.get("status_enabled") + : LangManager.get("status_disabled"))); + openSettings(p); + } default -> { p.sendMessage(Utils.color(LangManager.get("prefix")) + "§cUnbekannter Button: " + key); } } + return; } } } + + // Fallback click handler for the built-in status toggle item. + if (isStatusToggleTitle(title)) { + boolean next = !PresenceManager.isForcedOffline(p.getName()); + PresenceManager.setForcedOffline(p.getName(), next); + p.sendMessage(Utils.color(next + ? LangManager.get("status_enabled") + : LangManager.get("status_disabled"))); + openSettings(p); + } } /** @@ -128,4 +155,37 @@ public class SettingsGUI { public static String getGuiTitle() { return LangManager.get("settings_gui_title"); } + + private static boolean hasButtonKey(List buttons, String key) { + if (buttons == null || key == null) return false; + for (Object o : buttons) { + if (!(o instanceof Map map)) continue; + Object raw = map.get("key"); + if (raw instanceof String s && s.equalsIgnoreCase(key)) return true; + } + return false; + } + + private static ItemStack createStatusToggleItem(Player player) { + boolean forced = PresenceManager.isForcedOffline(player.getName()); + Material mat = forced ? Material.REDSTONE_TORCH : Material.LIME_DYE; + + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(Utils.color(LangManager.get("settings_status_toggle_title"))); + meta.setLore(List.of( + Utils.color(Utils.replace(LangManager.get("settings_status_toggle_lore_state"), "%state%", + forced ? LangManager.get("settings_status_state_offline") : LangManager.get("settings_status_state_online"))), + Utils.color(LangManager.get("settings_status_toggle_lore_vanish")), + Utils.color(LangManager.get("settings_status_toggle_lore_click")) + )); + item.setItemMeta(meta); + } + return item; + } + + private static boolean isStatusToggleTitle(String title) { + return title != null && title.equals(Utils.color(LangManager.get("settings_status_toggle_title"))); + } } diff --git a/src/main/java/me/viper/teamplugin/gui/TeamGUI.java b/src/main/java/me/viper/teamplugin/gui/TeamGUI.java index 2eee4f4..c2f9eea 100644 --- a/src/main/java/me/viper/teamplugin/gui/TeamGUI.java +++ b/src/main/java/me/viper/teamplugin/gui/TeamGUI.java @@ -4,6 +4,7 @@ import me.viper.teamplugin.Main; import me.viper.teamplugin.manager.DataManager; import me.viper.teamplugin.manager.LangManager; import me.viper.teamplugin.manager.MessageManager; +import me.viper.teamplugin.manager.PresenceManager; import me.viper.teamplugin.util.SkinResolver; import me.viper.teamplugin.util.Utils; import org.bukkit.Bukkit; @@ -82,6 +83,28 @@ public class TeamGUI { if (slot < 0 || slot >= GUI_SIZE) continue; inv.setItem(slot, createRankOverviewBlock(rank)); } + + // Info-Item + if (cfg.getBoolean("gui.info-item.enabled", false)) { + int infoSlot = cfg.getInt("gui.info-item.slot", 49); + if (infoSlot >= 0 && infoSlot < GUI_SIZE) { + String skullTex = cfg.getString("gui.info-item.skull_texture", ""); + ItemStack infoItem = (skullTex != null && !skullTex.isEmpty()) + ? buildCustomSkull(skullTex) + : new ItemStack(parseMaterial(cfg.getString("gui.info-item.material", "BOOK"), Material.BOOK)); + String infoDisplay = cfg.getString("gui.info-item.display", "&eInfo"); + List infoLore = cfg.getStringList("gui.info-item.lore") + .stream().map(Utils::color).collect(Collectors.toList()); + ItemMeta infoMeta = infoItem.getItemMeta(); + if (infoMeta != null) { + infoMeta.setDisplayName(Utils.color(infoDisplay)); + infoMeta.setLore(infoLore); + infoItem.setItemMeta(infoMeta); + } + inv.setItem(infoSlot, infoItem); + } + } + player.openInventory(inv); } @@ -267,6 +290,20 @@ public class TeamGUI { return; } } + + // Info-Item click + if (cfg.getBoolean("gui.info-item.enabled", false) + && slot == cfg.getInt("gui.info-item.slot", 49)) { + String action = cfg.getString("gui.info-item.on-click", ""); + if (action != null && !action.isEmpty()) { + player.closeInventory(); + if (action.startsWith("cmd:")) { + player.performCommand(action.substring(4)); + } else { + player.sendMessage(Utils.color(action)); + } + } + } } public static void handleRankPageClick(Player player, int slot, String currentRank) { @@ -389,8 +426,7 @@ public class TeamGUI { String statusOnline = cfg.getString("status.online", "&a\uD83D\uDFE2 Online"); String statusOffline = cfg.getString("status.offline", "&c\uD83D\uDD34 Offline"); - @SuppressWarnings("deprecation") - boolean online = Bukkit.getOfflinePlayer(name).isOnline(); + boolean online = PresenceManager.isShownAsOnline(name); lore.add(Utils.color("&7Status&8: " + (online ? statusOnline : statusOffline))); String iso = DataManager.getData().getString("JoinDates." + name, ""); diff --git a/src/main/java/me/viper/teamplugin/manager/DataManager.java b/src/main/java/me/viper/teamplugin/manager/DataManager.java index 91d3f50..0c1a937 100644 --- a/src/main/java/me/viper/teamplugin/manager/DataManager.java +++ b/src/main/java/me/viper/teamplugin/manager/DataManager.java @@ -13,6 +13,8 @@ import java.util.*; import java.util.stream.Collectors; public class DataManager { + private static final String FORCE_OFFLINE_PATH = "Status.force-offline"; + private static File file; private static FileConfiguration data; @@ -94,6 +96,7 @@ public class DataManager { } if (removed) { data.set("JoinDates." + name, null); + data.set(FORCE_OFFLINE_PATH + "." + normalizeName(name), null); save(); } } @@ -177,4 +180,17 @@ public class DataManager { public static String getJoinDate(String name) { return data.getString("JoinDates." + name, ""); } + + public static boolean isForcedOffline(String name) { + return data.getBoolean(FORCE_OFFLINE_PATH + "." + normalizeName(name), false); + } + + public static void setForcedOffline(String name, boolean forcedOffline) { + data.set(FORCE_OFFLINE_PATH + "." + normalizeName(name), forcedOffline ? true : null); + save(); + } + + private static String normalizeName(String name) { + return name == null ? "" : name.toLowerCase(Locale.ROOT); + } } diff --git a/src/main/java/me/viper/teamplugin/manager/LangManager.java b/src/main/java/me/viper/teamplugin/manager/LangManager.java index 77c8c60..cdcb5b9 100644 --- a/src/main/java/me/viper/teamplugin/manager/LangManager.java +++ b/src/main/java/me/viper/teamplugin/manager/LangManager.java @@ -6,6 +6,10 @@ import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; public class LangManager { @@ -17,6 +21,8 @@ public class LangManager { // ── Setup ───────────────────────────────────────────────────────── public static void setup() { + syncLanguageFiles(); + String lang = Main.getInstance().getConfig().getString("language", "de").toLowerCase(); String fileName = "lang_" + lang + ".yml"; @@ -31,6 +37,58 @@ public class LangManager { "[LangManager] Loaded language: " + lang + " (" + fileName + ")"); } + private static void syncLanguageFiles() { + syncLanguageFile("lang_de.yml"); + syncLanguageFile("lang_en.yml"); + } + + private static void syncLanguageFile(String fileName) { + Main plugin = Main.getInstance(); + File target = new File(plugin.getDataFolder(), fileName); + + if (!target.exists()) { + plugin.saveResource(fileName, false); + plugin.getLogger().info("[LangManager] Created missing file: " + fileName); + return; + } + + YamlConfiguration onDisk = YamlConfiguration.loadConfiguration(target); + YamlConfiguration defaults = loadLangDefaults(plugin, fileName); + if (defaults == null) return; + + List added = new ArrayList<>(); + for (String path : defaults.getKeys(true)) { + if (defaults.isConfigurationSection(path)) continue; + if (!onDisk.isSet(path)) { + onDisk.set(path, defaults.get(path)); + added.add(path); + } + } + + if (added.isEmpty()) return; + + try { + onDisk.save(target); + plugin.getLogger().info("[LangManager] " + fileName + ": " + added.size() + + " missing key(s) added."); + } catch (IOException e) { + plugin.getLogger().warning("[LangManager] Failed to save " + fileName + ": " + e.getMessage()); + } + } + + private static YamlConfiguration loadLangDefaults(Main plugin, String fileName) { + try (InputStream in = plugin.getResource(fileName)) { + if (in == null) { + plugin.getLogger().warning("[LangManager] Missing bundled language file: " + fileName); + return null; + } + return YamlConfiguration.loadConfiguration(new InputStreamReader(in, StandardCharsets.UTF_8)); + } catch (IOException e) { + plugin.getLogger().warning("[LangManager] Failed to load bundled " + fileName + ": " + e.getMessage()); + return null; + } + } + // ── Getters ─────────────────────────────────────────────────────── public static String get(String path) { diff --git a/src/main/java/me/viper/teamplugin/manager/PresenceManager.java b/src/main/java/me/viper/teamplugin/manager/PresenceManager.java new file mode 100644 index 0000000..f7de1a6 --- /dev/null +++ b/src/main/java/me/viper/teamplugin/manager/PresenceManager.java @@ -0,0 +1,64 @@ +package me.viper.teamplugin.manager; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.metadata.MetadataValue; + +import java.util.List; + +/** + * Zentraler Präsenz-Status für Team-Anzeige. + * Spieler gelten als offline, wenn sie wirklich offline sind, + * manuell auf offline gesetzt wurden oder im Vanish sind. + */ +public final class PresenceManager { + + private PresenceManager() {} + + public static boolean isShownAsOnline(String playerName) { + if (playerName == null || playerName.isEmpty()) return false; + + @SuppressWarnings("deprecation") + OfflinePlayer off = Bukkit.getOfflinePlayer(playerName); + if (!off.isOnline()) return false; + + Player player = off.getPlayer(); + if (player == null) return false; + + if (DataManager.isForcedOffline(playerName)) return false; + return !isVanished(player); + } + + public static boolean isForcedOffline(String playerName) { + return DataManager.isForcedOffline(playerName); + } + + public static void setForcedOffline(String playerName, boolean forcedOffline) { + DataManager.setForcedOffline(playerName, forcedOffline); + } + + private static boolean isVanished(Player player) { + if (player == null) return false; + + // Common metadata keys used by vanish plugins. + // Important: check metadata BOOLEAN value, not just key existence. + if (hasTrueMetadata(player, "vanished") + || hasTrueMetadata(player, "vanish") + || hasTrueMetadata(player, "essentials.vanish") + || hasTrueMetadata(player, "supervanish")) { + return true; + } + + // Fallback for plugins that toggle entity invisibility directly. + return player.isInvisible(); + } + + private static boolean hasTrueMetadata(Player player, String key) { + List values = player.getMetadata(key); + for (MetadataValue value : values) { + if (value != null && value.asBoolean()) return true; + } + return false; + } +} diff --git a/src/main/java/me/viper/teamplugin/util/ConfigUpdater.java b/src/main/java/me/viper/teamplugin/util/ConfigUpdater.java new file mode 100644 index 0000000..42f655c --- /dev/null +++ b/src/main/java/me/viper/teamplugin/util/ConfigUpdater.java @@ -0,0 +1,135 @@ +package me.viper.teamplugin.util; + +import me.viper.teamplugin.Main; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Versionierte config.yml-Migration: + * – Es werden niemals pauschal alle fehlenden Keys ergänzt. + * – Ergänzt werden nur explizit definierte, neue Migrations-Keys pro Version. + * – User-verwaltete Rangbereiche werden nie automatisch zurückgeschrieben. + */ +public class ConfigUpdater { + + private static final String CONFIG_VERSION_PATH = "config-version"; + private static final Set MIGRATION_EXCLUDED_ROOTS = Set.of( + "ranks", + "rank-settings" + ); + + /** + * Nur hier eingetragene Pfade werden bei einem Upgrade auf die jeweilige Version ergänzt. + * Format: targetVersion -> List + * + * Wichtig: Nur wirklich neue Keys eintragen, niemals bestehende User-Bereiche. + */ + private static final Map> MIGRATION_ADDITIONS = Map.of( + 1, List.of() + ); + + public static void update() { + Main plugin = Main.getInstance(); + File configFile = new File(plugin.getDataFolder(), "config.yml"); + + YamlConfiguration defaults = loadDefaults(plugin); + if (defaults == null) return; + + YamlConfiguration onDisk = YamlConfiguration.loadConfiguration(configFile); + int defaultVersion = Math.max(1, defaults.getInt(CONFIG_VERSION_PATH, 1)); + int diskVersion = onDisk.getInt(CONFIG_VERSION_PATH, 0); + + if (diskVersion > defaultVersion) { + plugin.getLogger().warning("[Config] config.yml hat eine neuere Version (" + diskVersion + + ") als dieses Plugin (" + defaultVersion + ")."); + return; + } + + if (diskVersion >= defaultVersion) { + // Bereits aktuell: keine Migration ausführen. + return; + } + + List added = new ArrayList<>(); + + // Migrationen inkrementell je Versionsschritt anwenden. + for (int targetVersion = diskVersion + 1; targetVersion <= defaultVersion; targetVersion++) { + List additions = MIGRATION_ADDITIONS.getOrDefault(targetVersion, List.of()); + for (String path : additions) { + addPathIfMissing(plugin, onDisk, defaults, path, added); + } + } + + // Nach erfolgreicher Migration Version anheben. + onDisk.set(CONFIG_VERSION_PATH, defaultVersion); + + try { + onDisk.save(configFile); + plugin.getLogger().info("[Config] Migration " + diskVersion + " -> " + defaultVersion + + " abgeschlossen; " + added.size() + " fehlende Einstellung(en) ergänzt."); + } catch (IOException e) { + plugin.getLogger().warning("[Config] Fehler beim Speichern der config.yml: " + e.getMessage()); + return; + } + + // Neu gespeicherte Migration sofort in die Runtime laden. + plugin.reloadConfig(); + } + + private static YamlConfiguration loadDefaults(Main plugin) { + try (InputStream ds = plugin.getResource("config.yml")) { + if (ds == null) return null; + return YamlConfiguration.loadConfiguration(new InputStreamReader(ds)); + } catch (IOException e) { + plugin.getLogger().warning("[Config] Fehler beim Laden der Default-config.yml: " + e.getMessage()); + return null; + } + } + + private static void addPathIfMissing(Main plugin, + YamlConfiguration onDisk, + YamlConfiguration defaults, + String path, + List added) { + if (path == null || path.trim().isEmpty()) return; + if (shouldSkipAutoMerge(path)) return; + + if (!defaults.isSet(path)) { + plugin.getLogger().warning("[Config] Migration-Key nicht in default config vorhanden: " + path); + return; + } + + if (defaults.isConfigurationSection(path)) { + for (String relPath : defaults.getConfigurationSection(path).getKeys(true)) { + String fullPath = path + "." + relPath; + if (defaults.isConfigurationSection(fullPath)) continue; + if (shouldSkipAutoMerge(fullPath)) continue; + if (!onDisk.isSet(fullPath)) { + onDisk.set(fullPath, defaults.get(fullPath)); + added.add(fullPath); + } + } + return; + } + + if (!onDisk.isSet(path)) { + onDisk.set(path, defaults.get(path)); + added.add(path); + } + } + + /** + * User-managed rank sections must not be recreated automatically. + * Otherwise intentionally removed ranks reappear after /team reload. + */ + private static boolean shouldSkipAutoMerge(String path) { + int dot = path.indexOf('.'); + String root = dot < 0 ? path : path.substring(0, dot); + return MIGRATION_EXCLUDED_ROOTS.contains(root); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 911b596..fc307bb 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -3,6 +3,11 @@ # GUI-Größe ist IMMER 54 und NICHT konfigurierbar. # ══════════════════════════════════════════════════════ +# Konfigurationsversion für automatische Migrationen +# Hinweis: Nur explizit in ConfigUpdater definierte neue Keys werden ergänzt. +# Es gibt kein pauschales Wiederherstellen fehlender Einträge. +config-version: 1 + # ── Sprache / Language ─────────────────────────────────────────────── # de → Deutsche Befehle & Nachrichten (/team hinzufügen, entfernen …) # en → English commands & messages (/team add, del …) @@ -29,6 +34,38 @@ gui: rank_page_title: "&8» &bTeam &7| " background: "GRAY_STAINED_GLASS_PANE" + # ── Info-Item im Übersichts-GUI ───────────────────────────────── + # Zeigt dem Spieler, wie er sich bewerben kann. + # slot → Slot-Nummer im Übersichts-GUI (0-53) + # material → Beliebiges Vanilla-Material (z. B. BOOK, PAPER, MAP) + # display → Anzeigename (& Farbcodes erlaubt) + # lore → Zeilenliste (& Farbcodes erlaubt) + # + # on-click → Aktion beim Klicken (optional): + # Leer lassen / weglassen → kein Klick-Effekt + # Präfix „cmd:" → Befehl ausführen (als Spieler, ohne /) + # Beispiel: cmd:team bewerben + # Kein Präfix → Chat-Nachricht senden + # Beispiel: "&eBenutze /team bewerben um dich zu bewerben!" + # + # skull_texture → Skull statt material verwenden (optional) + # Gleiche Formate wie bei rank-settings (URL / 64-Hex / Base64 eyJ...) + # Wenn gesetzt, wird material ignoriert. + info-item: + enabled: true + slot: 49 + material: BOOK + skull_texture: "http://textures.minecraft.net/texture/d01afe973c5482fdc71e6aa10698833c79c437f21308ea9a1a095746ec274a0f" + display: "&e&lBewirb dich!" + lore: + - "&7Möchtest du Teil unseres Teams werden?" + - "" + - "&eBefehl&8: &f/team bewerben" + - "" + - "&7Fülle die Bewerbung aus und schicke sie ab." + - "&7Wir melden uns so schnell wie möglich!" + on-click: "cmd:team bewerben" + # ── Rang-Einstellungen ────────────────────────────────────────────── # # Jeder Rang kann folgende Felder haben: @@ -189,4 +226,13 @@ admin-buttons: title: "&6Ränge bearbeiten" lore: - "&7Bearbeite ranks & rank-settings" - - "&7in der config.yml" \ No newline at end of file + - "&7in der config.yml" + + - key: status_toggle + slot: 24 + material: LIME_DYE + title: "&bStatus umschalten" + lore: + - "&7Schaltet deinen Team-Status" + - "&7zwischen online/offline um" + - "&8(Vanish wird automatisch als offline erkannt)" \ No newline at end of file diff --git a/src/main/resources/lang_de.yml b/src/main/resources/lang_de.yml index 1587faf..cc8acad 100644 --- a/src/main/resources/lang_de.yml +++ b/src/main/resources/lang_de.yml @@ -18,6 +18,7 @@ cmd_apply_accept: "annehmen" cmd_apply_deny: "ablehnen" cmd_log: "protokoll" cmd_mailbox: "postfach" +cmd_status: "status" # ── Allgemein ──────────────────────────────────────────────────────── no_permission: "%prefix%§cDazu hast du keine Berechtigung!" @@ -46,6 +47,13 @@ info_joined: "%prefix%§7Beigetreten§8: §e%joindate%" info_status: "%prefix%§7Status§8: %status%" info_not_team: "%prefix%§c%player% ist kein Teammitglied." +# ── /team status ──────────────────────────────────────────────────── +status_only_team: "%prefix%§cNur Teammitglieder können diesen Status setzen." +status_state_forced: "%prefix%§7Du wirst aktuell als §coffline §7angezeigt." +status_state_normal: "%prefix%§7Du wirst aktuell als §aonline §7angezeigt (sofern nicht vanished)." +status_enabled: "%prefix%§aOffline-Modus aktiviert. Du wirst in Team-Infos als offline angezeigt." +status_disabled: "%prefix%§aOffline-Modus deaktiviert. Du wirst wieder normal angezeigt." + # ── /team bewerben ─────────────────────────────────────────────────── apply_sent: "%prefix%§aDeine Bewerbung für §e%rank% §awurde eingereicht!" apply_already: "%prefix%§cDu hast bereits eine offene Bewerbung." @@ -71,6 +79,12 @@ settings_reload_lore: settings_backup: "§eBackups verwalten" settings_backup_lore: - "§7Öffnet die Backup-Übersicht" +settings_status_toggle_title: "&bStatus umschalten" +settings_status_toggle_lore_state: "&7Aktuell: %state%" +settings_status_toggle_lore_vanish: "&8(Bei Vanish immer offline)" +settings_status_toggle_lore_click: "&eKlicken zum Umschalten" +settings_status_state_online: "&aOnline" +settings_status_state_offline: "&cOffline" # ── Rang-Seite: Navigation ──────────────────────────────────────────── nav_prev_label: "§7« %rank%" diff --git a/src/main/resources/lang_en.yml b/src/main/resources/lang_en.yml index 5f22678..eac6393 100644 --- a/src/main/resources/lang_en.yml +++ b/src/main/resources/lang_en.yml @@ -17,6 +17,7 @@ cmd_apply_accept: "accept" cmd_apply_deny: "deny" cmd_log: "log" cmd_mailbox: "mailbox" +cmd_status: "status" # ── General ────────────────────────────────────────────────────────── no_permission: "%prefix%§cYou don't have permission to do that!" @@ -45,6 +46,13 @@ info_joined: "%prefix%§7Joined§8: §e%joindate%" info_status: "%prefix%§7Status§8: %status%" info_not_team: "%prefix%§c%player% is not a team member." +# ── /team status ──────────────────────────────────────────────────── +status_only_team: "%prefix%§cOnly team members can change this status." +status_state_forced: "%prefix%§7You are currently shown as §coffline§7." +status_state_normal: "%prefix%§7You are currently shown as §aonline§7 (unless vanished)." +status_enabled: "%prefix%§aOffline mode enabled. You will be shown as offline in team info." +status_disabled: "%prefix%§aOffline mode disabled. You will be shown normally again." + # ── /team apply ─────────────────────────────────────────────────────── apply_sent: "%prefix%§aYour application for §e%rank% §ahas been submitted!" apply_already: "%prefix%§cYou already have a pending application." @@ -70,6 +78,12 @@ settings_reload_lore: settings_backup: "§eManage backups" settings_backup_lore: - "§7Opens the backup overview" +settings_status_toggle_title: "&bToggle status" +settings_status_toggle_lore_state: "&7Current: %state%" +settings_status_toggle_lore_vanish: "&8(Always offline while vanished)" +settings_status_toggle_lore_click: "&eClick to toggle" +settings_status_state_online: "&aOnline" +settings_status_state_offline: "&cOffline" # ── Rank page navigation ────────────────────────────────────────────── nav_prev_label: "§7« %rank%" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 31d77ec..5a23733 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,8 +1,8 @@ name: Team -version: 1.0.2 +version: 1.0.4 main: me.viper.teamplugin.Main api-version: 1.21 -author: Viper +author: M_Viper description: Erweiterbares Team-Plugin mit GUI, Backup/Restore und vielen Config-Optionen commands: