16 Commits
1.0 ... 1.4

Author SHA1 Message Date
f72903c2a6 Update from Git Manager GUI 2026-01-28 17:34:09 +01:00
e2b3abb65d Upload pom.xml via GUI 2026-01-28 16:34:08 +00:00
26d1f27f79 Dateien nach "src/main/java/dev/viper/weathertime" hochladen 2025-08-11 15:34:44 +00:00
4e0195f7ab src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java aktualisiert 2025-08-11 15:34:20 +00:00
9856ad0e54 src/main/resources/plugin.yml aktualisiert 2025-08-11 15:33:31 +00:00
de4f24c368 pom.xml aktualisiert 2025-08-11 15:32:53 +00:00
7ae5eea24a src/main/java/dev/viper/weathertime/WeatherFetcher.java aktualisiert 2025-08-09 20:58:43 +00:00
ac318f4f78 src/main/java/dev/viper/weathertime/WeatherTimeData.java aktualisiert 2025-08-09 20:58:30 +00:00
fe3e1c1534 src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java aktualisiert 2025-08-09 20:58:15 +00:00
c7416baed5 src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java aktualisiert 2025-08-09 20:57:59 +00:00
98cad69efc src/main/java/dev/viper/weathertime/WeatherTimeSyncPlugin.java aktualisiert 2025-08-09 20:57:46 +00:00
64c994bc8b src/main/resources/config.yml aktualisiert 2025-08-09 20:57:10 +00:00
a1e29bc89e src/main/resources/plugin.yml aktualisiert 2025-08-09 20:56:48 +00:00
ede9d372f0 README.md aktualisiert 2025-08-09 20:53:45 +00:00
7761b5a1be README.md aktualisiert 2025-08-09 20:53:01 +00:00
032ada193a README.md aktualisiert 2025-08-09 20:51:42 +00:00
13 changed files with 1972 additions and 1238 deletions

205
README.md
View File

