diff --git a/src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java b/src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java index e233c0b..ac431ec 100644 --- a/src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java +++ b/src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java @@ -1,863 +1,729 @@ -package dev.viper.weathertime; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.File; -import java.io.IOException; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.UUID; - -public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener { - - private String apiKey; - private int updateInterval; - private final HashMap worldConfigs = new HashMap<>(); - private final HashMap worldWeatherData = new HashMap<>(); - private final HashMap> playersWithDisplay = new HashMap<>(); - private final HashMap playerLocations = new HashMap<>(); - private final HashMap playerGUIs = new HashMap<>(); - private BukkitAudiences audiences; - private BukkitRunnable weatherUpdateTask; - private BukkitRunnable syncTask; - private final HashSet processedLocations = new HashSet<>(); - private FileConfiguration langConfig; - - @Override - public void onEnable() { - saveDefaultConfig(); - loadLanguageConfig(); // Zuerst laden, um sicherzustellen, dass Übersetzungen verfügbar sind - loadConfig(); - this.audiences = BukkitAudiences.create(this); - - getCommand("wetter").setExecutor(new WetterCommand()); - getCommand("weatherforecast").setExecutor(new WeatherForecastCommand()); - getCommand("toggleweather").setExecutor(new ToggleWeatherCommand()); - - getServer().getPluginManager().registerEvents(this, this); - - setDoDaylightCycleForWorlds(); - startWeatherUpdateTask(); - startSyncTask(); - - initializePlayerDisplays(); - } - - @Override - public void onDisable() { - if (weatherUpdateTask != null) { - weatherUpdateTask.cancel(); - } - if (syncTask != null) { - syncTask.cancel(); - } - if (audiences != null) { - audiences.close(); - } - for (Player player : Bukkit.getOnlinePlayers()) { - if (playerGUIs.containsKey(player.getUniqueId())) { - player.closeInventory(); - } - } - } - - private void loadConfig() { - FileConfiguration cfg = getConfig(); - apiKey = cfg.getString("api-key"); - updateInterval = cfg.getInt("update-interval", 60); - worldConfigs.clear(); - - WorldConfig defaultConfig = new WorldConfig( - cfg.getBoolean("defaults.enabled", true), - cfg.getString("defaults.location", "Berlin,de"), - cfg.getString("defaults.units", "metric"), - cfg.getString("defaults.time-format", "24h"), - cfg.getBoolean("defaults.display-actionbar", true), - cfg.getBoolean("defaults.display-weather-icon", true), - cfg.getString("defaults.display-position", "top-right"), - cfg.getInt("defaults.padding-right", 100), - cfg.getBoolean("defaults.sync-in-game-weather", true) - ); - worldConfigs.put("defaults", defaultConfig); - - if (cfg.isConfigurationSection("worlds")) { - for (String worldName : cfg.getConfigurationSection("worlds").getKeys(false)) { - WorldConfig worldConfig = new WorldConfig( - cfg.getBoolean("worlds." + worldName + ".enabled", defaultConfig.enabled), - cfg.getString("worlds." + worldName + ".location", defaultConfig.location), - cfg.getString("worlds." + worldName + ".units", defaultConfig.units), - cfg.getString("worlds." + worldName + ".time-format", defaultConfig.timeFormat), - cfg.getBoolean("worlds." + worldName + ".display-actionbar", defaultConfig.displayActionbar), - cfg.getBoolean("worlds." + worldName + ".display-weather-icon", defaultConfig.displayWeatherIcon), - cfg.getString("worlds." + worldName + ".display-position", defaultConfig.displayPosition), - cfg.getInt("worlds." + worldName + ".padding-right", defaultConfig.paddingRight), - cfg.getBoolean("worlds." + worldName + ".sync-in-game-weather", defaultConfig.syncInGameWeather) - ); - worldConfigs.put(worldName, worldConfig); - } - } - - if (apiKey == null || apiKey.trim().isEmpty()) { - getLogger().severe("API-Key ist nicht gesetzt! Bitte in config.yml eintragen."); - } - } - - private void loadLanguageConfig() { - File langFile = new File(getDataFolder(), "lang.yml"); - try { - langConfig = YamlConfiguration.loadConfiguration(langFile); - if (!langFile.exists()) { - saveResource("lang.yml", false); - getLogger().info("lang.yml wurde aus den Ressourcen kopiert."); - langConfig = YamlConfiguration.loadConfiguration(langFile); - } else if (langConfig.getConfigurationSection("languages") == null) { - getLogger().warning("lang.yml enthält keinen 'languages'-Abschnitt. Standardwerte werden verwendet."); - saveResource("lang.yml", true); // Überschreibe mit Standardwerten - langConfig = YamlConfiguration.loadConfiguration(langFile); - } - // Debugging: Verfügbare Sprachen protokollieren - getLogger().info("Verfügbare Sprachen in lang.yml: " + langConfig.getConfigurationSection("languages").getKeys(false)); - } catch (Exception e) { - getLogger().severe("Fehler beim Laden von lang.yml: " + e.getMessage()); - saveResource("lang.yml", true); // Überschreibe bei Fehler mit Standardwerten - langConfig = YamlConfiguration.loadConfiguration(langFile); - getLogger().info("lang.yml wurde nach Fehler mit Standardwerten überschrieben."); - } - } - - private void setDoDaylightCycleForWorlds() { - for (World world : Bukkit.getWorlds()) { - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (config.enabled) { - world.setGameRuleValue("doDaylightCycle", "false"); - } - } - } - - private String getLocalizedMessage(String key, String countryCode, String... placeholders) { - String path = "languages." + (countryCode.isEmpty() ? "en" : countryCode.toLowerCase()) + "." + key; - String message = langConfig.getString(path); - if (message == null) { - path = "languages.en." + key; - message = langConfig.getString(path); - if (message == null) { - // Nur Warnung für fehlende Übersetzungen, keine Debugging-Info für jede Suche - getLogger().warning("Übersetzung für Schlüssel '" + key + "' in Sprache '" + countryCode + "' und Fallback 'en' nicht gefunden!"); - return key; - } - } - for (int i = 0; i < placeholders.length - 1; i += 2) { - message = message.replace("{" + placeholders[i] + "}", placeholders[i + 1]); - } - return message; - } - - private void startWeatherUpdateTask() { - weatherUpdateTask = new BukkitRunnable() { - @Override - public void run() { - processedLocations.clear(); - updateWeatherDataForAllWorlds(); - } - }; - weatherUpdateTask.runTaskTimerAsynchronously(this, 0L, updateInterval * 20L); - } - - private void startSyncTask() { - syncTask = new BukkitRunnable() { - @Override - public void run() { - for (World world : Bukkit.getWorlds()) { - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (!config.enabled) continue; - WeatherData data = worldWeatherData.get(world.getName()); - if (data != null) { - syncMinecraftTime(world, data.dateTime); - updateActionbarForWorld(world, data); - } - } - } - }; - syncTask.runTaskTimer(this, 0L, 20L); - } - - private void updateWeatherDataForAllWorlds() { - for (World world : Bukkit.getWorlds()) { - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (!config.enabled) { - worldWeatherData.remove(world.getName()); - continue; - } - if (processedLocations.contains(config.location)) { - for (String otherWorld : worldWeatherData.keySet()) { - WorldConfig otherConfig = worldConfigs.getOrDefault(otherWorld, worldConfigs.get("defaults")); - if (otherConfig.location.equals(config.location)) { - WeatherData existingData = worldWeatherData.get(otherWorld); - if (existingData != null) { - worldWeatherData.put(world.getName(), existingData); - break; - } - } - } - continue; - } - try { - WeatherFetcher fetcher = new WeatherFetcher(apiKey, config.location, config.units, getLogger()); - WeatherFetcher.WeatherData weatherData = fetcher.fetch(); - WeatherData newData = new WeatherData( - weatherData.dateTime, - weatherData.weatherMain, - weatherData.temperature - ); - worldWeatherData.put(world.getName(), newData); - processedLocations.add(config.location); - - new BukkitRunnable() { - @Override - public void run() { - syncMinecraftTime(world, weatherData.dateTime); - if (config.syncInGameWeather) { - int durationTicks = 20 * 60 * 5; // 5 Minuten - switch (weatherData.weatherMain.toLowerCase()) { - case "rain": - case "drizzle": - world.setStorm(true); - world.setThundering(false); - world.setWeatherDuration(durationTicks); - break; - case "thunderstorm": - world.setStorm(true); - world.setThundering(true); - world.setWeatherDuration(durationTicks); - break; - case "snow": - world.setStorm(true); - world.setThundering(false); - world.setWeatherDuration(durationTicks); - break; - default: - world.setStorm(false); - world.setThundering(false); - world.setWeatherDuration(durationTicks); - break; - } - } - } - }.runTask(this); - } catch (Exception e) { - getLogger().warning("Fehler beim Abrufen von Wetter/Zeit für Welt " + world.getName() + ": " + e.getMessage()); - String countryCode = getCountryCode(config.location); - String errorMsg = getLocalizedMessage("forecast_error", countryCode, "location", config.location, "error", e.getMessage()); - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.hasPermission("realtimeweather.admin")) { - audiences.player(player).sendMessage(Component.text(errorMsg, NamedTextColor.RED)); - } - } - } - } - } - - private void initializePlayerDisplays() { - playersWithDisplay.clear(); - for (Player player : Bukkit.getOnlinePlayers()) { - HashSet worlds = new HashSet<>(); - for (World world : Bukkit.getWorlds()) { - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (config.enabled && config.displayActionbar) { - worlds.add(world.getName()); - } - } - playersWithDisplay.put(player.getUniqueId(), worlds); - } - } - - private void syncMinecraftTime(World world, ZonedDateTime dateTime) { - int hour = dateTime.getHour(); - int minute = dateTime.getMinute(); - int second = dateTime.getSecond(); - int minecraftTime = ((hour + 18) % 24) * 1000 + (minute * 1000 / 60) + (second * 1000 / 3600); - world.setTime(minecraftTime); - world.setGameRuleValue("doDaylightCycle", "false"); - } - - private void updateActionbarForWorld(World world, WeatherData data) { - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (!config.enabled || !config.displayActionbar) return; - - for (Player player : world.getPlayers()) { - if (!playersWithDisplay.getOrDefault(player.getUniqueId(), new HashSet<>()).contains(world.getName())) { - continue; - } - WeatherData playerData = data; - String location = playerLocations.getOrDefault(player.getUniqueId(), ""); - if (!location.isEmpty()) { - try { - WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); - WeatherFetcher.WeatherData fetched = fetcher.fetch(); - playerData = new WeatherData(fetched.dateTime, fetched.weatherMain, fetched.temperature); - } catch (Exception e) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("forecast_error", countryCode, "location", location, "error", e.getMessage()), - NamedTextColor.RED)); - continue; - } - } - - String dateStr = playerData.dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")); - String timeStr = formatTime(playerData.dateTime, config.timeFormat); - String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; - String weatherIcon = config.displayWeatherIcon ? getWeatherSymbol(playerData.weatherMain) + " " : ""; - String tempStr = String.format("%.1f%s", playerData.temperature, tempUnit); - - Component message = Component.text() - .append(Component.text(dateStr + " ", NamedTextColor.AQUA, TextDecoration.BOLD)) - .append(Component.text(timeStr + " ", NamedTextColor.YELLOW)) - .append(Component.text(weatherIcon, NamedTextColor.WHITE)) - .append(Component.text(tempStr, NamedTextColor.GREEN)) - .build(); - - Component paddedMessage; - if ("top-right".equalsIgnoreCase(config.displayPosition)) { - paddedMessage = Component.text() - .append(Component.text(" ".repeat(config.paddingRight))) - .append(message) - .build(); - } else { - paddedMessage = Component.text() - .append(message) - .append(Component.text(" ".repeat(20))) - .build(); - } - - audiences.player(player).sendActionBar(paddedMessage); - } - } - - private String formatTime(ZonedDateTime dateTime, String timeFormat) { - int hour = dateTime.getHour(); - int minute = dateTime.getMinute(); - int second = dateTime.getSecond(); - - if ("12h".equalsIgnoreCase(timeFormat)) { - String ampm = hour >= 12 ? "PM" : "AM"; - int hour12 = hour % 12; - if (hour12 == 0) hour12 = 12; - return String.format("%d:%02d:%02d %s", hour12, minute, second, ampm); - } else { - return String.format("%02d:%02d:%02d", hour, minute, second); - } - } - - private String getWeatherSymbol(String weatherMain) { - String w = weatherMain.toLowerCase(); - switch (w) { - case "clear": return "☀️"; - case "clouds": return "☁️"; - case "rain": return "🌧️"; - case "thunderstorm": return "⛈️"; - case "snow": return "❄️"; - case "mist": - case "fog": - case "haze": return "🌫️"; - default: return weatherMain; - } - } - - private String getLocalizedWeatherMain(String weatherMain, String countryCode) { - String key = "weather." + weatherMain.toLowerCase(); - return getLocalizedMessage(key, countryCode); - } - - private String getCountryCode(String location) { - if (location == null || !location.contains(",")) { - getLogger().warning("Ungültiges Location-Format: " + location + ". Fallback auf 'en'."); - return "en"; - } - String[] parts = location.split(","); - String countryCode = parts.length > 1 ? parts[1].trim().toLowerCase() : "en"; - // Überprüfe, ob der Sprachcode in lang.yml existiert - if (!langConfig.contains("languages." + countryCode)) { - getLogger().warning("Sprache '" + countryCode + "' nicht in lang.yml gefunden. Fallback auf 'en'."); - return "en"; - } - return countryCode; - } - - private Inventory createWeatherGUI(Player player) { - String countryCode = getCountryCode(worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")).location); - - Inventory inv = Bukkit.createInventory(null, 9, getLocalizedMessage("gui_title", countryCode)); - - ItemStack setLocation = new ItemStack(Material.BOOK); - ItemStack toggleWeather = new ItemStack(Material.REDSTONE_TORCH); - ItemStack forecast = new ItemStack(Material.PAPER); - ItemStack info = new ItemStack(Material.OAK_SIGN); - - ItemMeta setLocationMeta = setLocation.getItemMeta(); - setLocationMeta.setDisplayName(getLocalizedMessage("gui_setlocation", countryCode)); - setLocationMeta.setLore(Arrays.asList(getLocalizedMessage("gui_setlocation_lore", countryCode))); - setLocation.setItemMeta(setLocationMeta); - - ItemMeta toggleWeatherMeta = toggleWeather.getItemMeta(); - toggleWeatherMeta.setDisplayName(getLocalizedMessage("gui_toggleweather", countryCode)); - toggleWeatherMeta.setLore(Arrays.asList(getLocalizedMessage("gui_toggleweather_lore", countryCode))); - toggleWeather.setItemMeta(toggleWeatherMeta); - - ItemMeta forecastMeta = forecast.getItemMeta(); - forecastMeta.setDisplayName(getLocalizedMessage("gui_forecast", countryCode)); - forecastMeta.setLore(Arrays.asList(getLocalizedMessage("gui_forecast_lore", countryCode))); - forecast.setItemMeta(forecastMeta); - - ItemMeta infoMeta = info.getItemMeta(); - infoMeta.setDisplayName(getLocalizedMessage("gui_info", countryCode)); - infoMeta.setLore(Arrays.asList(getLocalizedMessage("gui_info_lore", countryCode))); - info.setItemMeta(infoMeta); - - inv.setItem(2, setLocation); - inv.setItem(3, toggleWeather); - inv.setItem(4, forecast); - inv.setItem(5, info); - - playerGUIs.put(player.getUniqueId(), inv); - return inv; - } - - @EventHandler - public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { - Player player = event.getPlayer(); - World world = player.getWorld(); - WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); - if (!config.enabled) return; - WeatherData data = worldWeatherData.get(world.getName()); - if (data == null) return; - - String countryCode = getCountryCode(config.location); - String localizedWeather = getLocalizedWeatherMain(data.weatherMain, countryCode); - String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; - String city = config.location.split(",")[0]; - String message = String.format("%s: %s, %.1f%s", city, localizedWeather, data.temperature, tempUnit); - audiences.player(player).sendActionBar(Component.text(message, NamedTextColor.GREEN)); - } - - @EventHandler - public void onInventoryClick(InventoryClickEvent event) { - Player player = (Player) event.getWhoClicked(); - if (!playerGUIs.containsKey(player.getUniqueId()) || event.getInventory() != playerGUIs.get(player.getUniqueId())) { - return; - } - event.setCancelled(true); - ItemStack clickedItem = event.getCurrentItem(); - if (clickedItem == null || clickedItem.getType() == Material.AIR) return; - - WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); - String countryCode = getCountryCode(config.location); - - if (clickedItem.getType() == Material.BOOK) { - player.closeInventory(); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("gui_setlocation_prompt", countryCode), NamedTextColor.YELLOW)); - } else if (clickedItem.getType() == Material.REDSTONE_TORCH) { - String worldName = player.getWorld().getName(); - HashSet worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); - if (worlds.contains(worldName)) { - worlds.remove(worldName); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); - } else { - worlds.add(worldName); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); - } - } else if (clickedItem.getType() == Material.PAPER) { - player.closeInventory(); - new WeatherForecastCommand().onCommand(player, null, "weatherforecast", new String[]{}); - } else if (clickedItem.getType() == Material.OAK_SIGN) { - player.closeInventory(); - String infoMessage = "RealTimeWeather Plugin\nVersion: 1.0\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.8"; - audiences.player(player).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); - } - } - - @EventHandler - public void onInventoryClose(InventoryCloseEvent event) { - Player player = (Player) event.getPlayer(); - playerGUIs.remove(player.getUniqueId()); - } - - private static class WeatherData { - ZonedDateTime dateTime; - String weatherMain; - double temperature; - - public WeatherData(ZonedDateTime dateTime, String weatherMain, double temperature) { - this.dateTime = dateTime; - this.weatherMain = weatherMain; - this.temperature = temperature; - } - } - - private static class WorldConfig { - boolean enabled; - String location; - String units; - String timeFormat; - boolean displayActionbar; - boolean displayWeatherIcon; - String displayPosition; - int paddingRight; - boolean syncInGameWeather; - - public WorldConfig(boolean enabled, String location, String units, String timeFormat, boolean displayActionbar, - boolean displayWeatherIcon, String displayPosition, int paddingRight, boolean syncInGameWeather) { - this.enabled = enabled; - this.location = location; - this.units = units; - this.timeFormat = timeFormat; - this.displayActionbar = displayActionbar; - this.displayWeatherIcon = displayWeatherIcon; - this.displayPosition = displayPosition; - this.paddingRight = paddingRight; - this.syncInGameWeather = syncInGameWeather; - } - } - - private class PlayerConfig { - private final File configFile; - private final FileConfiguration config; - - public PlayerConfig(UUID playerId) { - File playerDir = new File(getDataFolder(), "players"); - playerDir.mkdirs(); - configFile = new File(playerDir, playerId.toString() + ".yml"); - config = YamlConfiguration.loadConfiguration(configFile); - } - - public String getLocation() { - return config.getString("location", ""); - } - - public void setLocation(String location) { - config.set("location", location); - try { - config.save(configFile); - } catch (IOException e) { - getLogger().warning("Fehler beim Speichern der Spieler-Konfiguration: " + e.getMessage()); - } - } - } - - private class WetterCommand implements CommandExecutor { - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (args.length == 0) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("usage", countryCode), NamedTextColor.RED)); - return true; - } - - if (args[0].equalsIgnoreCase("help")) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - Component helpMessage = Component.text() - .append(Component.text(getLocalizedMessage("help_header", countryCode), NamedTextColor.AQUA, TextDecoration.BOLD)) - .append(Component.newline()) - .append(Component.text("/wetter help", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_help", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/wetter reload", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_reload", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/wetter setlocation ", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_setlocation", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/wetter query", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_query", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/wetter info", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_info", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/wetter gui", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_gui", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/weatherforecast", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_weatherforecast", countryCode), NamedTextColor.WHITE)) - .append(Component.newline()) - .append(Component.text("/toggleweather", NamedTextColor.YELLOW)) - .append(Component.text(" - " + getLocalizedMessage("help_toggleweather", countryCode), NamedTextColor.WHITE)) - .build(); - audiences.sender(sender).sendMessage(helpMessage); - return true; - } - - if (args[0].equalsIgnoreCase("reload")) { - if (!sender.hasPermission("realtimeweather.reload")) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("no_permission", countryCode), NamedTextColor.RED)); - return true; - } - - if (weatherUpdateTask != null) { - weatherUpdateTask.cancel(); - } - if (syncTask != null) { - syncTask.cancel(); - } - - reloadConfig(); - loadConfig(); - loadLanguageConfig(); - worldWeatherData.clear(); - processedLocations.clear(); - initializePlayerDisplays(); - - setDoDaylightCycleForWorlds(); - startWeatherUpdateTask(); - startSyncTask(); - processedLocations.clear(); - updateWeatherDataForAllWorlds(); - - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("reload_success", countryCode), NamedTextColor.GREEN)); - return true; - } - - if (args[0].equalsIgnoreCase("setlocation")) { - if (!(sender instanceof Player)) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); - return true; - } - if (args.length != 2) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("usage_setlocation", countryCode), NamedTextColor.RED)); - return true; - } - Player player = (Player) sender; - String location = args[1]; - new BukkitRunnable() { - @Override - public void run() { - try { - WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, "metric", getLogger()); - fetcher.fetch(); // Teste, ob der Ort gültig ist - PlayerConfig playerConfig = new PlayerConfig(player.getUniqueId()); - playerConfig.setLocation(location); - playerLocations.put(player.getUniqueId(), location); - String countryCode = getCountryCode(location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("location_set", countryCode, "location", location), - NamedTextColor.GREEN)); - } catch (Exception e) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("invalid_location", countryCode, "location", location), - NamedTextColor.RED)); - } - } - }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); - return true; - } - - if (args[0].equalsIgnoreCase("query")) { - if (!(sender instanceof Player)) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); - return true; - } - Player player = (Player) sender; - WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); - if (!config.enabled) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("plugin_disabled", countryCode, "world", player.getWorld().getName()), - NamedTextColor.RED)); - return true; - } - String location = playerLocations.getOrDefault(player.getUniqueId(), config.location); - new BukkitRunnable() { - @Override - public void run() { - try { - WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); - WeatherFetcher.WeatherData data = fetcher.fetch(); - String countryCode = getCountryCode(location); - String localizedWeather = getLocalizedWeatherMain(data.weatherMain, countryCode); - String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; - String city = location.split(",")[0]; - String message = String.format("%s: %s, %.1f%s", city, localizedWeather, data.temperature, tempUnit); - audiences.player(player).sendMessage(Component.text(message, NamedTextColor.GREEN)); - } catch (Exception e) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("forecast_error", countryCode, "location", location, "error", e.getMessage()), - NamedTextColor.RED)); - } - } - }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); - return true; - } - - if (args[0].equalsIgnoreCase("info")) { - String infoMessage = "RealTimeWeather Plugin\nVersion: 1.0\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.8"; - audiences.sender(sender).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); - return true; - } - - if (args[0].equalsIgnoreCase("gui")) { - if (!(sender instanceof Player)) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); - return true; - } - Player player = (Player) sender; - WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); - if (!config.enabled) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("plugin_disabled", countryCode, "world", player.getWorld().getName()), - NamedTextColor.RED)); - return true; - } - player.openInventory(createWeatherGUI(player)); - return true; - } - - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("usage", countryCode), NamedTextColor.RED)); - return true; - } - } - - private class WeatherForecastCommand implements CommandExecutor { - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); - return true; - } - Player player = (Player) sender; - WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); - if (!config.enabled) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("plugin_disabled", countryCode, "world", player.getWorld().getName()), - NamedTextColor.RED)); - return true; - } - String location = playerLocations.getOrDefault(player.getUniqueId(), config.location); - new BukkitRunnable() { - @Override - public void run() { - try { - WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); - JSONObject json = fetcher.fetchForecast(); - JSONArray list = json.getJSONArray("list"); - - String[] locationParts = location.split(","); - String countryCode = locationParts.length > 1 ? locationParts[1].trim() : ""; - String city = locationParts[0].trim(); - - String forecastMessage = getLocalizedMessage("forecast_header", countryCode, "location", city); - Component message = Component.text(forecastMessage, NamedTextColor.AQUA); - - for (int i = 0; i < list.length() && i < 40; i += 8) { - JSONObject forecast = list.getJSONObject(i); - long dt = forecast.getLong("dt"); - String weatherMain = forecast.getJSONArray("weather").getJSONObject(0).getString("main"); - double temp = forecast.getJSONObject("main").getDouble("temp"); - ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC")); - - String localizedWeatherMain = getLocalizedWeatherMain(weatherMain, countryCode); - - message = message.append(Component.newline()) - .append(Component.text( - dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) + ": " + - localizedWeatherMain + ", " + String.format("%.1f%s", temp, config.units.equalsIgnoreCase("metric") ? "°C" : "°F"), - NamedTextColor.GREEN)); - } - audiences.player(player).sendMessage(message); - } catch (Exception e) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("forecast_error", countryCode, "location", location, "error", e.getMessage()), - NamedTextColor.RED)); - } - } - }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); - return true; - } - } - - private class ToggleWeatherCommand implements CommandExecutor { - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - String countryCode = getCountryCode(worldConfigs.get("defaults").location); - audiences.sender(sender).sendMessage(Component.text( - getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); - return true; - } - Player player = (Player) sender; - String worldName = player.getWorld().getName(); - WorldConfig config = worldConfigs.getOrDefault(worldName, worldConfigs.get("defaults")); - if (!config.enabled) { - String countryCode = getCountryCode(config.location); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("plugin_disabled", countryCode, "world", worldName), - NamedTextColor.RED)); - return true; - } - String countryCode = getCountryCode(config.location); - HashSet worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); - if (worlds.contains(worldName)) { - worlds.remove(worldName); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); - } else { - worlds.add(worldName); - audiences.player(player).sendMessage(Component.text( - getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); - } - return true; - } - } -} \ No newline at end of file +package dev.viper.weathertime; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.command.*; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener { + + private String apiKey; + private int updateInterval; + private final Map worldConfigs = new HashMap<>(); + private final Map worldWeatherData = new HashMap<>(); + private final Map> playersWithDisplay = new HashMap<>(); + private final Map playerLocations = new HashMap<>(); + private final Map playerGUIs = new HashMap<>(); + private BukkitAudiences audiences; + private BukkitRunnable weatherUpdateTask; + private BukkitRunnable syncTask; + private final Set processedLocations = new HashSet<>(); + private FileConfiguration langConfig; + + @Override + public void onEnable() { + saveDefaultConfig(); + loadLanguageConfig(); + loadConfig(); + audiences = BukkitAudiences.create(this); + + Objects.requireNonNull(getCommand("wetter")).setExecutor(new WetterCommand()); + Objects.requireNonNull(getCommand("weatherforecast")).setExecutor(new WeatherForecastCommand()); + Objects.requireNonNull(getCommand("toggleweather")).setExecutor(new ToggleWeatherCommand()); + + getServer().getPluginManager().registerEvents(this, this); + + setDoDaylightCycleForWorlds(); + startWeatherUpdateTask(); + startSyncTask(); + initializePlayerDisplays(); + } + + @Override + public void onDisable() { + if (weatherUpdateTask != null) weatherUpdateTask.cancel(); + if (syncTask != null) syncTask.cancel(); + if (audiences != null) audiences.close(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (playerGUIs.containsKey(player.getUniqueId())) player.closeInventory(); + } + } + + // =============== Config & Sprache =============== + private void loadConfig() { + FileConfiguration cfg = getConfig(); + apiKey = cfg.getString("api-key", "").trim(); + updateInterval = cfg.getInt("update-interval", 60); + worldConfigs.clear(); + + WorldConfig defaultConfig = new WorldConfig( + cfg.getBoolean("defaults.enabled", true), + cfg.getString("defaults.location", "Berlin,de"), + cfg.getString("defaults.units", "metric"), + cfg.getString("defaults.time-format", "24h"), + cfg.getBoolean("defaults.display-actionbar", true), + cfg.getBoolean("defaults.display-weather-icon", true), + cfg.getString("defaults.display-position", "top-right"), + cfg.getInt("defaults.padding-right", 100), + cfg.getBoolean("defaults.sync-in-game-weather", true) + ); + worldConfigs.put("defaults", defaultConfig); + + if (cfg.isConfigurationSection("worlds")) { + for (String worldName : cfg.getConfigurationSection("worlds").getKeys(false)) { + WorldConfig worldConfig = new WorldConfig( + cfg.getBoolean("worlds." + worldName + ".enabled", defaultConfig.enabled), + cfg.getString("worlds." + worldName + ".location", defaultConfig.location), + cfg.getString("worlds." + worldName + ".units", defaultConfig.units), + cfg.getString("worlds." + worldName + ".time-format", defaultConfig.timeFormat), + cfg.getBoolean("worlds." + worldName + ".display-actionbar", defaultConfig.displayActionbar), + cfg.getBoolean("worlds." + worldName + ".display-weather-icon", defaultConfig.displayWeatherIcon), + cfg.getString("worlds." + worldName + ".display-position", defaultConfig.displayPosition), + cfg.getInt("worlds." + worldName + ".padding-right", defaultConfig.paddingRight), + cfg.getBoolean("worlds." + worldName + ".sync-in-game-weather", defaultConfig.syncInGameWeather) + ); + worldConfigs.put(worldName, worldConfig); + } + } + if (apiKey.isEmpty()) { + getLogger().severe("API-Key ist nicht gesetzt! Bitte in config.yml eintragen."); + } + } + + private void loadLanguageConfig() { + File langFile = new File(getDataFolder(), "lang.yml"); + try { + langConfig = YamlConfiguration.loadConfiguration(langFile); + if (!langFile.exists()) { + saveResource("lang.yml", false); + langConfig = YamlConfiguration.loadConfiguration(langFile); + } else if (langConfig.getConfigurationSection("languages") == null) { + saveResource("lang.yml", true); + langConfig = YamlConfiguration.loadConfiguration(langFile); + } + } catch (Exception e) { + saveResource("lang.yml", true); + langConfig = YamlConfiguration.loadConfiguration(langFile); + } + } + + private void setDoDaylightCycleForWorlds() { + for (World world : Bukkit.getWorlds()) { + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (config.enabled) world.setGameRuleValue("doDaylightCycle", "false"); + } + } + + private void initializePlayerDisplays() { + playersWithDisplay.clear(); + for (Player player : Bukkit.getOnlinePlayers()) { + Set worlds = new HashSet<>(); + for (World world : Bukkit.getWorlds()) { + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (config.enabled && config.displayActionbar) worlds.add(world.getName()); + } + playersWithDisplay.put(player.getUniqueId(), worlds); + } + } + + private void startWeatherUpdateTask() { + weatherUpdateTask = new BukkitRunnable() { + @Override + public void run() { + processedLocations.clear(); + updateWeatherDataForAllWorlds(); + } + }; + weatherUpdateTask.runTaskTimerAsynchronously(this, 0L, updateInterval * 20L); + } + + private void startSyncTask() { + syncTask = new BukkitRunnable() { + @Override + public void run() { + for (World world : Bukkit.getWorlds()) { + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (!config.enabled) continue; + WeatherTimeData data = worldWeatherData.get(world.getName()); + if (data != null) { + syncMinecraftTime(world, data.getDateTime()); + updateActionbarForWorld(world, data); + } + } + } + }; + syncTask.runTaskTimer(this, 0L, 20L); + } + + private void updateWeatherDataForAllWorlds() { + for (World world : Bukkit.getWorlds()) { + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (!config.enabled) { + worldWeatherData.remove(world.getName()); + continue; + } + if (processedLocations.contains(config.location)) { + for (String otherWorld : worldWeatherData.keySet()) { + WorldConfig otherConfig = worldConfigs.getOrDefault(otherWorld, worldConfigs.get("defaults")); + if (otherConfig.location.equals(config.location)) { + WeatherTimeData existingData = worldWeatherData.get(otherWorld); + if (existingData != null) { + worldWeatherData.put(world.getName(), existingData); + break; + } + } + } + continue; + } + try { + WeatherFetcher fetcher = new WeatherFetcher(apiKey, config.location, config.units, getLogger()); + WeatherTimeData weatherData = fetcher.fetchAsWeatherTimeData(); + worldWeatherData.put(world.getName(), weatherData); + processedLocations.add(config.location); + + new BukkitRunnable() { + @Override + public void run() { + syncMinecraftTime(world, weatherData.getDateTime()); + if (config.syncInGameWeather) { + switch (weatherData.getWeatherMain().toLowerCase()) { + case "rain": + case "drizzle": + world.setStorm(true); + world.setThundering(false); + break; + case "thunderstorm": + world.setStorm(true); + world.setThundering(true); + break; + case "snow": + world.setStorm(true); + world.setThundering(false); + break; + default: + world.setStorm(false); + world.setThundering(false); + } + } + } + }.runTask(this); + } catch (Exception e) { + getLogger().warning("Fehler beim Abrufen von Wetter/Zeit für Welt " + world.getName() + ": " + e.getMessage()); + } + } + } + + private void syncMinecraftTime(World world, ZonedDateTime dateTime) { + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + int second = dateTime.getSecond(); + int minecraftTime = ((hour + 18) % 24) * 1000 + (minute * 1000 / 60) + (second * 1000 / 3600); + world.setTime(minecraftTime); + world.setGameRuleValue("doDaylightCycle", "false"); + } + + // === Anzeige (Actionbar, jetzt mit Luftfeuchtigkeit, Wind, Sonne) === + private void updateActionbarForWorld(World world, WeatherTimeData data) { + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (!config.enabled || !config.displayActionbar) return; + + for (Player player : world.getPlayers()) { + if (!playersWithDisplay.getOrDefault(player.getUniqueId(), new HashSet<>()).contains(world.getName())) + continue; + + WeatherTimeData playerData = data; + String location = playerLocations.getOrDefault(player.getUniqueId(), ""); + if (!location.isEmpty()) { + try { + WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); + playerData = fetcher.fetchAsWeatherTimeData(); + } catch (Exception e) { + String countryCode = getCountryCode(config.location); + audiences.player(player).sendMessage(Component.text( + getLocalizedMessage("forecast_error", countryCode, + "location", location, + "error", e.getMessage()), + NamedTextColor.RED)); + continue; + } + } + + String dateStr = playerData.getDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy")); + String timeStr = playerData.getFormattedTime(config.timeFormat); + String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; + String weatherIcon = config.displayWeatherIcon ? getWeatherSymbol(playerData.getWeatherMain()) + " " : ""; + String tempStr = String.format("%.1f%s", playerData.getTemp(config.units.equalsIgnoreCase("metric") ? "C" : "F"), tempUnit); + + String humidityStr = "💧 " + playerData.getHumidity() + "%"; + String windStr = "🌬️ " + String.format("%.1f", playerData.getWindSpeed()) + (config.units.equalsIgnoreCase("metric") ? " m/s" : " mph"); + String sunriseStr = "🌅 " + playerData.getSunrise().format(DateTimeFormatter.ofPattern("HH:mm")); + String sunsetStr = "🌇 " + playerData.getSunset().format(DateTimeFormatter.ofPattern("HH:mm")); + + Component message = Component.text() + .append(Component.text(dateStr + " ", NamedTextColor.AQUA, TextDecoration.BOLD)) + .append(Component.text("| " + timeStr + " ", NamedTextColor.YELLOW)) + .append(Component.text("| " + weatherIcon, NamedTextColor.WHITE)) + .append(Component.text("| " + tempStr + " ", NamedTextColor.GREEN)) + .append(Component.text("| " + humidityStr + " ", NamedTextColor.AQUA)) + .append(Component.text("| " + windStr + " ", NamedTextColor.GRAY)) + .append(Component.text("| " + sunriseStr + " ", NamedTextColor.GOLD)) + .append(Component.text("| " + sunsetStr, NamedTextColor.DARK_RED)) + .build(); + + audiences.player(player).sendActionBar(message); + } + } + + // === Events (z.B. Weltwechsel) === + @EventHandler + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + World world = player.getWorld(); + WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); + if (!config.enabled) return; + WeatherTimeData data = worldWeatherData.get(world.getName()); + if (data == null) return; + String countryCode = getCountryCode(config.location); + String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), countryCode); + String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; + String city = config.location.split(",")[0]; + String message = String.format("%s: %s, %.1f%s", city, localizedWeather, data.getTemp(config.units.equalsIgnoreCase("metric") ? "C" : "F"), tempUnit); + audiences.player(player).sendActionBar(Component.text(message, NamedTextColor.GREEN)); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + Inventory expectedGUI = playerGUIs.get(player.getUniqueId()); + if (expectedGUI == null || event.getInventory() != expectedGUI) return; + event.setCancelled(true); + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null || clickedItem.getType() == Material.AIR) return; + + WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); + String countryCode = getCountryCode(config.location); + + if (clickedItem.getType() == Material.BOOK) { + player.closeInventory(); + audiences.player(player).sendMessage(Component.text( + getLocalizedMessage("gui_setlocation_prompt", countryCode), NamedTextColor.YELLOW)); + } else if (clickedItem.getType() == Material.REDSTONE_TORCH) { + String worldName = player.getWorld().getName(); + Set worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); + if (worlds.contains(worldName)) { + worlds.remove(worldName); + audiences.player(player).sendMessage(Component.text( + getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); + } else { + worlds.add(worldName); + audiences.player(player).sendMessage(Component.text( + getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); + } + } else if (clickedItem.getType() == Material.PAPER) { + player.closeInventory(); + new WeatherForecastCommand().onCommand(player, null, "weatherforecast", new String[]{}); + } else if (clickedItem.getType() == Material.OAK_SIGN) { + player.closeInventory(); + String infoMessage = "RealTimeWeather Plugin\nVersion: 1.0\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.8"; + audiences.player(player).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + playerGUIs.remove(event.getPlayer().getUniqueId()); + } + + // === Hilfsmethoden & Modellklassen === + + private String getLocalizedMessage(String key, String countryCode, String... placeholders) { + String path = "languages." + (countryCode.isEmpty() ? "en" : countryCode.toLowerCase()) + "." + key; + String message = langConfig.getString(path); + if (message == null) { + path = "languages.en." + key; + message = langConfig.getString(path, key); + } + for (int i = 0; i < placeholders.length - 1; i += 2) { + message = message.replace("{" + placeholders[i] + "}", placeholders[i + 1]); + } + return message; + } + + private String getWeatherSymbol(String weatherMain) { + switch (weatherMain.toLowerCase()) { + case "clear": return "☀️"; + case "clouds": return "☁️"; + case "rain": return "🌧️"; + case "thunderstorm": return "⛈️"; + case "snow": return "❄️"; + case "mist": + case "fog": + case "haze": return "🌫️"; + default: return weatherMain; + } + } + + private String getLocalizedWeatherMain(String weatherMain, String countryCode) { + String key = "weather." + weatherMain.toLowerCase(); + return getLocalizedMessage(key, countryCode); + } + + private String getCountryCode(String location) { + if (location == null || !location.contains(",")) return "en"; + String[] parts = location.split(","); + String countryCode = parts.length > 1 ? parts[1].trim().toLowerCase() : "en"; + if (!langConfig.contains("languages." + countryCode)) return "en"; + return countryCode; + } + + private Inventory createWeatherGUI(Player player) { + String countryCode = getCountryCode(worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")).location); + Inventory inv = Bukkit.createInventory(null, 9, getLocalizedMessage("gui_title", countryCode)); + ItemStack setLocation = new ItemStack(Material.BOOK); + ItemStack toggleWeather = new ItemStack(Material.REDSTONE_TORCH); + ItemStack forecast = new ItemStack(Material.PAPER); + ItemStack info = new ItemStack(Material.OAK_SIGN); + + ItemMeta setLocationMeta = setLocation.getItemMeta(); + setLocationMeta.setDisplayName(getLocalizedMessage("gui_setlocation", countryCode)); + setLocationMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_setlocation_lore", countryCode))); + setLocation.setItemMeta(setLocationMeta); + + ItemMeta toggleWeatherMeta = toggleWeather.getItemMeta(); + toggleWeatherMeta.setDisplayName(getLocalizedMessage("gui_toggleweather", countryCode)); + toggleWeatherMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_toggleweather_lore", countryCode))); + toggleWeather.setItemMeta(toggleWeatherMeta); + + ItemMeta forecastMeta = forecast.getItemMeta(); + forecastMeta.setDisplayName(getLocalizedMessage("gui_forecast", countryCode)); + forecastMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_forecast_lore", countryCode))); + forecast.setItemMeta(forecastMeta); + + ItemMeta infoMeta = info.getItemMeta(); + infoMeta.setDisplayName(getLocalizedMessage("gui_info", countryCode)); + infoMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_info_lore", countryCode))); + info.setItemMeta(infoMeta); + + inv.setItem(2, setLocation); + inv.setItem(3, toggleWeather); + inv.setItem(4, forecast); + inv.setItem(5, info); + + playerGUIs.put(player.getUniqueId(), inv); + return inv; + } + + // ==== PlayerConfig etc. wie gehabt ==== + private class PlayerConfig { + private final File configFile; + private final FileConfiguration config; + public PlayerConfig(UUID playerId) { + File playerDir = new File(getDataFolder(), "players"); + playerDir.mkdirs(); + configFile = new File(playerDir, playerId.toString() + ".yml"); + config = YamlConfiguration.loadConfiguration(configFile); + } + public String getLocation() { + return config.getString("location", ""); + } + public void setLocation(String location) { + config.set("location", location); + try { + config.save(configFile); + } catch (IOException e) { + getLogger().warning("Fehler beim Speichern der Spieler-Konfiguration: " + e.getMessage()); + } + } + } + + // ==== Commands ==== + private class WetterCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + String countryCode = getCountryCode(worldConfigs.get("defaults").location); + if (args.length == 0) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("usage", countryCode), NamedTextColor.RED)); + return true; + } + switch (args[0].toLowerCase(Locale.ROOT)) { + case "help": { + Component helpMessage = Component.text() + .append(Component.text(getLocalizedMessage("help_header", countryCode), NamedTextColor.AQUA, TextDecoration.BOLD)) + .append(Component.newline()) + .append(Component.text("/wetter help", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_help", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/wetter reload", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_reload", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/wetter setlocation ", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_setlocation", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/wetter query", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_query", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/wetter info", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_info", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/wetter gui", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_gui", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/weatherforecast", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_weatherforecast", countryCode), NamedTextColor.WHITE)) + .append(Component.newline()) + .append(Component.text("/toggleweather", NamedTextColor.YELLOW)) + .append(Component.text(" - " + getLocalizedMessage("help_toggleweather", countryCode), NamedTextColor.WHITE)) + .build(); + audiences.sender(sender).sendMessage(helpMessage); + return true; + } + case "reload": { + if (!sender.hasPermission("realtimeweather.reload")) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("no_permission", countryCode), NamedTextColor.RED)); + return true; + } + if (weatherUpdateTask != null) weatherUpdateTask.cancel(); + if (syncTask != null) syncTask.cancel(); + reloadConfig(); + loadConfig(); + loadLanguageConfig(); + worldWeatherData.clear(); + processedLocations.clear(); + initializePlayerDisplays(); + setDoDaylightCycleForWorlds(); + startWeatherUpdateTask(); + startSyncTask(); + processedLocations.clear(); + updateWeatherDataForAllWorlds(); + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("reload_success", countryCode), NamedTextColor.GREEN)); + return true; + } + case "setlocation": { + if (!(sender instanceof Player)) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); + return true; + } + if (args.length != 2) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("usage_setlocation", countryCode), NamedTextColor.RED)); + return true; + } + Player player = (Player) sender; + String location = args[1]; + new BukkitRunnable() { + @Override + public void run() { + try { + WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, "metric", getLogger()); + fetcher.fetchAsWeatherTimeData(); + PlayerConfig playerConfig = new PlayerConfig(player.getUniqueId()); + playerConfig.setLocation(location); + playerLocations.put(player.getUniqueId(), location); + String cc = getCountryCode(location); + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("location_set", cc, "location", location), NamedTextColor.GREEN)); + } catch (Exception e) { + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("invalid_location", countryCode, "location", location), NamedTextColor.RED)); + } + } + }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); + return true; + } + case "query": { + if (!(sender instanceof Player)) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); + return true; + } + Player queryPlayer = (Player) sender; + WorldConfig config = worldConfigs.getOrDefault(queryPlayer.getWorld().getName(), worldConfigs.get("defaults")); + if (!config.enabled) { + audiences.player(queryPlayer).sendMessage(Component.text(getLocalizedMessage("plugin_disabled", countryCode, "world", queryPlayer.getWorld().getName()), NamedTextColor.RED)); + return true; + } + String queryLocation = playerLocations.getOrDefault(queryPlayer.getUniqueId(), config.location); + new BukkitRunnable() { + @Override + public void run() { + try { + WeatherFetcher fetcher = new WeatherFetcher(apiKey, queryLocation, config.units, getLogger()); + WeatherTimeData data = fetcher.fetchAsWeatherTimeData(); + String cc = getCountryCode(queryLocation); + String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), cc); + String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; + String city = queryLocation.split(",")[0]; + String message = String.format("%s: %s, %.1f%s", city, localizedWeather, data.getTemp(config.units.equalsIgnoreCase("metric") ? "C" : "F"), tempUnit); + audiences.player(queryPlayer).sendMessage(Component.text(message, NamedTextColor.GREEN)); + } catch (Exception e) { + audiences.player(queryPlayer).sendMessage(Component.text(getLocalizedMessage("forecast_error", countryCode, "location", queryLocation, "error", e.getMessage()), NamedTextColor.RED)); + } + } + }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); + return true; + } + case "info": { + String infoMessage = "RealTimeWeather Plugin\nVersion: 1.0\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.8"; + audiences.sender(sender).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); + return true; + } + case "gui": { + if (!(sender instanceof Player)) { + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); + return true; + } + Player guiPlayer = (Player) sender; + WorldConfig guiConfig = worldConfigs.getOrDefault(guiPlayer.getWorld().getName(), worldConfigs.get("defaults")); + if (!guiConfig.enabled) { + String cc = getCountryCode(guiConfig.location); + audiences.player(guiPlayer).sendMessage(Component.text(getLocalizedMessage("plugin_disabled", cc, "world", guiPlayer.getWorld().getName()), NamedTextColor.RED)); + return true; + } + guiPlayer.openInventory(createWeatherGUI(guiPlayer)); + return true; + } + default: + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("usage", countryCode), NamedTextColor.RED)); + return true; + } + } + } + + private class WeatherForecastCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + String countryCode = getCountryCode(worldConfigs.get("defaults").location); + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); + return true; + } + Player player = (Player) sender; + WorldConfig config = worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")); + if (!config.enabled) { + String countryCode = getCountryCode(config.location); + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("plugin_disabled", countryCode, "world", player.getWorld().getName()), NamedTextColor.RED)); + return true; + } + String location = playerLocations.getOrDefault(player.getUniqueId(), config.location); + new BukkitRunnable() { + @Override + public void run() { + try { + WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); + JSONObject json = fetcher.fetchForecast(); + JSONArray list = json.getJSONArray("list"); + + String[] locationParts = location.split(","); + String countryCode = locationParts.length > 1 ? locationParts[1].trim() : ""; + String city = locationParts[0].trim(); + + String forecastMessage = getLocalizedMessage("forecast_header", countryCode, "location", city); + Component message = Component.text(forecastMessage, NamedTextColor.AQUA); + + for (int i = 0; i < list.length() && i < 40; i += 8) { + JSONObject forecast = list.getJSONObject(i); + long dt = forecast.getLong("dt"); + String weatherMain = forecast.getJSONArray("weather").getJSONObject(0).getString("main"); + double temp = forecast.getJSONObject("main").getDouble("temp"); + ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC")); + + String localizedWeatherMain = getLocalizedWeatherMain(weatherMain, countryCode); + + message = message.append(Component.newline()) + .append(Component.text( + dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) + ": " + + localizedWeatherMain + ", " + String.format("%.1f%s", temp, config.units.equalsIgnoreCase("metric") ? "°C" : "°F"), + NamedTextColor.GREEN)); + } + audiences.player(player).sendMessage(message); + } catch (Exception e) { + String countryCode = getCountryCode(config.location); + audiences.player(player).sendMessage(Component.text( + getLocalizedMessage("forecast_error", countryCode, "location", location, "error", e.getMessage()), + NamedTextColor.RED)); + } + } + }.runTaskAsynchronously(WeatherTimeSyncPlugin.this); + return true; + } + } + + private class ToggleWeatherCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + String countryCode = getCountryCode(worldConfigs.get("defaults").location); + audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("only_players", countryCode), NamedTextColor.RED)); + return true; + } + Player player = (Player) sender; + String worldName = player.getWorld().getName(); + WorldConfig config = worldConfigs.getOrDefault(worldName, worldConfigs.get("defaults")); + if (!config.enabled) { + String countryCode = getCountryCode(config.location); + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("plugin_disabled", countryCode, "world", worldName), NamedTextColor.RED)); + return true; + } + String countryCode = getCountryCode(config.location); + Set worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); + if (worlds.contains(worldName)) { + worlds.remove(worldName); + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); + } else { + worlds.add(worldName); + audiences.player(player).sendMessage(Component.text(getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); + } + return true; + } + } + + // Modellklasse WorldConfig (nur als Speicher für die Welten-Settings) + private static class WorldConfig { + boolean enabled; + String location; + String units; + String timeFormat; + boolean displayActionbar; + boolean displayWeatherIcon; + String displayPosition; + int paddingRight; + boolean syncInGameWeather; + + public WorldConfig(boolean enabled, String location, String units, String timeFormat, + boolean displayActionbar, boolean displayWeatherIcon, + String displayPosition, int paddingRight, boolean syncInGameWeather) { + this.enabled = enabled; + this.location = location; + this.units = units; + this.timeFormat = timeFormat; + this.displayActionbar = displayActionbar; + this.displayWeatherIcon = displayWeatherIcon; + this.displayPosition = displayPosition; + this.paddingRight = paddingRight; + this.syncInGameWeather = syncInGameWeather; + } + } +}