Update from Git Manager GUI

This commit is contained in:
2026-02-28 13:36:51 +01:00
parent 9ace86a856
commit fa599de701
3 changed files with 431 additions and 246 deletions

View File

@@ -5,15 +5,13 @@ import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.command.*; import org.bukkit.command.*;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.entity.Snowman;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerChangedWorldEvent;
@@ -34,6 +32,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import dev.viper.weathertime.MetricsManager; import dev.viper.weathertime.MetricsManager;
import dev.viper.weathertime.UpdateChecker; import dev.viper.weathertime.UpdateChecker;
@@ -47,16 +46,22 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
private String apiKey; private String apiKey;
private int updateInterval; private int updateInterval;
private final Map<String, WorldConfig> worldConfigs = new HashMap<>(); private final Map<String, WorldConfig> worldConfigs = new HashMap<>();
private final Map<String, WeatherTimeData> worldWeatherData = new HashMap<>(); private final Map<String, WeatherTimeData> worldWeatherData = new ConcurrentHashMap<>();
// FIX: Scoreboard standardmäßig AUS nur aktiv wenn der Spieler es in der GUI aktiviert hat
private final Map<UUID, Set<String>> playersWithDisplay = new HashMap<>(); private final Map<UUID, Set<String>> playersWithDisplay = new HashMap<>();
private final Map<UUID, String> playerLocations = new HashMap<>(); private final Map<UUID, String> playerLocations = new HashMap<>();
private final Map<UUID, Inventory> playerGUIs = new HashMap<>(); private final Map<UUID, Inventory> playerGUIs = new HashMap<>();
private final Map<UUID, Scoreboard> playerScoreboards = new HashMap<>(); private final Map<UUID, Scoreboard> playerScoreboards = new HashMap<>();
private final Map<UUID, DisplayMode> playerDisplayModes = new HashMap<>(); private final Map<UUID, DisplayMode> playerDisplayModes = new HashMap<>();
// FIX: Gecachte Wetterdaten pro Spieler (custom location) vermeidet Blocking auf dem Main-Thread
private final Map<UUID, WeatherTimeData> playerWeatherCache = new ConcurrentHashMap<>();
private BukkitAudiences audiences; private BukkitAudiences audiences;
private BukkitRunnable weatherUpdateTask; private BukkitRunnable weatherUpdateTask;
private BukkitRunnable syncTask; private BukkitRunnable syncTask;
private final Set<String> processedLocations = new HashSet<>(); private final Set<String> processedLocations = ConcurrentHashMap.newKeySet();
private FileConfiguration langConfig; private FileConfiguration langConfig;
private MetricsManager metricsManager; private MetricsManager metricsManager;
@@ -74,22 +79,26 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
updateChecker = new UpdateChecker(this, 127846); updateChecker = new UpdateChecker(this, 127846);
updateChecker.getLatestVersion(version -> { updateChecker.getLatestVersion(version -> {
// FIX: NumberFormatException absichern falls Version kein valides Format hat
String cleanVersion = version.replaceAll("[^0-9.]", "").trim(); String cleanVersion = version.replaceAll("[^0-9.]", "").trim();
String currentVersion = getDescription().getVersion(); String currentVersion = getDescription().getVersion();
if (isVersionNewer(currentVersion, cleanVersion)) { try {
latestVersion = cleanVersion; if (isVersionNewer(currentVersion, cleanVersion)) {
latestVersion = cleanVersion;
getLogger().info("Neue Version verfügbar: " + cleanVersion + " (aktuell: " + currentVersion + ")");
getLogger().info("Download: https://www.spigotmc.org/resources/127846/");
getLogger().info("Neue Version verfügbar: " + cleanVersion + " (aktuell: " + currentVersion + ")"); for (Player player : Bukkit.getOnlinePlayers()) {
getLogger().info("Download: https://www.spigotmc.org/resources/127846/"); if (player.isOp()) {
player.sendMessage("§aEine neue Version von §eRealTimeWeather §aist verfügbar: §e"
for (Player player : Bukkit.getOnlinePlayers()) { + cleanVersion + " §7(aktuell: " + currentVersion + ")");
if (player.isOp()) { player.sendMessage("§eDownload: §bhttps://www.spigotmc.org/resources/127846/");
player.sendMessage("§aEine neue Version von §eRealTimeWeather §aist verfügbar: §e" }
+ cleanVersion + " §7(aktuell: " + currentVersion + ")");
player.sendMessage("§eDownload: §bhttps://www.spigotmc.org/resources/127846/");
} }
} }
} catch (Exception e) {
getLogger().warning("Versionsvergleich fehlgeschlagen: " + e.getMessage());
} }
}); });
@@ -107,25 +116,36 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
getLogger().info("=============================================="); getLogger().info("==============================================");
getLogger().info("RealTimeWeather nutzt SCOREBOARD-Anzeige!"); getLogger().info("RealTimeWeather nutzt SCOREBOARD-Anzeige!");
getLogger().info("Die Wetteranzeige erscheint rechts am Bildschirm."); getLogger().info("Das Scoreboard ist standardmäßig AUSGEBLENDET.");
getLogger().info("Spieler können es per /wetter gui oder /toggleweather aktivieren.");
getLogger().info("=============================================="); getLogger().info("==============================================");
} }
/**
* FIX: NumberFormatException durch try-catch abgesichert.
* Vergleicht semantische Versionen (z.B. "1.3.0" vs "1.4").
*/
private boolean isVersionNewer(String current, String latest) { private boolean isVersionNewer(String current, String latest) {
if (current == null || current.isEmpty() || latest == null || latest.isEmpty()) return false;
String[] curParts = current.split("\\."); String[] curParts = current.split("\\.");
String[] latParts = latest.split("\\."); String[] latParts = latest.split("\\.");
int length = Math.max(curParts.length, latParts.length); int length = Math.max(curParts.length, latParts.length);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int curNum = i < curParts.length ? Integer.parseInt(curParts[i]) : 0; int curNum, latNum;
int latNum = i < latParts.length ? Integer.parseInt(latParts[i]) : 0; try {
curNum = i < curParts.length ? Integer.parseInt(curParts[i].trim()) : 0;
} catch (NumberFormatException e) {
curNum = 0;
}
try {
latNum = i < latParts.length ? Integer.parseInt(latParts[i].trim()) : 0;
} catch (NumberFormatException e) {
latNum = 0;
}
if (latNum > curNum) { if (latNum > curNum) return true;
return true; if (latNum < curNum) return false;
}
if (latNum < curNum) {
return false;
}
} }
return false; return false;
} }
@@ -168,10 +188,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
cfg.getString("defaults.location", "Berlin,de"), cfg.getString("defaults.location", "Berlin,de"),
cfg.getString("defaults.units", "metric"), cfg.getString("defaults.units", "metric"),
cfg.getString("defaults.time-format", "24h"), 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) cfg.getBoolean("defaults.sync-in-game-weather", true)
); );
worldConfigs.put("defaults", defaultConfig); worldConfigs.put("defaults", defaultConfig);
@@ -183,10 +199,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
cfg.getString("worlds." + worldName + ".location", defaultConfig.location), cfg.getString("worlds." + worldName + ".location", defaultConfig.location),
cfg.getString("worlds." + worldName + ".units", defaultConfig.units), cfg.getString("worlds." + worldName + ".units", defaultConfig.units),
cfg.getString("worlds." + worldName + ".time-format", defaultConfig.timeFormat), 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) cfg.getBoolean("worlds." + worldName + ".sync-in-game-weather", defaultConfig.syncInGameWeather)
); );
worldConfigs.put(worldName, worldConfig); worldConfigs.put(worldName, worldConfig);
@@ -218,25 +230,33 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
for (World world : Bukkit.getWorlds()) { for (World world : Bukkit.getWorlds()) {
WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults"));
if (config.enabled) { if (config.enabled) {
// Daylight-Cycle deaktivieren Zeit wird vom Plugin gesetzt
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
GameRule<Boolean> doDaylightCycle = (GameRule<Boolean>) GameRule.getByName("doDaylightCycle"); GameRule<Boolean> doDaylightCycle = (GameRule<Boolean>) GameRule.getByName("doDaylightCycle");
if (doDaylightCycle != null) { if (doDaylightCycle != null) {
world.setGameRule(doDaylightCycle, false); world.setGameRule(doDaylightCycle, false);
} }
// Weather-Cycle deaktivieren verhindert dass Minecraft das Wetter
// nach ein paar Minuten eigenständig zurücksetzt
@SuppressWarnings("unchecked")
GameRule<Boolean> doWeatherCycle = (GameRule<Boolean>) GameRule.getByName("doWeatherCycle");
if (doWeatherCycle != null) {
world.setGameRule(doWeatherCycle, false);
}
} }
} }
} }
/**
* FIX: Scoreboard wird standardmäßig NICHT aktiviert.
* playersWithDisplay wird nur mit leeren Sets initialisiert.
* Kein createScoreboardForPlayer() beim Start.
*/
private void initializePlayerDisplays() { private void initializePlayerDisplays() {
playersWithDisplay.clear(); playersWithDisplay.clear();
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
Set<String> worlds = new HashSet<>(); // Leeres Set Spieler hat noch kein Scoreboard aktiviert
for (World world : Bukkit.getWorlds()) { playersWithDisplay.put(player.getUniqueId(), new HashSet<>());
WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults"));
if (config.enabled && config.displayActionbar) worlds.add(world.getName());
}
playersWithDisplay.put(player.getUniqueId(), worlds);
createScoreboardForPlayer(player);
} }
} }
@@ -301,26 +321,7 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
public void run() { public void run() {
syncMinecraftTime(world, weatherData.getDateTime()); syncMinecraftTime(world, weatherData.getDateTime());
if (config.syncInGameWeather) { if (config.syncInGameWeather) {
switch (weatherData.getWeatherMain().toLowerCase()) { syncInGameWeather(world, weatherData);
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);
spawnTemporarySnowInWorld(world, WeatherTimeSyncPlugin.this);
spawnTemporarySnowmen(world, 25, WeatherTimeSyncPlugin.this);
break;
default:
world.setStorm(false);
world.setThundering(false);
}
} }
} }
}.runTask(this); }.runTask(this);
@@ -329,78 +330,153 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
getLogger().warning("Fehler beim Abrufen von Wetter/Zeit für Welt " + world.getName() + ": " + e.getMessage()); getLogger().warning("Fehler beim Abrufen von Wetter/Zeit für Welt " + world.getName() + ": " + e.getMessage());
} }
} }
// Custom-Location-Wetterdaten für Spieler asynchron aktualisieren (kein Blocking auf dem Main-Thread)
refreshPlayerWeatherCaches();
} }
private void spawnTemporarySnowInWorld(World world, JavaPlugin plugin) { /**
List<Block> snowBlocks = new ArrayList<>(); * Synchronisiert das Minecraft-Ingame-Wetter mit den echten API-Daten.
*
* Mapping der OpenWeatherMap-Wettertypen auf Minecraft:
*
* Thunderstorm → Gewitter (Sturm + Donner)
* Squall → Gewitter (böiger Wind/Sturm)
* Tornado → Gewitter (stärkste Stufe)
* Rain → Regen (Sturm, kein Donner)
* Drizzle → Nieselregen (Sturm, kein Donner)
* Snow → Schnee (Sturm; in kalten Biomen schneit es automatisch)
* Mist → Nebel/Dunst → klares Wetter (Minecraft hat keinen Nebel-Zustand)
* Smoke → Rauch → klares Wetter
* Haze → Dunst → klares Wetter
* Dust → Staub → klares Wetter
* Sand → Sandsturm → klares Wetter
* Ash → Asche → klares Wetter
* Fog → Nebel → klares Wetter
* Clouds → Bewölkt → klares Wetter (Minecraft hat keine reine Bewölkungsstufe)
* Clear → Sonnig → klares Wetter
*
* Wichtig: doWeatherCycle muss false sein (wird in setDoDaylightCycleForWorlds gesetzt),
* damit Minecraft das Wetter nicht nach wenigen Minuten eigenständig zurücksetzt.
*/
private void syncInGameWeather(World world, WeatherTimeData data) {
// Wetter-Duration etwas länger als das Update-Interval setzen,
// damit kein Flickern zwischen den API-Abfragen entsteht.
// Auch bei deaktiviertem doWeatherCycle empfohlen für maximale Stabilität.
int durationTicks = (updateInterval + 120) * 20;
for (org.bukkit.Chunk chunk : world.getLoadedChunks()) { switch (data.getWeatherMain().toLowerCase()) {
int chunkX = chunk.getX() << 4;
int chunkZ = chunk.getZ() << 4;
for (int x = 0; x < 16; x++) { // --- Gewitter-Gruppe ---
for (int z = 0; z < 16; z++) { case "thunderstorm":
if (Math.random() > 0.7) continue; // Klassisches Gewitter mit Blitz und Donner
int worldX = chunkX + x; world.setStorm(true);
int worldZ = chunkZ + z; world.setThundering(true);
int worldY = world.getHighestBlockYAt(worldX, worldZ); world.setWeatherDuration(durationTicks);
world.setThunderDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Gewitter (Thunderstorm)");
break;
Block ground = world.getBlockAt(worldX, worldY - 1, worldZ); case "squall":
Block above = world.getBlockAt(worldX, worldY, worldZ); // Bö / Sturmböe starker Wind mit Gewittercharakter
world.setStorm(true);
world.setThundering(true);
world.setWeatherDuration(durationTicks);
world.setThunderDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Sturmböe (Squall)");
break;
if (ground.getType().isSolid() && above.getType() == Material.AIR) { case "tornado":
above.setType(Material.SNOW); // Tornado stärkste Wetterstufe, Gewitter als Annäherung
snowBlocks.add(above); world.setStorm(true);
} world.setThundering(true);
} world.setWeatherDuration(durationTicks);
} world.setThunderDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Tornado");
break;
// --- Regen-Gruppe ---
case "rain":
// Normaler Regen
world.setStorm(true);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Regen (Rain)");
break;
case "drizzle":
// Nieselregen in Minecraft gleich wie Regen
world.setStorm(true);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Nieselregen (Drizzle)");
break;
// --- Schnee ---
case "snow":
// Schneefall Minecraft lässt es in kalten Biomen bei storm=true automatisch schneien.
// Kein Snowmen/Snow-Block-Hack nötig das Spiel kümmert sich selbst darum.
world.setStorm(true);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Schnee (Snow)");
break;
// --- Atmosphärische Zustände (Nebel, Dunst, Staub, etc.) ---
// Minecraft hat keinen echten Nebel-Wetterzustand.
// Klares Wetter ist die sinnvollste Annäherung.
case "mist":
case "smoke":
case "haze":
case "dust":
case "sand":
case "ash":
case "fog":
world.setStorm(false);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Atmosphärisch (" + data.getWeatherMain() + ") → klar");
break;
// --- Bewölkt & Klar ---
case "clouds":
// Minecraft unterscheidet nicht zwischen klar und bewölkt → kein Sturm
world.setStorm(false);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Bewölkt (Clouds) → klar");
break;
case "clear":
default:
// Klarer Himmel oder unbekannter Typ → sicherer Fallback auf klar
world.setStorm(false);
world.setThundering(false);
world.setWeatherDuration(durationTicks);
getLogger().fine("Wetter [" + world.getName() + "]: Klar (Clear / unbekannt: " + data.getWeatherMain() + ")");
break;
} }
new BukkitRunnable() {
@Override
public void run() {
for (Block b : snowBlocks) {
if (b.getType() == Material.SNOW) {
b.setType(Material.AIR);
}
}
}
}.runTaskLater(plugin, 180 * 20L);
} }
private void spawnTemporarySnowmen(World world, int count, JavaPlugin plugin) { /**
List<Snowman> snowmen = new ArrayList<>(); * Spieler mit eigener Location bekommen ihre Wetterdaten asynchron gecacht.
Random random = new Random(); * updateScoreboardForWorld() nutzt dann nur noch den Cache kein API-Call auf dem Main-Thread.
*/
private void refreshPlayerWeatherCaches() {
for (Map.Entry<UUID, String> entry : playerLocations.entrySet()) {
UUID playerId = entry.getKey();
String location = entry.getValue();
if (location == null || location.isEmpty()) continue;
List<org.bukkit.Chunk> loadedChunks = Arrays.asList(world.getLoadedChunks()); // Läuft bereits asynchron (aufgerufen aus weatherUpdateTask)
try {
for (int i = 0; i < count; i++) { WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, "metric", getLogger());
if (loadedChunks.isEmpty()) break; WeatherTimeData data = fetcher.fetchAsWeatherTimeData();
org.bukkit.Chunk chunk = loadedChunks.get(random.nextInt(loadedChunks.size())); playerWeatherCache.put(playerId, data);
} catch (Exception e) {
int chunkX = chunk.getX() << 4; getLogger().warning("Fehler beim Aktualisieren des Wetter-Caches für Spieler " + playerId + ": " + e.getMessage());
int chunkZ = chunk.getZ() << 4;
int x = chunkX + random.nextInt(16);
int z = chunkZ + random.nextInt(16);
int y = world.getHighestBlockYAt(x, z);
if (world.getBlockAt(x, y - 1, z).getType().isSolid()) {
Snowman snowman = (Snowman) world.spawnEntity(
new Location(world, x + 0.5, y, z + 0.5),
EntityType.SNOW_GOLEM
);
snowmen.add(snowman);
} }
} }
new BukkitRunnable() {
@Override
public void run() {
for (Snowman s : snowmen) {
if (!s.isDead()) s.remove();
}
}
}.runTaskLater(plugin, 180 * 20L);
} }
private void syncMinecraftTime(World world, ZonedDateTime dateTime) { private void syncMinecraftTime(World world, ZonedDateTime dateTime) {
@@ -416,6 +492,10 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
} }
} }
/**
* Erstellt ein neues Scoreboard für den Spieler und weist es zu.
* Wird nur aufgerufen wenn der Spieler das Scoreboard explizit aktiviert.
*/
private void createScoreboardForPlayer(Player player) { private void createScoreboardForPlayer(Player player) {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard(); Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
Objective objective = scoreboard.registerNewObjective("weather", "dummy", Objective objective = scoreboard.registerNewObjective("weather", "dummy",
@@ -426,22 +506,30 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
playerScoreboards.put(player.getUniqueId(), scoreboard); playerScoreboards.put(player.getUniqueId(), scoreboard);
} }
/**
* Entfernt das Scoreboard vom Spieler (setzt auf Main-Scoreboard zurück).
*/
private void removeScoreboardFromPlayer(Player player) {
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
playerScoreboards.remove(player.getUniqueId());
}
/**
* FIX: API-Calls nur noch aus dem Cache (playerWeatherCache) lesen.
* Kein blockierender fetchAsWeatherTimeData()-Aufruf auf dem Main-Thread mehr.
*/
private void updateScoreboardForWorld(World world, WeatherTimeData data) { private void updateScoreboardForWorld(World world, WeatherTimeData data) {
WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults")); WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults"));
if (!config.enabled || !config.displayActionbar) { if (!config.enabled) return;
return;
}
for (Player player : world.getPlayers()) { for (Player player : world.getPlayers()) {
Set<String> displayWorlds = playersWithDisplay.getOrDefault(player.getUniqueId(), new HashSet<>()); Set<String> displayWorlds = playersWithDisplay.getOrDefault(player.getUniqueId(), new HashSet<>());
if (!displayWorlds.contains(world.getName())) { if (!displayWorlds.contains(world.getName())) {
// Scoreboard entfernen wenn disabled // Scoreboard entfernen wenn für diese Welt deaktiviert
Scoreboard scoreboard = playerScoreboards.get(player.getUniqueId()); if (playerScoreboards.containsKey(player.getUniqueId())) {
if (scoreboard != null) { removeScoreboardFromPlayer(player);
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
playerScoreboards.remove(player.getUniqueId());
} }
continue; continue;
} }
@@ -456,19 +544,19 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
if (objective == null) continue; if (objective == null) continue;
// Alte Scores löschen // Alte Scores löschen
for (String entry : scoreboard.getEntries()) { for (String entry : new HashSet<>(scoreboard.getEntries())) {
scoreboard.resetScores(entry); scoreboard.resetScores(entry);
} }
// FIX: Wetterdaten aus Cache holen statt API-Call auf Main-Thread
WeatherTimeData playerData = data; WeatherTimeData playerData = data;
String location = playerLocations.getOrDefault(player.getUniqueId(), ""); String location = playerLocations.getOrDefault(player.getUniqueId(), "");
if (!location.isEmpty()) { if (!location.isEmpty()) {
try { WeatherTimeData cached = playerWeatherCache.get(player.getUniqueId());
WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, config.units, getLogger()); if (cached != null) {
playerData = fetcher.fetchAsWeatherTimeData(); playerData = cached;
} catch (Exception e) {
continue;
} }
// Kein else bei fehlendem Cache einfach Welt-Daten verwenden
} }
// Display Mode des Spielers // Display Mode des Spielers
@@ -518,7 +606,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
objective.getScore(tempColor + "🌡 " + String.format("%.1f%s", temp, tempUnit)).setScore(line--); objective.getScore(tempColor + "🌡 " + String.format("%.1f%s", temp, tempUnit)).setScore(line--);
objective.getScore(humidityColor + "💧 " + playerData.getHumidity() + "%").setScore(line--); objective.getScore(humidityColor + "💧 " + playerData.getHumidity() + "%").setScore(line--);
// Windrichtung mit Pfeil
String windDir = WindDirection.getDirection(playerData.getWindDeg()); String windDir = WindDirection.getDirection(playerData.getWindDeg());
String windArrow = WindDirection.getArrow(playerData.getWindDeg()); String windArrow = WindDirection.getArrow(playerData.getWindDeg());
objective.getScore(windColor + "🌬 " + String.format("%.1f", playerData.getWindSpeed()) + objective.getScore(windColor + "🌬 " + String.format("%.1f", playerData.getWindSpeed()) +
@@ -527,8 +614,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
objective.getScore(separator + "2").setScore(line--); objective.getScore(separator + "2").setScore(line--);
objective.getScore(ChatColor.GOLD + "🌅 " + playerData.getSunrise().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--); objective.getScore(ChatColor.GOLD + "🌅 " + playerData.getSunrise().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--);
objective.getScore(ChatColor.DARK_RED + "🌇 " + playerData.getSunset().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--); objective.getScore(ChatColor.DARK_RED + "🌇 " + playerData.getSunset().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--);
// Mondphase
objective.getScore(ChatColor.LIGHT_PURPLE + MoonPhase.getEmoji(world) + " " + MoonPhase.getName(world)).setScore(line--); objective.getScore(ChatColor.LIGHT_PURPLE + MoonPhase.getEmoji(world) + " " + MoonPhase.getName(world)).setScore(line--);
objective.getScore(separator + "3").setScore(line--); objective.getScore(separator + "3").setScore(line--);
@@ -544,30 +629,23 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
objective.getScore(weatherColor + weatherIcon + " " + ChatColor.GRAY + weatherMain).setScore(line--); objective.getScore(weatherColor + weatherIcon + " " + ChatColor.GRAY + weatherMain).setScore(line--);
objective.getScore(tempColor + "🌡 " + String.format("%.1f%s", temp, tempUnit)).setScore(line--); objective.getScore(tempColor + "🌡 " + String.format("%.1f%s", temp, tempUnit)).setScore(line--);
// Gefühlte Temperatur
double feelsLike = playerData.getFeelsLike(config.units.equalsIgnoreCase("metric") ? "C" : "F"); double feelsLike = playerData.getFeelsLike(config.units.equalsIgnoreCase("metric") ? "C" : "F");
ChatColor feelsLikeColor = WeatherColors.getTemperatureColor(playerData.getFeelsLike("C")); ChatColor feelsLikeColor = WeatherColors.getTemperatureColor(playerData.getFeelsLike("C"));
objective.getScore(feelsLikeColor + "🌡️ Gefühlt: " + String.format("%.1f%s", feelsLike, tempUnit)).setScore(line--); objective.getScore(feelsLikeColor + "🌡️ Gefühlt: " + String.format("%.1f%s", feelsLike, tempUnit)).setScore(line--);
objective.getScore(humidityColor + "💧 " + playerData.getHumidity() + "%").setScore(line--); objective.getScore(humidityColor + "💧 " + playerData.getHumidity() + "%").setScore(line--);
// Windrichtung mit Pfeil
String windDir = WindDirection.getDirection(playerData.getWindDeg()); String windDir = WindDirection.getDirection(playerData.getWindDeg());
String windArrow = WindDirection.getArrow(playerData.getWindDeg()); String windArrow = WindDirection.getArrow(playerData.getWindDeg());
objective.getScore(windColor + "🌬 " + String.format("%.1f", playerData.getWindSpeed()) + objective.getScore(windColor + "🌬 " + String.format("%.1f", playerData.getWindSpeed()) +
(config.units.equalsIgnoreCase("metric") ? " m/s " : " mph ") + windArrow + " " + windDir).setScore(line--); (config.units.equalsIgnoreCase("metric") ? " m/s " : " mph ") + windArrow + " " + windDir).setScore(line--);
// Luftdruck
objective.getScore(ChatColor.GRAY + "📊 " + playerData.getPressure() + " hPa").setScore(line--); objective.getScore(ChatColor.GRAY + "📊 " + playerData.getPressure() + " hPa").setScore(line--);
// Wolkendichte
objective.getScore(ChatColor.WHITE + "☁️ Wolken: " + playerData.getClouds() + "%").setScore(line--); objective.getScore(ChatColor.WHITE + "☁️ Wolken: " + playerData.getClouds() + "%").setScore(line--);
// Sichtweite
int visKm = playerData.getVisibility() / 1000; int visKm = playerData.getVisibility() / 1000;
objective.getScore(ChatColor.BLUE + "👁️ Sicht: " + visKm + " km").setScore(line--); objective.getScore(ChatColor.BLUE + "👁️ Sicht: " + visKm + " km").setScore(line--);
// UV-Index
int uvIndex = playerData.getUVIndex(); int uvIndex = playerData.getUVIndex();
ChatColor uvColor = WeatherColors.getUVIndexColor(uvIndex); ChatColor uvColor = WeatherColors.getUVIndexColor(uvIndex);
objective.getScore(uvColor + "☀️ UV: " + uvIndex).setScore(line--); objective.getScore(uvColor + "☀️ UV: " + uvIndex).setScore(line--);
@@ -575,8 +653,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
objective.getScore(separator + "2").setScore(line--); objective.getScore(separator + "2").setScore(line--);
objective.getScore(ChatColor.GOLD + "🌅 " + playerData.getSunrise().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--); objective.getScore(ChatColor.GOLD + "🌅 " + playerData.getSunrise().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--);
objective.getScore(ChatColor.DARK_RED + "🌇 " + playerData.getSunset().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--); objective.getScore(ChatColor.DARK_RED + "🌇 " + playerData.getSunset().format(DateTimeFormatter.ofPattern("HH:mm"))).setScore(line--);
// Mondphase
objective.getScore(ChatColor.LIGHT_PURPLE + MoonPhase.getPhase(world)).setScore(line--); objective.getScore(ChatColor.LIGHT_PURPLE + MoonPhase.getPhase(world)).setScore(line--);
objective.getScore(separator + "3").setScore(line--); objective.getScore(separator + "3").setScore(line--);
@@ -598,30 +674,81 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), countryCode); String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), countryCode);
String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F";
String city = config.location.split(",")[0]; 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); String message = String.format("%s: %s, %.1f%s", city, localizedWeather,
data.getTemp(config.units.equalsIgnoreCase("metric") ? "C" : "F"), tempUnit);
player.sendMessage(ChatColor.GREEN + message); player.sendMessage(ChatColor.GREEN + message);
// Scoreboard-Status prüfen: wenn in neuer Welt nicht aktiviert → entfernen
Set<String> displayWorlds = playersWithDisplay.getOrDefault(player.getUniqueId(), new HashSet<>());
if (!displayWorlds.contains(world.getName()) && playerScoreboards.containsKey(player.getUniqueId())) {
removeScoreboardFromPlayer(player);
}
} }
/**
* FIX: Beim Join wird das Scoreboard NICHT automatisch aktiviert.
* Spieler starten mit leerem Display-Set.
*/
@EventHandler @EventHandler
public void onPlayerJoinForDisplay(PlayerJoinEvent event) { public void onPlayerJoinForDisplay(PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
Set<String> worlds = new HashSet<>(); // Nur leeres Set initialisieren kein automatisches Scoreboard
for (World world : Bukkit.getWorlds()) { playersWithDisplay.put(player.getUniqueId(), new HashSet<>());
WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults"));
if (config.enabled && config.displayActionbar) { // Gespeicherte Location laden (falls vorhanden)
worlds.add(world.getName()); PlayerConfig playerConfig = new PlayerConfig(player.getUniqueId());
} String savedLocation = playerConfig.getLocation();
if (savedLocation != null && !savedLocation.isEmpty()) {
playerLocations.put(player.getUniqueId(), savedLocation);
} }
playersWithDisplay.put(player.getUniqueId(), worlds);
createScoreboardForPlayer(player);
} }
/**
* Verhindert dass Schnee ingame liegenbleibt wenn das Plugin Schneewetter setzt.
*
* Minecraft akkumuliert Schneelagen (SNOW) und Eis (ICE / FROSTED_ICE) über Zeit
* in kalten Biomen sobald storm=true gesetzt ist. Da wir das Wetter dauerhaft
* steuern, würde Schnee sonst endlos wachsen.
*
* Lösung: BlockFormEvent abfangen und canceln wenn:
* - Die Welt vom Plugin verwaltet wird (enabled + syncInGameWeather)
* - Das aktuelle API-Wetter "Snow" ist
* - Der entstehende Block eine Schneelage oder Eis ist
*/
@EventHandler(ignoreCancelled = true)
public void onBlockForm(BlockFormEvent event) {
World world = event.getBlock().getWorld();
WorldConfig config = worldConfigs.getOrDefault(world.getName(), worldConfigs.get("defaults"));
// Nur in vom Plugin verwalteten Welten eingreifen
if (!config.enabled || !config.syncInGameWeather) return;
WeatherTimeData data = worldWeatherData.get(world.getName());
if (data == null) return;
// Nur bei Schnee-Wetter aktiv
if (!data.getWeatherMain().equalsIgnoreCase("snow")) return;
Material formed = event.getNewState().getType();
if (formed == Material.SNOW || formed == Material.ICE || formed == Material.FROSTED_ICE) {
event.setCancelled(true);
}
}
/**
* FIX: instanceof-Check vor Cast auf Player hinzugefügt.
* FIX: GUI-Toggle korrekt mit createScoreboardForPlayer verknüpft.
*/
@EventHandler @EventHandler
public void onInventoryClick(InventoryClickEvent event) { public void onInventoryClick(InventoryClickEvent event) {
// FIX: Sicherheitscheck nur Player können die GUI bedienen
if (!(event.getWhoClicked() instanceof Player)) return;
Player player = (Player) event.getWhoClicked(); Player player = (Player) event.getWhoClicked();
Inventory expectedGUI = playerGUIs.get(player.getUniqueId()); Inventory expectedGUI = playerGUIs.get(player.getUniqueId());
if (expectedGUI == null || event.getInventory() != expectedGUI) return; if (expectedGUI == null || event.getInventory() != expectedGUI) return;
event.setCancelled(true); event.setCancelled(true);
ItemStack clickedItem = event.getCurrentItem(); ItemStack clickedItem = event.getCurrentItem();
if (clickedItem == null || clickedItem.getType() == Material.AIR) return; if (clickedItem == null || clickedItem.getType() == Material.AIR) return;
@@ -632,24 +759,35 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
player.closeInventory(); player.closeInventory();
audiences.player(player).sendMessage(Component.text( audiences.player(player).sendMessage(Component.text(
getLocalizedMessage("gui_setlocation_prompt", countryCode), NamedTextColor.YELLOW)); getLocalizedMessage("gui_setlocation_prompt", countryCode), NamedTextColor.YELLOW));
} else if (clickedItem.getType() == Material.REDSTONE_TORCH) { } else if (clickedItem.getType() == Material.REDSTONE_TORCH) {
// FIX: Scoreboard korrekt aktivieren/deaktivieren
String worldName = player.getWorld().getName(); String worldName = player.getWorld().getName();
Set<String> worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); Set<String> worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>());
if (worlds.contains(worldName)) { if (worlds.contains(worldName)) {
worlds.remove(worldName); worlds.remove(worldName);
removeScoreboardFromPlayer(player);
audiences.player(player).sendMessage(Component.text( audiences.player(player).sendMessage(Component.text(
getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); ChatColor.RED + "⛅ Scoreboard-Wetteranzeige deaktiviert.", NamedTextColor.RED));
} else { } else {
worlds.add(worldName); worlds.add(worldName);
createScoreboardForPlayer(player);
audiences.player(player).sendMessage(Component.text( audiences.player(player).sendMessage(Component.text(
getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); ChatColor.GREEN + "⛅ Scoreboard-Wetteranzeige aktiviert!", NamedTextColor.GREEN));
} }
// GUI offen lassen damit Spieler Status sehen kann
player.closeInventory();
player.openInventory(createWeatherGUI(player));
} else if (clickedItem.getType() == Material.PAPER) { } else if (clickedItem.getType() == Material.PAPER) {
player.closeInventory(); player.closeInventory();
new WeatherForecastCommand().onCommand(player, null, "weatherforecast", new String[]{}); new WeatherForecastCommand().onCommand(player, null, "weatherforecast", new String[]{});
} else if (clickedItem.getType() == Material.OAK_SIGN) { } else if (clickedItem.getType() == Material.OAK_SIGN) {
player.closeInventory(); player.closeInventory();
String infoMessage = "RealTimeWeather Plugin\nVersion: 1.3\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.11"; String infoMessage = getDescription().getName() + " Plugin"
+ "\nVersion: " + getDescription().getVersion()
+ "\nAutor: " + String.join(", ", getDescription().getAuthors());
audiences.player(player).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); audiences.player(player).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA));
} }
} }
@@ -699,36 +837,55 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
return countryCode; return countryCode;
} }
/**
* FIX: GUI-Toggle-Button zeigt korrekt "Scoreboard" an (statt "Bossbar").
* Toggle-Button aktualisiert seinen Status live (✅ aktiv / ❌ inaktiv).
*/
private Inventory createWeatherGUI(Player player) { private Inventory createWeatherGUI(Player player) {
String countryCode = getCountryCode(worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")).location); String countryCode = getCountryCode(worldConfigs.getOrDefault(player.getWorld().getName(), worldConfigs.get("defaults")).location);
Inventory inv = Bukkit.createInventory(null, 9, getLocalizedMessage("gui_title", countryCode)); 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);
// --- Standort setzen ---
ItemStack setLocation = new ItemStack(Material.BOOK);
ItemMeta setLocationMeta = setLocation.getItemMeta(); ItemMeta setLocationMeta = setLocation.getItemMeta();
setLocationMeta.setDisplayName(getLocalizedMessage("gui_setlocation", countryCode)); setLocationMeta.setDisplayName(ChatColor.YELLOW + "📍 Standort setzen");
setLocationMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_setlocation_lore", countryCode))); setLocationMeta.setLore(Collections.singletonList(ChatColor.GRAY + "Klicke um deinen Standort zu ändern"));
setLocation.setItemMeta(setLocationMeta); setLocation.setItemMeta(setLocationMeta);
ItemMeta toggleWeatherMeta = toggleWeather.getItemMeta(); // --- Scoreboard umschalten ---
toggleWeatherMeta.setDisplayName(getLocalizedMessage("gui_toggleweather", countryCode)); // FIX: Korrekte Beschriftung als "Scoreboard" (nicht mehr "Bossbar")
toggleWeatherMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_toggleweather_lore", countryCode))); boolean scoreboardActive = playersWithDisplay
toggleWeather.setItemMeta(toggleWeatherMeta); .getOrDefault(player.getUniqueId(), new HashSet<>())
.contains(player.getWorld().getName());
ItemStack toggleScoreboard = new ItemStack(Material.REDSTONE_TORCH);
ItemMeta toggleMeta = toggleScoreboard.getItemMeta();
toggleMeta.setDisplayName(ChatColor.GOLD + "⛅ Scoreboard-Wetteranzeige");
List<String> toggleLore = new ArrayList<>();
toggleLore.add(ChatColor.GRAY + "Zeigt Wetterdaten im Scoreboard (rechts)");
toggleLore.add(scoreboardActive
? ChatColor.GREEN + "✅ Aktuell: Aktiv"
: ChatColor.RED + "❌ Aktuell: Inaktiv");
toggleLore.add(ChatColor.GRAY + "Klicke zum Umschalten");
toggleMeta.setLore(toggleLore);
toggleScoreboard.setItemMeta(toggleMeta);
// --- Vorhersage ---
ItemStack forecast = new ItemStack(Material.PAPER);
ItemMeta forecastMeta = forecast.getItemMeta(); ItemMeta forecastMeta = forecast.getItemMeta();
forecastMeta.setDisplayName(getLocalizedMessage("gui_forecast", countryCode)); forecastMeta.setDisplayName(ChatColor.AQUA + "📋 Wettervorhersage");
forecastMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_forecast_lore", countryCode))); forecastMeta.setLore(Collections.singletonList(ChatColor.GRAY + "5-Tage-Vorhersage anzeigen"));
forecast.setItemMeta(forecastMeta); forecast.setItemMeta(forecastMeta);
// --- Info ---
ItemStack info = new ItemStack(Material.OAK_SIGN);
ItemMeta infoMeta = info.getItemMeta(); ItemMeta infoMeta = info.getItemMeta();
infoMeta.setDisplayName(getLocalizedMessage("gui_info", countryCode)); infoMeta.setDisplayName(ChatColor.WHITE + " Plugin-Info");
infoMeta.setLore(Collections.singletonList(getLocalizedMessage("gui_info_lore", countryCode))); infoMeta.setLore(Collections.singletonList(ChatColor.GRAY + "Plugin-Informationen anzeigen"));
info.setItemMeta(infoMeta); info.setItemMeta(infoMeta);
inv.setItem(2, setLocation); inv.setItem(2, setLocation);
inv.setItem(3, toggleWeather); inv.setItem(3, toggleScoreboard);
inv.setItem(4, forecast); inv.setItem(4, forecast);
inv.setItem(5, info); inv.setItem(5, info);
@@ -739,15 +896,18 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
private class PlayerConfig { private class PlayerConfig {
private final File configFile; private final File configFile;
private final FileConfiguration config; private final FileConfiguration config;
public PlayerConfig(UUID playerId) { public PlayerConfig(UUID playerId) {
File playerDir = new File(getDataFolder(), "players"); File playerDir = new File(getDataFolder(), "players");
playerDir.mkdirs(); playerDir.mkdirs();
configFile = new File(playerDir, playerId.toString() + ".yml"); configFile = new File(playerDir, playerId.toString() + ".yml");
config = YamlConfiguration.loadConfiguration(configFile); config = YamlConfiguration.loadConfiguration(configFile);
} }
public String getLocation() { public String getLocation() {
return config.getString("location", ""); return config.getString("location", "");
} }
public void setLocation(String location) { public void setLocation(String location) {
config.set("location", location); config.set("location", location);
try { try {
@@ -815,10 +975,8 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
processedLocations.clear(); processedLocations.clear();
initializePlayerDisplays(); initializePlayerDisplays();
setDoDaylightCycleForWorlds(); setDoDaylightCycleForWorlds();
startWeatherUpdateTask(); startWeatherUpdateTask(); // startet async und ruft updateWeatherDataForAllWorlds() intern auf
startSyncTask(); startSyncTask();
processedLocations.clear();
updateWeatherDataForAllWorlds();
audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("reload_success", countryCode), NamedTextColor.GREEN)); audiences.sender(sender).sendMessage(Component.text(getLocalizedMessage("reload_success", countryCode), NamedTextColor.GREEN));
return true; return true;
} }
@@ -838,10 +996,12 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
public void run() { public void run() {
try { try {
WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, "metric", getLogger()); WeatherFetcher fetcher = new WeatherFetcher(apiKey, location, "metric", getLogger());
fetcher.fetchAsWeatherTimeData(); WeatherTimeData newData = fetcher.fetchAsWeatherTimeData();
PlayerConfig playerConfig = new PlayerConfig(player.getUniqueId()); PlayerConfig playerConfig = new PlayerConfig(player.getUniqueId());
playerConfig.setLocation(location); playerConfig.setLocation(location);
playerLocations.put(player.getUniqueId(), location); playerLocations.put(player.getUniqueId(), location);
// FIX: Sofort in Cache speichern
playerWeatherCache.put(player.getUniqueId(), newData);
String cc = getCountryCode(location); String cc = getCountryCode(location);
audiences.player(player).sendMessage(Component.text(getLocalizedMessage("location_set", cc, "location", location), NamedTextColor.GREEN)); audiences.player(player).sendMessage(Component.text(getLocalizedMessage("location_set", cc, "location", location), NamedTextColor.GREEN));
} catch (Exception e) { } catch (Exception e) {
@@ -873,7 +1033,8 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), cc); String localizedWeather = getLocalizedWeatherMain(data.getWeatherMain(), cc);
String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F"; String tempUnit = config.units.equalsIgnoreCase("metric") ? "°C" : "°F";
String city = queryLocation.split(",")[0]; String city = queryLocation.split(",")[0];
String message = String.format("%s: %s, %.1f%s", city, localizedWeather, data.getTemp(config.units.equalsIgnoreCase("metric") ? "C" : "F"), tempUnit); 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)); audiences.player(queryPlayer).sendMessage(Component.text(message, NamedTextColor.GREEN));
} catch (Exception e) { } catch (Exception e) {
audiences.player(queryPlayer).sendMessage(Component.text(getLocalizedMessage("forecast_error", countryCode, "location", queryLocation, "error", e.getMessage()), NamedTextColor.RED)); audiences.player(queryPlayer).sendMessage(Component.text(getLocalizedMessage("forecast_error", countryCode, "location", queryLocation, "error", e.getMessage()), NamedTextColor.RED));
@@ -883,7 +1044,11 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
return true; return true;
} }
case "info": { case "info": {
String infoMessage = "RealTimeWeather Plugin\nVersion: 1.3\nAutor: M_Viper\nGetestete Minecraft-Version: 1.21.1 - 1.21.11\n\nDie Wetteranzeige erscheint als SCOREBOARD rechts am Bildschirm."; String infoMessage = getDescription().getName() + " Plugin"
+ "\nVersion: " + getDescription().getVersion()
+ "\nAutor: " + String.join(", ", getDescription().getAuthors())
+ "\n\nDas Scoreboard ist standardmäßig ausgeblendet."
+ "\nAktiviere es per /wetter gui oder /toggleweather.";
audiences.sender(sender).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA)); audiences.sender(sender).sendMessage(Component.text(infoMessage, NamedTextColor.AQUA));
return true; return true;
} }
@@ -924,7 +1089,6 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
modePlayer.sendMessage(ChatColor.GREEN + "Anzeigemodus geändert zu: " + ChatColor.GOLD + newMode.getDisplayName()); modePlayer.sendMessage(ChatColor.GREEN + "Anzeigemodus geändert zu: " + ChatColor.GOLD + newMode.getDisplayName());
modePlayer.sendMessage(ChatColor.GRAY + "Das Scoreboard wird beim nächsten Update aktualisiert."); modePlayer.sendMessage(ChatColor.GRAY + "Das Scoreboard wird beim nächsten Update aktualisiert.");
return true; return true;
} }
default: default:
@@ -971,13 +1135,13 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
String weatherMain = forecast.getJSONArray("weather").getJSONObject(0).getString("main"); String weatherMain = forecast.getJSONArray("weather").getJSONObject(0).getString("main");
double temp = forecast.getJSONObject("main").getDouble("temp"); double temp = forecast.getJSONObject("main").getDouble("temp");
ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC")); ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC"));
String localizedWeatherMain = getLocalizedWeatherMain(weatherMain, countryCode); String localizedWeatherMain = getLocalizedWeatherMain(weatherMain, countryCode);
message = message.append(Component.newline()) message = message.append(Component.newline())
.append(Component.text( .append(Component.text(
dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) + ": " + dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) + ": " +
localizedWeatherMain + ", " + String.format("%.1f%s", temp, config.units.equalsIgnoreCase("metric") ? "°C" : "°F"), localizedWeatherMain + ", " + String.format("%.1f%s", temp,
config.units.equalsIgnoreCase("metric") ? "°C" : "°F"),
NamedTextColor.GREEN)); NamedTextColor.GREEN));
} }
audiences.player(player).sendMessage(message); audiences.player(player).sendMessage(message);
@@ -1013,14 +1177,11 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
Set<String> worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>()); Set<String> worlds = playersWithDisplay.computeIfAbsent(player.getUniqueId(), k -> new HashSet<>());
if (worlds.contains(worldName)) { if (worlds.contains(worldName)) {
worlds.remove(worldName); worlds.remove(worldName);
// Scoreboard entfernen removeScoreboardFromPlayer(player);
player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
playerScoreboards.remove(player.getUniqueId());
audiences.player(player).sendMessage(Component.text( audiences.player(player).sendMessage(Component.text(
getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED)); getLocalizedMessage("toggle_disabled", countryCode, "world", worldName), NamedTextColor.RED));
} else { } else {
worlds.add(worldName); worlds.add(worldName);
// Scoreboard erstellen
createScoreboardForPlayer(player); createScoreboardForPlayer(player);
audiences.player(player).sendMessage(Component.text( audiences.player(player).sendMessage(Component.text(
getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN)); getLocalizedMessage("toggle_enabled", countryCode, "world", worldName), NamedTextColor.GREEN));
@@ -1034,23 +1195,14 @@ public class WeatherTimeSyncPlugin extends JavaPlugin implements Listener {
String location; String location;
String units; String units;
String timeFormat; String timeFormat;
boolean displayActionbar;
boolean displayWeatherIcon;
String displayPosition;
int paddingRight;
boolean syncInGameWeather; boolean syncInGameWeather;
public WorldConfig(boolean enabled, String location, String units, String timeFormat, public WorldConfig(boolean enabled, String location, String units, String timeFormat,
boolean displayActionbar, boolean displayWeatherIcon, boolean syncInGameWeather) {
String displayPosition, int paddingRight, boolean syncInGameWeather) {
this.enabled = enabled; this.enabled = enabled;
this.location = location; this.location = location;
this.units = units; this.units = units;
this.timeFormat = timeFormat; this.timeFormat = timeFormat;
this.displayActionbar = displayActionbar;
this.displayWeatherIcon = displayWeatherIcon;
this.displayPosition = displayPosition;
this.paddingRight = paddingRight;
this.syncInGameWeather = syncInGameWeather; this.syncInGameWeather = syncInGameWeather;
} }
} }

View File

@@ -1,39 +1,61 @@
# OpenWeatherMap API-Key hier eintragen # ============================================================
# RealTimeWeather config.yml
# ============================================================
# OpenWeatherMap API-Key
# Kostenlos erhältlich unter: https://openweathermap.org/api
api-key: "DEIN_API_KEY_HIER" api-key: "DEIN_API_KEY_HIER"
# Update-Intervall in Sekunden (Standard: 60) # Wie oft Wetter & Zeit von der API abgerufen werden (in Sekunden)
# Empfohlen: 60300 (zu niedrige Werte können das API-Limit überschreiten)
update-interval: 60 update-interval: 60
# ------------------------------------------------------------
# Standard-Einstellungen (gelten wenn keine Welt-spezifischen
# Einstellungen definiert sind)
# ------------------------------------------------------------
defaults: defaults:
# Plugin für diese Welt aktiv?
enabled: true enabled: true
location: "Berlin,de" # Stadt,Land
units: "metric" # metric = °C, imperial = °F # Standort für Wetter & Zeit Format: "Stadt,Länderkürzel"
time-format: "24h" # oder "12h" # Beispiele: "Berlin,de" | "London,gb" | "New York,us"
display-actionbar: true location: "Berlin,de"
display-weather-icon: true
display-position: "top-right" # Temperatureinheit: "metric" (°C) oder "imperial" (°F)
padding-right: 100 units: "metric"
# Uhrzeitformat: "24h" oder "12h"
time-format: "24h"
# Soll das Ingame-Wetter mit der echten API synchronisiert werden?
# (Regen, Gewitter, Schnee, Klar etc.)
sync-in-game-weather: true sync-in-game-weather: true
# ------------------------------------------------------------
# Welt-spezifische Einstellungen
# Nicht definierte Felder erben vom "defaults"-Block oben.
# ------------------------------------------------------------
worlds: worlds:
world: world:
enabled: true enabled: true
location: "Berlin,de" location: "Berlin,de"
units: "metric" units: "metric"
time-format: "24h" time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position: "top-left"
padding-right: 50
sync-in-game-weather: true sync-in-game-weather: true
world_nether: world_nether:
# Nether hat kein Wetter/Tageslicht Plugin hier deaktivieren
enabled: false
location: "Berlin,de"
units: "metric"
time-format: "24h"
sync-in-game-weather: false
world_the_end:
# End hat kein Wetter/Tageslicht Plugin hier deaktivieren
enabled: false enabled: false
location: "Berlin,de" location: "Berlin,de"
units: "metric" units: "metric"
time-format: "24h" time-format: "24h"
display-actionbar: true
display-weather-icon: false
display-position: "top-right"
padding-right: 150
sync-in-game-weather: false sync-in-game-weather: false

View File

@@ -1,32 +1,43 @@
name: RealTimeWeather name: RealTimeWeather
version: 1.4 version: 1.5
main: dev.viper.weathertime.WeatherTimeSyncPlugin main: dev.viper.weathertime.WeatherTimeSyncPlugin
api-version: 1.21 api-version: 1.21
authors: [M_Viper]
description: Synchronizes real-time weather and time from OpenWeatherMap with your Minecraft world.
commands: commands:
wetter: wetter:
description: Manages real-time weather and time synchronization description: Manages real-time weather and time synchronization
usage: /<command> reload | setlocation <city,country> | query | info | gui | help usage: /<command> <reload|setlocation|query|mode|info|gui|help>
permission: realtimeweather.use permission: realtimeweather.use
weatherforecast: weatherforecast:
description: Shows the weather forecast for the player's location description: Shows the 5-day weather forecast for the player's location
usage: /<command> usage: /<command>
permission: realtimeweather.forecast permission: realtimeweather.forecast
toggleweather: toggleweather:
description: Toggles the weather display for the current world description: Toggles the scoreboard weather display for the current world
usage: /<command> usage: /<command>
permission: realtimeweather.toggle permission: realtimeweather.toggle
permissions: permissions:
realtimeweather.use: realtimeweather.use:
description: Allows usage of the /wetter command description: Allows usage of the /wetter command (query, info, gui, mode, help)
default: true default: true
children:
realtimeweather.setlocation: true
realtimeweather.setlocation:
description: Allows setting a custom weather location via /wetter setlocation
default: true
realtimeweather.forecast: realtimeweather.forecast:
description: Allows usage of the /weatherforecast command description: Allows usage of the /weatherforecast command
default: true default: true
realtimeweather.toggle: realtimeweather.toggle:
description: Allows usage of the /toggleweather command description: Allows toggling the scoreboard weather display via /toggleweather
default: true default: true
realtimeweather.reload: realtimeweather.reload:
description: Allows reloading the plugin configuration description: Allows reloading the plugin configuration via /wetter reload
default: op default: op