@@ -1,141 +1,108 @@
# RealTimeWeather
Ein Minecraft-Plugin, das Echtzeit-Wetter und -Zeit in deine Welten bringt! **RealTimeWeather** synchronisiert das In-Game-Wetter und die Tageszeit mit Daten von OpenWeatherMap und bietet eine benutzerfreundliche GUI, Wettervorhersagen und anpassbare Anzeigeoptionen. Entwickelt für Minecraft 1.21.1.
**RealTimeWeather** ist ein Bukkit/Spigot/Paper-Plugin, das echte Wetter- und Zeitdaten aus der realen Welt in deine Minecraft-Welt synchronisiert und live auf der Actionbar anzeigt inklusive Temperatur, Wettericon, Luftfeuchtigkeit, Windstärke und Sonnenauf-/untergang.
## Funktionen
## ⭐ Features
- **Echtzeit-Wetter**: Synchronisiert Regen, Gewitter oder klares Wetter mit realen Wetterdaten eines konfigurierbaren Orts.
- **Echtzeit-Zeit**: Passt die Minecraft-Tageszeit an die aktuelle Uhrzeit an (deaktiviert den Tageszyklus).
- **Actionbar-Anzeige**: Zeigt Datum, Uhrzeit, Wetter-Symbol (z. B. 🌧️) und Temperatur in der Actionbar an, mit Optionen für Position (oben links/rechts) und Symbolanzeige.
- **Wettervorhersage**: 5-Tage-Wettervorhersage über den Befehl `/weatherforecast`.
- **GUI**: Intuitive Benutzeroberfläche zum Festlegen von Orten, Umschalten der Anzeige und Abrufen von Informationen.
- **Mehrsprachenunterstützung**: Unterstützt Deutsch (`de`) und Englisch (`en`) über `lang.yml`, mit einfacher Erweiterung für weitere Sprachen.
- **Weltspezifische Konfiguration**: Unterschiedliche Einstellungen für jede Welt in `config.yml`.
- **Spielerindividuelle Orte**: Spieler können ihren eigenen Ort mit `/wetter setlocation` festlegen.
- **Berechtigungen**: Admin-Befehle sind durch Berechtigungen geschützt.
- Echte Wetter- und Uhrzeit-Synchronisation pro Welt
- Anzeige auf der Actionbar:
Datum, Uhrzeit, Wetter-Icon, Temperatur, Luftfeuchtigkeit 💧, Windgeschwindigkeit 🌬️, Sonnenaufgang 🌅 & Sonnenuntergang 🌇
- Eigene Location pro Spieler möglich (GUI & Befehl)
- Ingame-Vorhersage (5 Tage) als Command
- Wetterumschaltung auf Server synchron mit echten Wetterdaten (optional)
- Volle Mehrsprachigkeit über lang.yml
- Kompatibel mit Minecraft 1.19.x 1.21.x
## Installation
## 🔧 Installation
1. **Download**: Lade die `RealTimeWeather.jar` aus den [Releases](https://github.com/M_Viper/RealTimeWeather/releases) herunter.
2. **Plugin-Ordner**: Kopiere die JAR-Datei in den `plugins`-Ordner deines Minecraft-Servers.
3. **API-Schlüssel**: Registriere dich bei [OpenWeatherMap](https://openweathermap.org/) für einen kostenlosen API-Schlüssel.
4. **Konfiguration**: Bearbeite die `config.yml` im Ordner `plugins/RealTimeWeather/` und füge deinen API-Schlüssel ein.
5. **Server starten**: Starte deinen Server, um das Plugin zu laden. Die `lang.yml` wird automatisch generiert.
1. **Kopiere die Dateien:**<br>
Die JAR nach `/plugins/` kopieren, Server neu starten.
2. **OpenWeatherMap API-Key** im `/plugins/RealTimeWeather/config.yml` eintragen:
## Konfiguration
api-key: "DEIN_OPENWEATHERMAP_API_KEY"
Die Konfigurationsdateien befinden sich im Ordner `plugins/RealTimeWeather/`.
Registriere gratis unter https://openweathermap.org.
### config.yml
3. **Plugin starten/neu laden:**<br>
`reload` Befehl (`/wetter reload`) oder Server-Neustart.
## ⚙️ Kurzes Setup
- Die wichtigsten Einstellungen sind in der `config.yml` dokumentiert (siehe unten).
- Du kannst für jede Welt eigene Locations, Anzeige-Modi oder Wettertypen setzen.
## 🕹️ Kommandos
| Befehl | Beschreibung |
|-----------------------|-----------------------------------------------|
| `/wetter help` | Alle Befehle und Kurzinfos anzeigen |
| `/wetter reload` | Konfiguration und Sprache neu laden |
| `/wetter setlocation <city,country>` | Eigene Stadt für Anzeige wählen |
| `/wetter query` | Zeigt das aktuelle Wetter für dich an |
| `/wetter info` | Infos zum Plugin |
| `/wetter gui` | Öffnet das GUI mit Schnell-Optionen |
| `/weatherforecast` | 5-Tage-Wetterprognose deiner Location |
| `/toggleweather` | Actionbar-Wetteranzeige für Welt (de)aktivieren |
## 📊 Live-Anzeige (Actionbar)
Beispiel:
08.08.2025 18:31 ☁️ 21.7°C | 💧 68% | 🌬️ 2.5 m/s | 🌅 05:48 | 🌇 21:34
```yml
## 📝 Beispiel-config.yml
```yaml
api-key: "DEIN_API_KEY_HIER"
update-interval: 60 # Sekunden
update-interval: 60
defaults:
enabled: true # Plugin für Welten ohne spezifische Konfiguration aktivieren
location: "Berlin,de" # Stadt,Ländercode
units: "metric" # metric = Celsius, imperial = Fahrenheit
time-format: "24h" # "24h" oder "12h"
display-actionbar: true
display-weather-icon: true
display-position: "top-right" # top-left oder top-right
padding-right: 100 # Anzahl der Leerzeichen für top-right
enabled: true
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position / padding-right werden zentriert ignoriert
display-position: "top-right"
padding-right: 100
sync-in-game-weather: true
worlds:
world:
enabled: true
location: "London,uk"
units: "imperial"
time-format: "12h"
display-actionbar: true
display-weather-icon: true
display-position: "top-left"
padding-right: 50
world_nether:
enabled: false
location: "Tokyo,jp"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: false
display-position: "top-right"
padding-right: 150
world:
enabled: true
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position: "top-left"
padding-right: 50
sync-in-game-weather: true
world_nether:
enabled: false
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: false
display-position: "top-right"
padding-right: 150
sync-in-game-weather: false
```
# RealTimeWeather
Ein Minecraft-Plugin, das Echtzeit-Wetter und -Zeit in deine Welten bringt. Synchronisiert Wetter und Tageszeit mit OpenWeatherMap-Daten. Entwickelt für Minecraft 1.21.1 - 1.21.8.
## 🧑‍💻 Erweiterungen
## Konfiguration
- Anzeige von Feuchtigkeit, Wind & Sonnenzeiten kann leicht deaktiviert/angepasst werden (siehe Actionbar-Code).
- Ländercodes und Städte werden in OpenWeatherMap-Syntax verwendet, z.B. `London,gb`, `Frankfurt,de`.
## ❤️ Lizenz
### lang.yml
[MIT License](LICENSE)
Enthält Übersetzungen für Deutsch (`de`) und Englisch (`en`). Beispiel für Deutsch:
---
```yaml
languages:
de:
help_header: "RealTimeWeather-Befehle"
help_help: "Diese Hilfenachricht anzeigen"
help_reload: "Die Plugin-Konfiguration neu laden (Berechtigung: realtimeweather.reload)"
help_setlocation: "Eigenen Wetterort für den Spieler festlegen (z.B. Berlin,de)"
help_query: "Aktuelles Wetter für den Spielerort oder die Welt anzeigen"
help_info: "Plugin-Informationen anzeigen"
help_gui: "GUI für Wettersteuerung öffnen"
help_weatherforecast: "Wettervorhersage für 5 Tage anzeigen"
help_toggleweather: "Wetteranzeige in der Actionbar ein-/ausschalten"
usage: "Verwendung: /wetter reload | setlocation <Ort> | query | info | gui | help"
usage_setlocation: "Verwendung: /wetter setlocation <Stadt,Land>"
no_permission: "Du hast keine Berechtigung für diesen Befehl!"
reload_success: "Konfiguration erfolgreich neu geladen!"
only_players: "Dieser Befehl ist nur für Spieler!"
plugin_disabled: "Plugin ist für die Welt {world} deaktiviert!"
location_set: "Ort auf {location} gesetzt!"
invalid_location: "Ungültiger Ort: {location}"
forecast_error: "Fehler beim Abrufen der Wetterdaten für {location}: {error}"
gui_title: "Wettersteuerung"
gui_setlocation: "Ort festlegen"
gui_setlocation_lore: "Eigenen Wetterort festlegen"
gui_toggleweather: "Wetteranzeige umschalten"
gui_toggleweather_lore: "Wetter-Actionbar ein-/ausschalten"
gui_forecast: "Wettervorhersage"
gui_forecast_lore: "5-Tage-Wettervorhersage anzeigen"
gui_info: "Info"
gui_info_lore: "Plugin-Informationen anzeigen"
gui_setlocation_prompt: "Gib den Ort ein (z.B. Berlin,de)"
toggle_enabled: "Wetteranzeige für {world} aktiviert!"
toggle_disabled: "Wetteranzeige für {world} deaktiviert!"
forecast_header: "Wettervorhersage für {location}"
weather:
clear: "Klar"
clouds: "Bewölkt"
rain: "Regen"
thunderstorm: "Gewitter"
snow: "Schnee"
mist: "Nebel"
fog: "Nebel"
haze: "Dunst"
en:
# Ähnliche Schlüssel wie oben, auf Englisch
```
# RealTimeWeather
Ein Minecraft-Plugin, das Echtzeit-Wetter und -Zeit in deine Welten bringt. Entwickelt für Minecraft 1.21.1.
## Befehle
- `/wetter help`: Zeigt die Hilfeübersicht an.
- `/wetter reload`: Lädt die Konfiguration neu (Berechtigung: `realtimeweather.reload`).
- `/wetter setlocation <city,country>`: Legt einen individuellen Ort für den Spieler fest.
- `/wetter query`: Zeigt aktuelles Wetter für den Spielerort oder die Welt.
- `/wetter info`: Zeigt Plugin-Informationen an.
- `/wetter gui`: Öffnet die Wetter-GUI.
- `/weatherforecast`: Zeigt die 5-Tage-Wettervorhersage.
- `/toggleweather`: Schaltet die Wetteranzeige in der Actionbar ein/aus.
## Berechtigungen
- `realtimeweather.reload`: Erlaubt `/wetter reload`.
- `realtimeweather.admin`: Erlaubt Admin-Benachrichtigungen bei Wetterabfrage-Fehlern.
> Fragen, Feature-Wünsche oder Bugreports gerne als Gitea-Issue!

48
pom.xml
View File

@@ -7,7 +7,7 @@
<groupId>dev.viper</groupId>
<artifactId>RealTimeWeather</artifactId>
<version>1.0</version>
<version>1.4</version>
<packaging>jar</packaging>
<name>RealTimeWeather</name>
@@ -15,8 +15,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<repositories>
@@ -46,18 +46,36 @@
<version>20231013</version>
</dependency>
<!-- Adventure API -->
<!-- Adventure API (nur für Chat-Nachrichten, NICHT für ActionBar) -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.17.0</version>
<scope>compile</scope>
</dependency>
<!-- Adventure Platform Bukkit -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.3.3</version>
<version>4.3.4</version>
<scope>compile</scope>
</dependency>
<!-- Adventure Text Serializer Legacy -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>4.17.0</version>
<scope>compile</scope>
</dependency>
<!-- bStats Bukkit Library -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -65,7 +83,7 @@
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<version>3.13.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
@@ -75,7 +93,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
@@ -87,21 +105,19 @@
<pattern>org.json</pattern>
<shadedPattern>dev.viper.shaded.org.json</shadedPattern>
</relocation>
<!-- Shade Adventure API -->
<!-- Shade ALLE Adventure/Kyori Packages zusammen -->
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>dev.viper.shaded.net.kyori.adventure</shadedPattern>
<pattern>net.kyori</pattern>
<shadedPattern>dev.viper.shaded.net.kyori</shadedPattern>
</relocation>
<!-- Shade Adventure Platform Bukkit -->
<!-- Shade bStats -->
<relocation>
<pattern>net.kyori.platform.bukkit</pattern>
<shadedPattern>dev.viper.shaded.net.kyori.platform.bukkit</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>dev.viper.shaded.org.bstats</shadedPattern>
</relocation>
</relocations>
<createDependencyReducedPom>false</createDependencyReducedPom>
<!-- Minimieren, um die JAR-Größe zu reduzieren (optional) -->
<minimizeJar>true</minimizeJar>
<!-- Filter, um unnötige Klassen auszuschließen -->
<minimizeJar>false</minimizeJar>
<filters>
<filter>
<artifact>*:*</artifact>

View File

@@ -0,0 +1,32 @@
package dev.viper.weathertime;
public enum DisplayMode {
COMPACT("Kompakt", 5),
STANDARD("Standard", 10),
DETAILED("Detailliert", 15);
private final String displayName;
private final int lineCount;
DisplayMode(String displayName, int lineCount) {
this.displayName = displayName;
this.lineCount = lineCount;
}
public String getDisplayName() {
return displayName;
}
public int getLineCount() {
return lineCount;
}
public static DisplayMode fromString(String mode) {
for (DisplayMode dm : values()) {
if (dm.name().equalsIgnoreCase(mode)) {
return dm;
}
}
return STANDARD;
}
}

View File

@@ -0,0 +1,21 @@
package dev.viper.weathertime;
import org.bstats.bukkit.Metrics;
import org.bukkit.plugin.java.JavaPlugin;
public class MetricsManager {
private final Metrics metrics;
public MetricsManager(JavaPlugin plugin) {
int pluginId = 26865; // Deine Plugin-ID von bStats
this.metrics = new Metrics(plugin, pluginId);
// Optional: Eigene Charts hinzufügen
// metrics.addCustomChart(new Metrics.SingleLineChart("custom_chart", () -> 1));
}
public Metrics getMetrics() {
return metrics;
}
}

View File

@@ -0,0 +1,69 @@
package dev.viper.weathertime;
import org.bukkit.World;
public class MoonPhase {
/**
* Berechnet die aktuelle Mondphase basierend auf der Minecraft-Zeit
*/
public static String getPhase(World world) {
long fullTime = world.getFullTime();
long daysPassed = fullTime / 24000L;
int phase = (int) (daysPassed % 8);
switch (phase) {
case 0: return "🌑 Neumond";
case 1: return "🌒 Zunehmende Sichel";
case 2: return "🌓 Erstes Viertel";
case 3: return "🌔 Zunehmender Mond";
case 4: return "🌕 Vollmond";
case 5: return "🌖 Abnehmender Mond";
case 6: return "🌗 Letztes Viertel";
case 7: return "🌘 Abnehmende Sichel";
default: return "🌑 Neumond";
}
}
/**
* Gibt nur das Emoji der Mondphase zurück
*/
public static String getEmoji(World world) {
long fullTime = world.getFullTime();
long daysPassed = fullTime / 24000L;
int phase = (int) (daysPassed % 8);
switch (phase) {
case 0: return "🌑";
case 1: return "🌒";
case 2: return "🌓";
case 3: return "🌔";
case 4: return "🌕";
case 5: return "🌖";
case 6: return "🌗";
case 7: return "🌘";
default: return "🌑";
}
}
/**
* Gibt den Namen der Mondphase zurück (ohne Emoji)
*/
public static String getName(World world) {
long fullTime = world.getFullTime();
long daysPassed = fullTime / 24000L;
int phase = (int) (daysPassed % 8);
switch (phase) {
case 0: return "Neumond";
case 1: return "Zunehmende Sichel";
case 2: return "Erstes Viertel";
case 3: return "Zunehmender Mond";
case 4: return "Vollmond";
case 5: return "Abnehmender Mond";
case 6: return "Letztes Viertel";
case 7: return "Abnehmende Sichel";
default: return "Neumond";
}
}
}

View File

@@ -0,0 +1,64 @@
package dev.viper.weathertime;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.JSONObject;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import java.util.function.Consumer;
public class UpdateChecker {
private final JavaPlugin plugin;
private final int resourceId;
public UpdateChecker(JavaPlugin plugin, int resourceId) {
this.plugin = plugin;
this.resourceId = resourceId;
}
public void getLatestVersion(Consumer<String> consumer) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
InputStream inputStream = null;
Scanner scanner = null;
try {
HttpURLConnection connection = (HttpURLConnection)
new URL("https://api.spiget.org/v2/resources/" + resourceId + "/versions/latest").openConnection();
connection.setRequestMethod("GET");
connection.addRequestProperty("User-Agent", "Mozilla/5.0");
inputStream = connection.getInputStream();
scanner = new Scanner(inputStream);
String response = scanner.useDelimiter("\\A").next();
JSONObject json = new JSONObject(response);
String versionName = json.optString("name");
// Nur Zahlen + Punkte behalten (z.B. "version 1.1" -> "1.1")
String cleanVersion = versionName.replaceAll("[^0-9.]", "").trim();
consumer.accept(cleanVersion.isEmpty() ? versionName : cleanVersion);
} catch (Exception e) {
plugin.getLogger().warning("Konnte keine Update-Info abrufen: " + e.getMessage());
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (Exception e) {
// Ignore
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
// Ignore
}
}
}
});
}
}

View File

@@ -0,0 +1,97 @@
package dev.viper.weathertime;
import org.bukkit.ChatColor;
public class WeatherColors {
/**
* Gibt die Farbe basierend auf der Temperatur zurück
*/
public static ChatColor getTemperatureColor(double tempCelsius) {
if (tempCelsius < 0) {
return ChatColor.AQUA; // Unter 0°C - Hellblau (eisig)
} else if (tempCelsius < 10) {
return ChatColor.BLUE; // 0-10°C - Blau (kalt)
} else if (tempCelsius < 20) {
return ChatColor.GREEN; // 10-20°C - Grün (mild)
} else if (tempCelsius < 30) {
return ChatColor.YELLOW; // 20-30°C - Gelb (warm)
} else {
return ChatColor.RED; // Über 30°C - Rot (heiß)
}
}
/**
* Gibt die Farbe basierend auf dem Wetter zurück
*/
public static ChatColor getWeatherColor(String weatherMain) {
switch (weatherMain.toLowerCase()) {
case "clear":
return ChatColor.GOLD; // Sonnig - Gold
case "clouds":
return ChatColor.GRAY; // Bewölkt - Grau
case "rain":
case "drizzle":
return ChatColor.BLUE; // Regen - Blau
case "thunderstorm":
return ChatColor.DARK_PURPLE; // Gewitter - Dunkelviolett
case "snow":
return ChatColor.WHITE; // Schnee - Weiß
case "mist":
case "fog":
case "haze":
return ChatColor.DARK_GRAY; // Nebel - Dunkelgrau
default:
return ChatColor.WHITE;
}
}
/**
* Gibt die Farbe basierend auf der Luftfeuchtigkeit zurück
*/
public static ChatColor getHumidityColor(int humidity) {
if (humidity < 30) {
return ChatColor.RED; // Trocken
} else if (humidity < 60) {
return ChatColor.GREEN; // Angenehm
} else if (humidity < 80) {
return ChatColor.YELLOW; // Feucht
} else {
return ChatColor.AQUA; // Sehr feucht
}
}
/**
* Gibt die Farbe basierend auf der Windgeschwindigkeit zurück (m/s)
*/
public static ChatColor getWindSpeedColor(double windSpeed) {
if (windSpeed < 5) {
return ChatColor.GREEN; // Leicht
} else if (windSpeed < 10) {
return ChatColor.YELLOW; // Mäßig
} else if (windSpeed < 15) {
return ChatColor.GOLD; // Frisch
} else if (windSpeed < 20) {
return ChatColor.RED; // Stark
} else {
return ChatColor.DARK_RED; // Sturm
}
}
/**
* Gibt die Farbe basierend auf dem UV-Index zurück
*/
public static ChatColor getUVIndexColor(int uvIndex) {
if (uvIndex <= 2) {
return ChatColor.GREEN; // Niedrig
} else if (uvIndex <= 5) {
return ChatColor.YELLOW; // Mäßig
} else if (uvIndex <= 7) {
return ChatColor.GOLD; // Hoch
} else if (uvIndex <= 10) {
return ChatColor.RED; // Sehr hoch
} else {
return ChatColor.DARK_RED; // Extrem
}
}
}

View File

@@ -1,123 +1,196 @@
package dev.viper.weathertime;
import org.json.JSONObject;
import org.json.JSONArray;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.logging.Logger;
public class WeatherFetcher {
private final String apiKey;
private final String location;
private final String units;
private final Logger logger;
public WeatherFetcher(String apiKey, String location, String units, Logger logger) {
this.apiKey = apiKey;
this.location = location;
this.units = units.toLowerCase();
this.logger = logger;
if (!this.units.equals("metric") && !this.units.equals("imperial")) {
logger.warning("Ungültige Einheit für " + location + ": " + units + ". Verwende 'metric' als Fallback.");
}
}
public WeatherData fetch() throws Exception {
String effectiveUnits = units.equals("metric") || units.equals("imperial") ? units : "metric";
String urlString = String.format(
"https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s",
location, apiKey, effectiveUnits);
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
int status = connection.getResponseCode();
if (status != 200) {
logger.warning("HTTP-Fehlercode: " + status + " für Ort: " + location);
throw new RuntimeException("HTTP-Fehlercode: " + status + " für Ort: " + location);
}
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder content = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
connection.disconnect();
JSONObject json = new JSONObject(content.toString());
long dt = json.getLong("dt");
int timezoneShift;
try {
timezoneShift = json.getInt("timezone");
} catch (Exception e) {
logger.warning("Fehler beim Abrufen der Zeitzone für Ort: " + location + ": " + e.getMessage());
throw new RuntimeException("Fehler beim Abrufen der Zeitzone für Ort: " + location, e);
}
ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC")).plusSeconds(timezoneShift);
String weatherMain = json.getJSONArray("weather").getJSONObject(0).getString("main");
double temperature = json.getJSONObject("main").getDouble("temp");
if (effectiveUnits.equals("imperial") && (temperature < -50 || temperature > 150)) {
logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°F. API-Fehler?");
} else if (effectiveUnits.equals("metric") && (temperature < -50 || temperature > 60)) {
logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°C. API-Fehler?");
}
return new WeatherData(dateTime, weatherMain, temperature);
}
public JSONObject fetchForecast() throws Exception {
String effectiveUnits = units.equals("metric") || units.equals("imperial") ? units : "metric";
String urlString = String.format(
"https://api.openweathermap.org/data/2.5/forecast?q=%s&appid=%s&units=%s",
location, apiKey, effectiveUnits);
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
int status = connection.getResponseCode();
if (status != 200) {
logger.warning("HTTP-Fehlercode: " + status + " für Ort: " + location);
throw new RuntimeException("HTTP-Fehlercode: " + status + " für Ort: " + location);
}
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder content = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
connection.disconnect();
return new JSONObject(content.toString());
}
public 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;
}
}
}
package dev.viper.weathertime;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.logging.Logger;
/**
* Holt Wetter und Zeitdaten asynchron von OpenWeatherMap.
* Liefert WeatherTimeData zurück, mit konsistentem Modell und Fehlerhandling.
* Erweiterung: Erfasst Luftfeuchtigkeit, Windgeschwindigkeit, Sonnenauf- und -untergang.
*/
public class WeatherFetcher {
private final String apiKey;
private final String location;
private final String units;
private final Logger logger;
public WeatherFetcher(String apiKey, String location, String units, Logger logger) {
this.apiKey = apiKey == null ? "" : apiKey.trim();
this.location = location == null ? "Berlin,de" : location.trim();
this.units = units == null ? "metric" : units.trim().toLowerCase();
this.logger = logger;
if (!this.units.equals("metric") && !this.units.equals("imperial")) {
logger.warning("Ungültige Einheit für " + this.location + ": " + this.units + ". Verwende 'metric' als Fallback.");
}
}
/**
* Holt die aktuellen Wetter- und Zeitdaten inklusive Zusatzinfos.
* Wirft RuntimeExceptions mit klaren Messages bei Fehlern.
*/
public WeatherTimeData fetchAsWeatherTimeData() throws Exception {
if (apiKey.isEmpty()) throw new RuntimeException("API-Key fehlt.");
String effectiveUnits = (units.equals("metric") || units.equals("imperial")) ? units : "metric";
String urlString = String.format(
"https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s",
location, apiKey, effectiveUnits);
BufferedReader in = null;
try {
JSONObject json;
in = new BufferedReader(new InputStreamReader(openConnectionStream(urlString)));
StringBuilder content = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
json = new JSONObject(content.toString());
// Fehlerfall: Antwort-Code ungleich 200
if (json.has("cod") && json.getInt("cod") != 200) {
String msg = json.has("message") ? json.getString("message") : "Unbekannter Fehler";
logger.warning("API Error für " + location + ": " + msg);
throw new RuntimeException(msg);
}
long dt = json.getLong("dt");
int timezoneShift = json.optInt("timezone", 0); // Zeitzonen-Offset in Sekunden
ZonedDateTime dateTime = Instant.ofEpochSecond(dt)
.atZone(ZoneId.of("UTC"))
.plusSeconds(timezoneShift);
String weatherMain = json.getJSONArray("weather").getJSONObject(0).getString("main");
double temperature = json.getJSONObject("main").getDouble("temp");
if (effectiveUnits.equals("imperial") && (temperature < -50 || temperature > 150)) {
logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°F. API-Fehler?");
} else if (effectiveUnits.equals("metric") && (temperature < -50 || temperature > 60)) {
logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°C. API-Fehler?");
}
// Temperatur immer in Celsius für WeatherTimeData speichern
double tempCelsius = effectiveUnits.equals("imperial") ? (temperature - 32) * 5.0 / 9.0 : temperature;
// FELDER AUSLESEN
int humidity = json.getJSONObject("main").getInt("humidity");
double windSpeed = json.getJSONObject("wind").optDouble("speed", 0.0);
double windDeg = json.getJSONObject("wind").optDouble("deg", 0.0);
int pressure = json.getJSONObject("main").optInt("pressure", 1013);
int clouds = json.getJSONObject("clouds").optInt("all", 0);
double feelsLikeTemp = json.getJSONObject("main").optDouble("feels_like", temperature);
double feelsLikeCelsius = effectiveUnits.equals("imperial") ? (feelsLikeTemp - 32) * 5.0 / 9.0 : feelsLikeTemp;
int visibility = json.optInt("visibility", 10000); // Standard 10km
ZonedDateTime sunrise = Instant.ofEpochSecond(json.getJSONObject("sys").getLong("sunrise"))
.atZone(ZoneId.of("UTC"))
.plusSeconds(timezoneShift);
ZonedDateTime sunset = Instant.ofEpochSecond(json.getJSONObject("sys").getLong("sunset"))
.atZone(ZoneId.of("UTC"))
.plusSeconds(timezoneShift);
return new WeatherTimeData(dateTime, weatherMain, tempCelsius,
humidity, windSpeed, windDeg, sunrise, sunset,
pressure, clouds, feelsLikeCelsius, visibility);
} catch (RuntimeException ru) {
throw ru;
} catch (Exception ex) {
logger.warning("Fehler beim Abrufen von OpenWeatherMap: " + ex.getMessage());
throw new RuntimeException("Verbindung oder API-Fehler: " + ex.getMessage(), ex);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// Ignore
}
}
}
}
/**
* Holt das Wetter-Vorhersage-JSON (5 Tage, 3-Stunden-Takt).
* Muss im Command asynchron ausgewertet werden!
*/
public JSONObject fetchForecast() throws Exception {
if (apiKey.isEmpty()) throw new RuntimeException("API-Key fehlt.");
String effectiveUnits = (units.equals("metric") || units.equals("imperial")) ? units : "metric";
String urlString = String.format(
"https://api.openweathermap.org/data/2.5/forecast?q=%s&appid=%s&units=%s",
location, apiKey, effectiveUnits);
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(openConnectionStream(urlString)));
StringBuilder content = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
JSONObject json = new JSONObject(content.toString());
if (json.has("cod") && (!"200".equals(json.get("cod").toString()))) {
String msg = json.has("message") ? json.getString("message") : "Unbekannter Fehler";
logger.warning("API Error (Forecast) für " + location + ": " + msg);
throw new RuntimeException(msg);
}
return json;
} catch (Exception e) {
logger.warning("Fehler beim Abrufen der Wettervorhersage: " + e.getMessage());
throw new RuntimeException("Fehler beim Abrufen der Wettervorhersage: " + e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// Ignore
}
}
}
}
/** Öffnet eine HTTP-GET-Verbindung und liefert den InputStream der Response zurück (Timeouts gesetzt). */
private java.io.InputStream openConnectionStream(String urlString) throws Exception {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(6000);
connection.setReadTimeout(6000);
int status = connection.getResponseCode();
if (status != 200) {
String err = "(keine Fehlerdetails)";
BufferedReader errIn = null;
try {
errIn = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
StringBuilder sb = new StringBuilder();
String l;
while ((l = errIn.readLine()) != null) sb.append(l);
err = sb.toString();
} catch (Exception suppress) {
// Ignore
} finally {
if (errIn != null) {
try {
errIn.close();
} catch (Exception e) {
// Ignore
}
}
}
throw new RuntimeException("HTTP-Fehlercode: " + status + " " + err);
}
return connection.getInputStream();
}
}

View File

@@ -1,57 +1,164 @@
package dev.viper.weathertime;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneOffset;
public class WeatherTimeData {
private final boolean rain;
private final boolean thunder;
private final long unixTime;
private final double tempCelsius;
public WeatherTimeData(boolean rain, boolean thunder, long unixTime, double tempCelsius) {
this.rain = rain;
this.thunder = thunder;
this.unixTime = unixTime;
this.tempCelsius = tempCelsius;
}
public boolean isRain() {
return rain;
}
public boolean isThunder() {
return thunder;
}
public double getTemp(String unit) {
return unit.equalsIgnoreCase("F") ? (tempCelsius * 9 / 5) + 32 : tempCelsius;
}
public String getFormattedTime(String format) {
LocalTime time = Instant.ofEpochSecond(unixTime)
.atOffset(ZoneOffset.UTC)
.toLocalTime();
if ("12h".equalsIgnoreCase(format)) {
int hour = time.getHour();
String ampm = hour >= 12 ? "PM" : "AM";
hour = hour % 12;
if (hour == 0) hour = 12;
return String.format("%02d:%02d %s", hour, time.getMinute(), ampm);
} else {
return String.format("%02d:%02d", time.getHour(), time.getMinute());
}
}
public long toMinecraftTime() {
LocalTime time = Instant.ofEpochSecond(unixTime)
.atOffset(ZoneOffset.UTC)
.toLocalTime();
int hour = time.getHour();
int minute = time.getMinute();
return (hour * 1000L / 24) + (minute * 1000L / (24 * 60));
}
}
package dev.viper.weathertime;
import java.time.ZonedDateTime;
/**
* Zentrale Datenklasse für Wetter- und Zeitinformationen.
* Erweiterung: enthält jetzt auch Luftfeuchtigkeit, Windgeschwindigkeit, Windrichtung,
* Sonnenauf/untergang, Luftdruck, Wolkendichte und UV-Index.
*/
public class WeatherTimeData {
private final ZonedDateTime dateTime; // Lokale Zeit am Standort
private final String weatherMain; // Hauptwetterbeschreibung (z. B. "Clear", "Rain")
private final double tempCelsius; // Temperatur in °C
private final int humidity; // Luftfeuchtigkeit in %
private final double windSpeed; // Windgeschwindigkeit (m/s oder mph)
private final double windDeg; // Windrichtung in Grad (0-360)
private final ZonedDateTime sunrise; // Sonnenaufgang
private final ZonedDateTime sunset; // Sonnenuntergang
private final int pressure; // Luftdruck in hPa
private final int clouds; // Wolkendichte in %
private final double feelsLike; // Gefühlte Temperatur in °C
private final int visibility; // Sichtweite in Metern
public WeatherTimeData(
ZonedDateTime dateTime,
String weatherMain,
double tempCelsius,
int humidity,
double windSpeed,
double windDeg,
ZonedDateTime sunrise,
ZonedDateTime sunset,
int pressure,
int clouds,
double feelsLike,
int visibility) {
this.dateTime = dateTime;
this.weatherMain = weatherMain;
this.tempCelsius = tempCelsius;
this.humidity = humidity;
this.windSpeed = windSpeed;
this.windDeg = windDeg;
this.sunrise = sunrise;
this.sunset = sunset;
this.pressure = pressure;
this.clouds = clouds;
this.feelsLike = feelsLike;
this.visibility = visibility;
}
public ZonedDateTime getDateTime() {
return dateTime;
}
public String getWeatherMain() {
return weatherMain;
}
public double getTemp(String unit) {
return "F".equalsIgnoreCase(unit)
? (tempCelsius * 9.0 / 5.0) + 32.0
: tempCelsius;
}
public int getHumidity() {
return humidity;
}
public double getWindSpeed() {
return windSpeed;
}
public double getWindDeg() {
return windDeg;
}
public ZonedDateTime getSunrise() {
return sunrise;
}
public ZonedDateTime getSunset() {
return sunset;
}
public int getPressure() {
return pressure;
}
public int getClouds() {
return clouds;
}
public double getFeelsLike(String unit) {
return "F".equalsIgnoreCase(unit)
? (feelsLike * 9.0 / 5.0) + 32.0
: feelsLike;
}
public int getVisibility() {
return visibility;
}
/**
* Berechnet einen einfachen UV-Index basierend auf Tageszeit, Wolken und Wetter
* (0-11+: 0-2=niedrig, 3-5=mäßig, 6-7=hoch, 8-10=sehr hoch, 11+=extrem)
*/
public int getUVIndex() {
int hour = dateTime.getHour();
// Nachts ist UV-Index 0
if (hour < 6 || hour > 20) {
return 0;
}
// Basis UV-Index je nach Tageszeit (Maximum mittags)
int baseUV;
if (hour >= 11 && hour <= 13) {
baseUV = 9; // Mittags am höchsten
} else if (hour >= 10 && hour <= 14) {
baseUV = 7;
} else if (hour >= 9 && hour <= 15) {
baseUV = 5;
} else {
baseUV = 3;
}
// Reduzierung durch Wolken
baseUV = baseUV - (clouds / 20);
// Reduzierung bei Regen/Schnee/Gewitter
if (weatherMain.equalsIgnoreCase("rain") ||
weatherMain.equalsIgnoreCase("drizzle") ||
weatherMain.equalsIgnoreCase("thunderstorm") ||
weatherMain.equalsIgnoreCase("snow")) {
baseUV = Math.max(1, baseUV / 2);
}
return Math.max(0, Math.min(11, baseUV));
}
public String getFormattedTime(String format) {
int hour = dateTime.getHour();
int minute = dateTime.getMinute();
if ("12h".equalsIgnoreCase(format)) {
String ampm = hour >= 12 ? "PM" : "AM";
hour = hour % 12;
if (hour == 0) hour = 12;
return String.format("%02d:%02d %s", hour, minute, ampm);
}
return String.format("%02d:%02d", hour, minute);
}
public long toMinecraftTime() {
int hour = dateTime.getHour();
int minute = dateTime.getMinute();
int second = dateTime.getSecond();
return ((hour + 18) % 24) * 1000L
+ (minute * 1000L / 60)
+ (second * 1000L / 3600);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,83 @@
package dev.viper.weathertime;
public class WindDirection {
/**
* Konvertiert Windrichtung in Grad (0-360) zu Himmelsrichtung
*/
public static String getDirection(double degrees) {
// Normalisiere auf 0-360
degrees = degrees % 360;
if (degrees < 0) degrees += 360;
if (degrees >= 337.5 || degrees < 22.5) {
return "N"; // Nord
} else if (degrees >= 22.5 && degrees < 67.5) {
return "NO"; // Nordost
} else if (degrees >= 67.5 && degrees < 112.5) {
return "O"; // Ost
} else if (degrees >= 112.5 && degrees < 157.5) {
return "SO"; // Südost
} else if (degrees >= 157.5 && degrees < 202.5) {
return "S"; // Süd
} else if (degrees >= 202.5 && degrees < 247.5) {
return "SW"; // Südwest
} else if (degrees >= 247.5 && degrees < 292.5) {
return "W"; // West
} else {
return "NW"; // Nordwest
}
}
/**
* Gibt den Pfeil-Emoji basierend auf der Windrichtung zurück
*/
public static String getArrow(double degrees) {
degrees = degrees % 360;
if (degrees < 0) degrees += 360;
if (degrees >= 337.5 || degrees < 22.5) {
return ""; // Nord (Wind kommt von Norden, weht nach Süden)
} else if (degrees >= 22.5 && degrees < 67.5) {
return ""; // Nordost
} else if (degrees >= 67.5 && degrees < 112.5) {
return ""; // Ost
} else if (degrees >= 112.5 && degrees < 157.5) {
return ""; // Südost
} else if (degrees >= 157.5 && degrees < 202.5) {
return ""; // Süd
} else if (degrees >= 202.5 && degrees < 247.5) {
return ""; // Südwest
} else if (degrees >= 247.5 && degrees < 292.5) {
return ""; // West
} else {
return ""; // Nordwest
}
}
/**
* Gibt die volle Bezeichnung der Windrichtung zurück
*/
public static String getFullName(double degrees) {
degrees = degrees % 360;
if (degrees < 0) degrees += 360;
if (degrees >= 337.5 || degrees < 22.5) {
return "Nord";
} else if (degrees >= 22.5 && degrees < 67.5) {
return "Nordost";
} else if (degrees >= 67.5 && degrees < 112.5) {
return "Ost";
} else if (degrees >= 112.5 && degrees < 157.5) {
return "Südost";
} else if (degrees >= 157.5 && degrees < 202.5) {
return "Süd";
} else if (degrees >= 202.5 && degrees < 247.5) {
return "Südwest";
} else if (degrees >= 247.5 && degrees < 292.5) {
return "West";
} else {
return "Nordwest";
}
}
}

View File

@@ -1,30 +1,39 @@
api-key: "DEIN_API_KEY_HIER"
update-interval: 60
defaults:
enabled: true
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position: "top-right"
padding-right: 100
worlds:
world:
enabled: true
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position: "top-left"
padding-right: 50
world_nether:
enabled: false
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: false
display-position: "top-right"
padding-right: 150
# OpenWeatherMap API-Key hier eintragen
api-key: "DEIN_API_KEY_HIER"
# Update-Intervall in Sekunden (Standard: 60)
update-interval: 60
defaults:
enabled: true
location: "Berlin,de" # Stadt,Land
units: "metric" # metric = °C, imperial = °F
time-format: "24h" # oder "12h"
display-actionbar: true
display-weather-icon: true
display-position: "top-right"
padding-right: 100
sync-in-game-weather: true
worlds:
world:
enabled: true
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: true
display-position: "top-left"
padding-right: 50
sync-in-game-weather: true
world_nether:
enabled: false
location: "Berlin,de"
units: "metric"
time-format: "24h"
display-actionbar: true
display-weather-icon: false
display-position: "top-right"
padding-right: 150
sync-in-game-weather: false

View File

@@ -1,30 +1,32 @@
name: RealTimeWeather
version: 1.0
main: dev.viper.weathertime.WeatherTimeSyncPlugin
api-version: 1.21
commands:
wetter:
description: Manages real-time weather and time synchronization
usage: /<command> reload | setlocation <city,country> | query | info | gui | help
permission: realtimeweather.use
weatherforecast:
description: Shows the weather forecast for the player's location
usage: /<command>
permission: realtimeweather.forecast
toggleweather:
description: Toggles the weather display for the current world
usage: /<command>
permission: realtimeweather.toggle
permissions:
realtimeweather.use:
description: Allows usage of the /wetter command
default: true
realtimeweather.forecast:
description: Allows usage of the /weatherforecast command
default: true
realtimeweather.toggle:
description: Allows usage of the /toggleweather command
default: true
realtimeweather.reload:
description: Allows reloading the plugin configuration
default: op
name: RealTimeWeather
version: 1.4
main: dev.viper.weathertime.WeatherTimeSyncPlugin
api-version: 1.21
commands:
wetter:
description: Manages real-time weather and time synchronization
usage: /<command> reload | setlocation <city,country> | query | info | gui | help
permission: realtimeweather.use
weatherforecast:
description: Shows the weather forecast for the player's location
usage: /<command>
permission: realtimeweather.forecast
toggleweather:
description: Toggles the weather display for the current world
usage: /<command>
permission: realtimeweather.toggle
permissions:
realtimeweather.use:
description: Allows usage of the /wetter command
default: true
realtimeweather.forecast:
description: Allows usage of the /weatherforecast command
default: true
realtimeweather.toggle:
description: Allows usage of the /toggleweather command
default: true
realtimeweather.reload:
description: Allows reloading the plugin configuration
default: op