diff --git a/.idea/copyright/gpl3.xml b/.idea/copyright/gpl3.xml new file mode 100644 index 0000000..429950e --- /dev/null +++ b/.idea/copyright/gpl3.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..19771df --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DOMAINS-FIREWALL.md b/DOMAINS-FIREWALL.md index 4cd8908..b99b2aa 100644 --- a/DOMAINS-FIREWALL.md +++ b/DOMAINS-FIREWALL.md @@ -2,7 +2,7 @@ If you're using a firewall, you must allow the following hosts: - updater: * git.m724.eu - weather: - * api.openweathermap.org + * api.open-meteo.com - thunder: * ws1.blitzortung.org * ws7.blitzortung.org diff --git a/notes.txt b/notes.txt deleted file mode 100644 index 5f0f512..0000000 --- a/notes.txt +++ /dev/null @@ -1,73 +0,0 @@ -goal: realtime to in game time conversion -There is no need to keep days - -minecraft day is 0 - 24000 ticks -where 6000 ticks is noon (peak sun) and 18000 is midnight (peak moon) - -irl day is 0 - 86400 seconds - -0. s = epoch % 86400 to get seconds since midnight - ^ - (* scale) here -1. t = s / 72.0 to fit into minecraft day -2. t = t * 20 to convert that to ticks -3. t = t - 6000 to align noon and midnight - this leaves us with negative time, so -4. t = floorMod(t, 24000) to wrap if negative - -example: -epoch = 1713593340 - -0. getting seconds since midnight - s = epoch % 86400 - s = 1713593340 % 86400 - s = 22140 - -1. conversion to minecraft day length - gs = s / 72.0 - gs = 22140 / 72.0 - gs = 307.5 - -2. to ticks - t = gs * 20 - t = 307.5 * 20 - t = 6150 - -3. step 3 - t = t - 6000 - t = 6150 - 6000 - t = 150 - -4. wrapping - t = floorMod(150, 24000) - t = 150 - - -goal: frequency of time update - -t = 72 / scale -t is the period, in ticks of course - -to see how many irl seconds a tick represents: -s = 3.6 * scale -(from 1 / (1/72 * 20 * scale)) - -however, some scales result in fractions -here's how many in game aligned seconds have passed at the end of a real day: - 84000 / 72 * scale * floor(72/scale) -for scale 0.99: 83160 -so we'll be 14 minutes behind - -solution? for now let's warn and update time every tick -to check: -scale * floor(72/scale) == 72 - - -goal: offsetting by player position - -t = (longitude / 15) * 1000 * scale - -accounting for sunrise and sunset -TODO, idk yet without -update: this is now possible with 0.8.0 api - diff --git a/pom.xml b/pom.xml index 71b767c..c0308a4 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ eu.m724 wtapi - 0.9.2 + 0.9.3 eu.m724 diff --git a/src/main/java/eu/m724/realweather/Configs.java b/src/main/java/eu/m724/realweather/Configs.java index 18ebb52..51e9bd1 100644 --- a/src/main/java/eu/m724/realweather/Configs.java +++ b/src/main/java/eu/m724/realweather/Configs.java @@ -1,22 +1,19 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather; -import eu.m724.realweather.mapper.MapperConfig; +import eu.m724.realweather.map.MapConfig; import eu.m724.realweather.thunder.ThunderConfig; import eu.m724.realweather.time.TimeConfig; -import eu.m724.realweather.updater.UpdaterConfig; -import eu.m724.realweather.weather.WeatherConfig; +import eu.m724.realweather.weather.WeatherChanger; -// TODO replaces GlobalConstants for configs -public class Configs { - static WeatherConfig weatherConfig; - static TimeConfig timeConfig; - static ThunderConfig thunderConfig; - static MapperConfig mapperConfig; - static UpdaterConfig updaterConfig; - - public static WeatherConfig weatherConfig() { return weatherConfig; } - public static TimeConfig timeConfig() { return timeConfig; } - public static ThunderConfig thunderConfig() { return thunderConfig; } - public static MapperConfig mapperConfig() { return mapperConfig; } - public static UpdaterConfig updaterConfig() { return updaterConfig; } +public record Configs( + WeatherChanger weatherConfig, + TimeConfig timeConfig, + ThunderConfig thunderConfig, + MapConfig mapConfig +) { } diff --git a/src/main/java/eu/m724/realweather/DebugLogger.java b/src/main/java/eu/m724/realweather/DebugLogger.java index 6850958..030bbc0 100644 --- a/src/main/java/eu/m724/realweather/DebugLogger.java +++ b/src/main/java/eu/m724/realweather/DebugLogger.java @@ -1,18 +1,78 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather; +import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class DebugLogger { - static Logger baseLogger; - static int debugLevel; - - public static int getDebugLevel() { - return debugLevel; + private DebugLogger() {} + static Logger logger; + + public static void info(String message, Object... format) { + log(Level.INFO, message, format); } - - - public static void info(String message, int minDebugLevel, Object... format) { - if (debugLevel >= minDebugLevel) - baseLogger.info(message.formatted(format)); + + public static void warning(String message, Object... format) { + log(Level.WARNING, message, format); } -} + + public static void severe(String message, Object... format) { + log(Level.SEVERE, message, format); + } + + public static void fine(String message, Object... format) { + log(Level.FINE, message, format); + } + + public static void finer(String message, Object... format) { + log(Level.FINER, message, format); + } + + private static void log(Level level, String message, Object... format) { + + if (!logger.isLoggable(level)) { + return; + } + + message = message.formatted(format); + + if (logger.getLevel().intValue() <= Level.FINE.intValue()) { + message = "[" + getCaller() + "] " + message; + } + + if (level.intValue() < Level.INFO.intValue()) { // levels below info are never logged even if set for some reason + // colors text gray (cyan is close to gray) + if (level == Level.FINE) { + message = "\033[38;5;250m" + message + "\033[39m"; + } else { + message = "\033[38;5;245m" + message + "\033[39m"; + } + level = Level.INFO; + } + + logger.log(level, message); + + } + + private static String getCaller() { + String caller = Thread.currentThread().getStackTrace()[4].getClassName(); + + if (caller.startsWith("eu.m724.realweather.")) { + caller = caller.substring("eu.m724.realweather.".length()); + + String[] packages = caller.split("\\."); + + caller = IntStream.range(0, packages.length - 1) + .mapToObj(i -> packages[i].substring(0, 2)) + .collect(Collectors.joining(".")) + "." + packages[packages.length - 1]; + } + + return caller; + } +} \ No newline at end of file diff --git a/src/main/java/eu/m724/realweather/GlobalConstants.java b/src/main/java/eu/m724/realweather/GlobalConstants.java deleted file mode 100644 index e6c1d0b..0000000 --- a/src/main/java/eu/m724/realweather/GlobalConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -package eu.m724.realweather; - -import org.bukkit.plugin.Plugin; - -import eu.m724.realweather.mapper.Mapper; -import eu.m724.realweather.weather.PlayerWeatherCache; - -// perhaps replace with a singleton -// TODO actually, remove it altogether -public class GlobalConstants { - static Mapper mapper; - static Plugin plugin; - static PlayerWeatherCache playerWeatherCache; - - public static Mapper getMapper() { - return mapper; - } - public static Plugin getPlugin() { - return plugin; - } - public static PlayerWeatherCache getPlayerWeatherCache() { - return playerWeatherCache; - } - -} diff --git a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java index 692a5c5..fd63d9c 100644 --- a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java +++ b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather; import java.io.File; @@ -5,139 +10,213 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.logging.Logger; +import java.util.logging.Level; import eu.m724.mstats.MStatsPlugin; -import eu.m724.wtapi.provider.exception.NoSuchProviderException; +import eu.m724.realweather.map.WorldList; +import eu.m724.realweather.updater.UpdateNotifier; import org.bukkit.configuration.file.YamlConfiguration; import eu.m724.realweather.commands.AdminCommand; -import eu.m724.realweather.commands.GeoCommand; -import eu.m724.realweather.commands.LocalTimeCommand; -import eu.m724.realweather.commands.LocalWeatherCommand; -import eu.m724.realweather.mapper.Mapper; -import eu.m724.realweather.mapper.MapperConfig; +import eu.m724.realweather.map.command.GeoCommand; +import eu.m724.realweather.time.command.LocalTimeCommand; +import eu.m724.realweather.weather.command.LocalWeatherCommand; +import eu.m724.realweather.map.CoordinatesLocationConverter; +import eu.m724.realweather.map.MapConfig; import eu.m724.realweather.thunder.ThunderConfig; import eu.m724.realweather.thunder.ThunderMaster; import eu.m724.realweather.time.TimeConfig; import eu.m724.realweather.time.TimeMaster; import eu.m724.realweather.updater.PluginUpdater; -import eu.m724.realweather.updater.UpdaterConfig; -import eu.m724.realweather.weather.PlayerWeatherCache; import eu.m724.realweather.weather.WeatherConfig; import eu.m724.realweather.weather.WeatherMaster; -import eu.m724.wtapi.provider.exception.ProviderException; // TODO unmess this too public class RealWeatherPlugin extends MStatsPlugin { + private Configs configs; + private WorldList worldList; + private WeatherMaster weatherMaster; - private ThunderMaster thunderMaster; private TimeMaster timeMaster; - private PluginUpdater updater; - - private Logger logger; + private ThunderMaster thunderMaster; + + private static RealWeatherPlugin INSTANCE; + + private CoordinatesLocationConverter coordinatesLocationConverter; @Override public void onEnable() { - logger = getLogger(); + long start = System.nanoTime(); + INSTANCE = this; File dataFolder = getDataFolder(); File modulesFolder = new File("modules"); modulesFolder.mkdir(); - - YamlConfiguration configuration, - mapConfiguration, weatherConfiguration, - thunderConfiguration, timeConfiguration; - - DebugLogger.info("loading configurations", 1); + boolean firstRun = !new File(dataFolder, "config.yml").exists(); + YamlConfiguration configuration; + try { configuration = getConfig("config.yml"); - mapConfiguration = getConfig("map.yml"); - weatherConfiguration = getConfig("modules/weather.yml"); - thunderConfiguration = getConfig("modules/thunder.yml"); - timeConfiguration = getConfig("modules/time.yml"); } catch (IOException e) { - logger.severe("Failed to load config!"); - throw new RuntimeException(e); + DebugLogger.severe("Failed to load configuration:"); + DebugLogger.severe(" " + e); + + getServer().getPluginManager().disablePlugin(this); + return; } + DebugLogger.logger = getLogger(); + + if (configuration.getBoolean("debug")) { + getLogger().setLevel(Level.FINEST); + DebugLogger.warning("Debug harms performance"); + } + + if (firstRun) { - logger.warning("This is your first time running this plugin."); - logger.warning("Please *shut down* the server, review the config files (enable modules, enter your API keys, etc.)"); - logger.warning("Don't forget to enable the plugin in config.yml"); + DebugLogger.warning("This is your first time running this plugin."); + DebugLogger.warning("Please *shut down* the server, review the config files (enable modules, enter API keys, etc.)"); + DebugLogger.warning("Don't forget to enable the plugin in config.yml"); getServer().getPluginManager().disablePlugin(this); return; } - - DebugLogger.baseLogger = logger; - DebugLogger.debugLevel = configuration.getInt("debug"); - - if (!configuration.getBoolean("enabled")) { - logger.warning("Plugin disabled by administrator. Enable it in config.yml"); + + if (configuration.getBoolean("disabled")) { + DebugLogger.warning("Plugin disabled per config. Enable it in config.yml"); + getServer().getPluginManager().disablePlugin(this); return; } - - GlobalConstants.plugin = this; - GlobalConstants.playerWeatherCache = new PlayerWeatherCache(); - - DebugLogger.info("loading mapper", 1); - Configs.mapperConfig = MapperConfig.fromConfiguration(mapConfiguration); - GlobalConstants.mapper = new Mapper(); - GlobalConstants.mapper.registerEvents(this); try { - DebugLogger.info("loading weather", 1); - Configs.weatherConfig = WeatherConfig.fromConfiguration(weatherConfiguration); - weatherMaster = new WeatherMaster(); - weatherMaster.init(this); - - DebugLogger.info("loading thunder", 1); - Configs.thunderConfig = ThunderConfig.fromConfiguration(thunderConfiguration); - thunderMaster = new ThunderMaster(); - thunderMaster.init(this); - - DebugLogger.info("loading time", 1); - Configs.timeConfig = TimeConfig.fromConfiguration(timeConfiguration); - timeMaster = new TimeMaster(); - timeMaster.init(); + loadMapModules(); + } catch (Exception e) { + DebugLogger.severe("Failed to load the Map module:"); + DebugLogger.severe(" " + e); - Configs.updaterConfig = UpdaterConfig.fromConfiguration(configuration.getConfigurationSection("updater")); - updater = PluginUpdater.build(this, this.getFile()); - //updater.init(); - } catch (NoSuchProviderException e) { - logger.severe("There's an error in your config:"); - logger.severe(" " + e.getMessage()); - - getServer().getPluginManager().disablePlugin(this); - return; - } catch (ProviderException e) { - logger.severe("Couldn't initialize provider!"); - logger.severe("Possible causes:"); - logger.severe("1. Your API key is invalid, or was added too recently"); - logger.severe("2. The provider or your internet is down"); - e.printStackTrace(); - getServer().getPluginManager().disablePlugin(this); return; } - getCommand("rwadmin").setExecutor(new AdminCommand(updater, thunderMaster)); - getCommand("geo").setExecutor(new GeoCommand()); + try { + loadWeatherModule(); + } catch (Exception e) { + DebugLogger.severe("Failed to load the Weather module:"); + DebugLogger.severe(" " + e); - if (Configs.timeConfig.enabled()) - getCommand("localtime").setExecutor(new LocalTimeCommand(timeMaster.getTimeConverter())); - - if (Configs.weatherConfig.enabled()) { - getCommand("localweather").setExecutor(new LocalWeatherCommand()); + getServer().getPluginManager().disablePlugin(this); + return; } + try { + loadThunderModule(); + } catch (Exception e) { + DebugLogger.severe("Failed to load the Thunder module:"); + DebugLogger.severe(" " + e); + + getServer().getPluginManager().disablePlugin(this); + return; + } + + try { + loadTimeModule(); + } catch (Exception e) { + DebugLogger.severe("Failed to load the Time module:"); + DebugLogger.severe(" " + e); + + getServer().getPluginManager().disablePlugin(this); + return; + } + + DebugLogger.fine("Loading Updater"); + + PluginUpdater updater = PluginUpdater.build(this.getFile(), configuration.getString("updater.channel")); + if (configuration.getBoolean("updater.notify")) { + UpdateNotifier updateNotifier = new UpdateNotifier(updater); + updateNotifier.register(); + } + + DebugLogger.fine("Done loading Updater"); + + getCommand("rwadmin").setExecutor(new AdminCommand(this, updater)); + getCommand("geo").setExecutor(new GeoCommand(coordinatesLocationConverter)); + mStats(2); - DebugLogger.info("ended loading", 1); + DebugLogger.fine("Plugin enabled. Took %.3f milliseconds", (System.nanoTime() - start) / 1000000.0); + } + + // TODO repeating here! + + private void loadMapModules() throws IOException { + DebugLogger.fine("Loading the Map modules"); + + YamlConfiguration yamlConfiguration = getConfig("map.yml"); + MapConfig mapConfig = MapConfig.fromConfiguration(yamlConfiguration); + this.coordinatesLocationConverter = new CoordinatesLocationConverter(mapConfig); + + this.worldList = new WorldList(mapConfig.worldNames(), mapConfig.worldNamesIsBlacklist()); + getServer().getPluginManager().registerEvents(worldList, this); + + DebugLogger.finer("Done loading Map modules"); + } + + private void loadWeatherModule() throws IOException { + DebugLogger.fine("Loading the Weather module"); + + YamlConfiguration yamlConfiguration = getConfig("modules/weather.yml"); + WeatherConfig weatherConfig = WeatherConfig.fromConfiguration(yamlConfiguration); + + if (!weatherConfig.enabled()) { + DebugLogger.finer("Weather module disabled per config"); + return; + } + + this.weatherMaster = new WeatherMaster(this, weatherConfig); + weatherMaster.init(); + + getCommand("localweather").setExecutor(new LocalWeatherCommand(weatherMaster.getPlayerWeatherStore())); + + DebugLogger.finer("Enabled Weather module"); + } + + private void loadThunderModule() throws IOException { + DebugLogger.fine("Loading the Thunder module"); + + YamlConfiguration yamlConfiguration = getConfig("modules/thunder.yml"); + ThunderConfig thunderConfig = ThunderConfig.fromConfiguration(yamlConfiguration); + + if (!thunderConfig.enabled()) { + DebugLogger.finer("Thunder module disabled per config"); + return; + } + + this.thunderMaster = new ThunderMaster(this, thunderConfig); + thunderMaster.init(); + + DebugLogger.finer("Enabled Thunder module"); + } + + private void loadTimeModule() throws IOException { + DebugLogger.fine("Loading the Time module"); + + YamlConfiguration yamlConfiguration = getConfig("modules/time.yml"); + TimeConfig timeConfig = TimeConfig.fromConfiguration(yamlConfiguration); + + if (!timeConfig.enabled()) { + DebugLogger.finer("Time module disabled per config"); + return; + } + + this.timeMaster = new TimeMaster(this, timeConfig); + timeMaster.init(); + + getCommand("localtime").setExecutor(new LocalTimeCommand(timeMaster, coordinatesLocationConverter)); + + DebugLogger.finer("Enabled Time module"); } public YamlConfiguration getConfig(String configFilePath) throws IOException { @@ -145,15 +224,50 @@ public class RealWeatherPlugin extends MStatsPlugin { YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); if (!configFile.exists()) { - final InputStream defConfigStream = getResource(configFilePath); - - if (defConfigStream == null) - return null; - - config = YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, StandardCharsets.UTF_8)); + try (InputStream defConfigStream = getResource(configFilePath)) { + if (defConfigStream == null) + return null; + + config = YamlConfiguration.loadConfiguration(new InputStreamReader(defConfigStream, StandardCharsets.UTF_8)); + } + config.save(configFile); } return config; } + + /** + * Gets the instance of RealWeather plugin. + * + * @return The instance of RealWeather plugin. + */ + public static RealWeatherPlugin getInstance() { + return INSTANCE; + } + + /** + * Gets the coordinates to location converter. + * + * @return The coordinates to location converter. + */ + public CoordinatesLocationConverter getCoordinatesLocationConverter() { + return coordinatesLocationConverter; + } + + public WeatherMaster getWeatherMaster() { + return weatherMaster; + } + + public TimeMaster getTimeMaster() { + return timeMaster; + } + + public ThunderMaster getThunderMaster() { + return thunderMaster; + } + + public WorldList getWorldList() { + return worldList; + } } diff --git a/src/main/java/eu/m724/realweather/api/weather/AsyncGlobalWeatherUpdateEvent.java b/src/main/java/eu/m724/realweather/api/weather/AsyncGlobalWeatherUpdateEvent.java index acb07b9..04d2b56 100644 --- a/src/main/java/eu/m724/realweather/api/weather/AsyncGlobalWeatherUpdateEvent.java +++ b/src/main/java/eu/m724/realweather/api/weather/AsyncGlobalWeatherUpdateEvent.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.api.weather; import eu.m724.wtapi.object.Weather; diff --git a/src/main/java/eu/m724/realweather/api/weather/AsyncPlayerWeatherUpdateEvent.java b/src/main/java/eu/m724/realweather/api/weather/AsyncPlayerWeatherUpdateEvent.java index bb4a27a..4a1c445 100644 --- a/src/main/java/eu/m724/realweather/api/weather/AsyncPlayerWeatherUpdateEvent.java +++ b/src/main/java/eu/m724/realweather/api/weather/AsyncPlayerWeatherUpdateEvent.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.api.weather; import eu.m724.wtapi.object.Weather; diff --git a/src/main/java/eu/m724/realweather/api/weather/AsyncWeatherUpdateEvent.java b/src/main/java/eu/m724/realweather/api/weather/AsyncWeatherUpdateEvent.java index f93a967..31c038b 100644 --- a/src/main/java/eu/m724/realweather/api/weather/AsyncWeatherUpdateEvent.java +++ b/src/main/java/eu/m724/realweather/api/weather/AsyncWeatherUpdateEvent.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.api.weather; import eu.m724.wtapi.object.Weather; diff --git a/src/main/java/eu/m724/realweather/commands/AdminCommand.java b/src/main/java/eu/m724/realweather/commands/AdminCommand.java index 712dda9..362aa02 100644 --- a/src/main/java/eu/m724/realweather/commands/AdminCommand.java +++ b/src/main/java/eu/m724/realweather/commands/AdminCommand.java @@ -1,67 +1,80 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.commands; -import eu.m724.realweather.Configs; +import eu.m724.realweather.RealWeatherPlugin; +import eu.m724.realweather.map.CoordinatesLocationConverter; +import eu.m724.realweather.time.TimeMaster; +import eu.m724.realweather.updater.command.UpdateCommand; +import eu.m724.realweather.weather.WeatherMaster; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.MapperConfig; -import eu.m724.realweather.thunder.ThunderConfig; import eu.m724.realweather.thunder.ThunderMaster; -import eu.m724.realweather.time.TimeConfig; import eu.m724.realweather.updater.PluginUpdater; -import eu.m724.realweather.weather.WeatherConfig; import net.md_5.bungee.api.ChatColor; -// TODO unmess this all public class AdminCommand implements CommandExecutor { + private final RealWeatherPlugin plugin; private final UpdateCommand updateCommand; - private final Plugin plugin = GlobalConstants.getPlugin(); - - private final WeatherConfig weatherConfig = Configs.weatherConfig(); - private final TimeConfig timeConfig = Configs.timeConfig(); - private final ThunderConfig thunderConfig = Configs.thunderConfig(); - private final MapperConfig mapperConfig = Configs.mapperConfig(); - + + private final WeatherMaster weatherMaster; + private final TimeMaster timeMaster; private final ThunderMaster thunderMaster; + private final CoordinatesLocationConverter coordinatesLocationConverter; - public AdminCommand(PluginUpdater updater, ThunderMaster thunderMaster) { + public AdminCommand(RealWeatherPlugin plugin, PluginUpdater updater) { + this.plugin = plugin; this.updateCommand = new UpdateCommand(updater); - this.thunderMaster = thunderMaster; + this.weatherMaster = plugin.getWeatherMaster(); + this.timeMaster = plugin.getTimeMaster(); + this.thunderMaster = plugin.getThunderMaster(); + this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter(); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length > 0 && args[0].equals("update")) { - return updateCommand.onCommand(sender, command, label, args); + updateCommand.updateCommand(sender, args); + return true; } colorize(sender, "\n&eRealWeather %s\n\n", plugin.getDescription().getVersion().replace("-SNAPSHOT", "&c-SNAPSHOT")); - colorize(sender, "&6Coordinate scale: &b%d, %d &7blocks / deg", mapperConfig.scaleLatitude, mapperConfig.scaleLongitude); + colorize(sender, "&6Coordinate scale: &b%f, %f &7blocks / deg", coordinatesLocationConverter.getScaleLatitude(), coordinatesLocationConverter.getScaleLongitude()); + colorize(sender, "&6Static point: &b%f, %f &7lat, lon", coordinatesLocationConverter.getStaticPoint().latitude(), coordinatesLocationConverter.getStaticPoint().longitude()); + sender.sendMessage(""); - colorize(sender, "\n&6Weather: %s", weatherConfig.enabled() ? (weatherConfig.dynamic() ? "&aYes, dynamic" : "&aYes, static") : "&cDisabled"); - - if (weatherConfig.enabled()) { - colorize(sender, " &6Provider: &b%s", weatherConfig.provider()); + if (weatherMaster != null) { + colorize(sender, "&6Weather: %s", weatherMaster.isDynamic() ? "&aYes, dynamic" : "&aYes, static"); + colorize(sender, " &6Provider: &b%s", weatherMaster.getProviderName()); colorize(sender, " &6/localweather to see current weather"); + } else { + colorize(sender, "&6Weather: %s", "&cDisabled"); } - colorize(sender, "\n&6Time: %s", timeConfig.enabled() ? (timeConfig.dynamic() ? "&aYes, dynamic" : "&aYes, static") : "&cDisabled"); - - if (timeConfig.enabled()) { - colorize(sender, " &6Scale: &b%s&7x", timeConfig.scale()); + sender.sendMessage(""); + + if (timeMaster != null) { + colorize(sender, "&6Time: %s", timeMaster.isDynamic() ? "&aYes, dynamic" : "&aYes, static"); + colorize(sender, " &6Scale: &b%s&7x", timeMaster.getTimeConverter().getScale()); colorize(sender, " &6/localtime to see current time"); + } else { + colorize(sender, "&6Time: &cDisabled"); } - colorize(sender, "\n&6Thunder: %s", thunderConfig.enabled() ? "&aYes, dynamic" : "&cDisabled"); - - if (thunderConfig.enabled()) { - colorize(sender, " &6Provider: &b%s", thunderConfig.provider()); - colorize(sender, " &6Refresh period: &b%d &7ticks", thunderConfig.refreshPeriod()); + sender.sendMessage(""); + + if (thunderMaster != null) { + colorize(sender, "&6Thunder: &aYes, dynamic"); + colorize(sender, " &6Provider: &b%s", thunderMaster.getProviderName()); colorize(sender, " &6API latency: &b%d&7ms", thunderMaster.getLatency()); + } else { + colorize(sender, "&6Thunder: &cDisabled"); } return true; diff --git a/src/main/java/eu/m724/realweather/commands/UpdateCommand.java b/src/main/java/eu/m724/realweather/commands/UpdateCommand.java deleted file mode 100644 index e8d6ed8..0000000 --- a/src/main/java/eu/m724/realweather/commands/UpdateCommand.java +++ /dev/null @@ -1,77 +0,0 @@ -package eu.m724.realweather.commands; - -import java.nio.file.NoSuchFileException; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; - -import eu.m724.jarupdater.updater.Updater; -import eu.m724.jarupdater.object.Version; - -/** - * not actually a command but deserves a separate file - */ -public class UpdateCommand { - private final Updater updater; - - public UpdateCommand(Updater updater) { - this.updater = updater; - } - - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!sender.hasPermission("realweather.admin.update")) return false; - - sender.sendMessage("Please wait"); - CompletableFuture latestFuture = updater.getLatestVersion(); - - if (args.length == 0) { - latestFuture.thenAccept(metadata -> { - if (metadata != null) { - sender.sendMessage("An update is available!"); - sender.sendMessage("RealWeather %s released %s".formatted(metadata.getLabel(), formatDate(metadata.getTimestamp()))); - sender.sendMessage("To download: /rwadmin update download"); - } else { - sender.sendMessage("No new updates"); // TODO color - } - }); - } else { - String action = args[1]; // remember this function is proxied - - if (action.equals("download")) { - sender.sendMessage("Started download"); - - updater.downloadLatestVersion().handle((file, ex) -> { - sender.sendMessage("Download failed. See console for details."); - ex.printStackTrace(); - return null; - }).thenAccept(file -> { - if (file != null) - sender.sendMessage("Download finished, install with /rwadmin update install"); - }); - - } else if (action.equals("install")) { - try { - updater.installLatestVersion().handle((v, ex) -> { - sender.sendMessage("Install failed. See console for details."); - ex.printStackTrace(); - return null; - }).thenAccept(v -> { - sender.sendMessage("Installation completed, restart server to apply"); - }); - } catch (NoSuchFileException e) { - sender.sendMessage("Download the update first"); - } - } else return false; - } - - return true; - } - - private String formatDate(long timestamp) { // TODO move this - return DateTimeFormatter.ofPattern("dd.MM.yyyy").format(Instant.ofEpochSecond(timestamp)); - } - -} diff --git a/src/main/java/eu/m724/realweather/map/CoordinatesLocationConverter.java b/src/main/java/eu/m724/realweather/map/CoordinatesLocationConverter.java new file mode 100644 index 0000000..595b1fd --- /dev/null +++ b/src/main/java/eu/m724/realweather/map/CoordinatesLocationConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.map; + +import org.bukkit.Location; +import org.bukkit.World; + +import eu.m724.wtapi.object.Coordinates; + +public class CoordinatesLocationConverter { + private final double scaleLatitude; + private final double scaleLongitude; + + private final Coordinates staticPoint; + + public CoordinatesLocationConverter(MapConfig config) { + this.scaleLatitude = config.scaleLatitude(); + this.scaleLongitude = config.scaleLongitude(); + this.staticPoint = config.point(); + } + + /** + * Converts a {@link Location} to {@link Coordinates}
+ * The result is scaled and wrapped + * + * @param location the location to convert + * @return the coordinates + */ + public Coordinates locationToCoordinates(Location location) { + // it's <-90, 90> (inclusive), but there's no point to make it that way here, because it's easier, and nice precision is enough + double latitude = (-location.getZ() / scaleLatitude) % 90; + // here it's <-180, 180) so it's correct, and we don't need excuses + double longitude = (location.getX() / scaleLongitude) % 180; + + return new Coordinates(latitude, longitude); + } + + /** + * Converts {@link Coordinates} to a {@link Location}
+ * The result is scaled, but not wrapped to world border or anything + * + * @param world the world of the location + * @param coordinates the coordinates to convert + * @return the location in {@code world} + */ + public Location coordinatesToLocation(World world, Coordinates coordinates) { + double x = coordinates.longitude() * scaleLongitude; + double z = -coordinates.latitude() * scaleLatitude; + + return new Location(world, x, 0, z); + } + + public Coordinates getStaticPoint() { + return staticPoint; + } + + public double getScaleLatitude() { + return scaleLatitude; + } + + public double getScaleLongitude() { + return scaleLongitude; + } +} diff --git a/src/main/java/eu/m724/realweather/map/MapConfig.java b/src/main/java/eu/m724/realweather/map/MapConfig.java new file mode 100644 index 0000000..b203e97 --- /dev/null +++ b/src/main/java/eu/m724/realweather/map/MapConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.map; + +import java.util.List; + +import org.bukkit.configuration.ConfigurationSection; + +import eu.m724.wtapi.object.Coordinates; + +public record MapConfig( + List worldNames, + boolean worldNamesIsBlacklist, + + double scaleLatitude, + double scaleLongitude, + + Coordinates point +) { + public static MapConfig fromConfiguration(ConfigurationSection configuration) { + List worldNames = configuration.getStringList("worlds"); + boolean worldBlacklist = configuration.getBoolean("isBlacklist"); + + double scaleLatitude = configuration.getDouble("dimensions.latitude"); + double scaleLongitude = configuration.getDouble("dimensions.longitude"); + + Coordinates point = new Coordinates( + configuration.getDouble("point.latitude"), + configuration.getDouble("point.longitude") + ); + + + return new MapConfig(worldNames, worldBlacklist, scaleLatitude, scaleLongitude, point); + } +} diff --git a/src/main/java/eu/m724/realweather/map/WorldList.java b/src/main/java/eu/m724/realweather/map/WorldList.java new file mode 100644 index 0000000..bc1983b --- /dev/null +++ b/src/main/java/eu/m724/realweather/map/WorldList.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.map; + +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class WorldList implements Listener { + private final List worldNames; + private final boolean isBlacklist; + + private final List worlds = new ArrayList<>(); + + public WorldList(List worldNames, boolean isBlacklist) { + this.worldNames = worldNames; + this.isBlacklist = isBlacklist; + } + + @EventHandler + void onWorldLoad(WorldLoadEvent e) { + if (worldNames.contains(e.getWorld().getName()) ^ isBlacklist) { + worlds.add(e.getWorld()); + } + } + + @EventHandler + void onWorldUnload(WorldUnloadEvent e) { + worlds.remove(e.getWorld()); + } + + public List getIncludedWorlds() { + return Collections.unmodifiableList(this.worlds); + } + + public boolean isWorldIncluded(World world) { + return worlds.contains(world); + } +} diff --git a/src/main/java/eu/m724/realweather/commands/GeoCommand.java b/src/main/java/eu/m724/realweather/map/command/GeoCommand.java similarity index 69% rename from src/main/java/eu/m724/realweather/commands/GeoCommand.java rename to src/main/java/eu/m724/realweather/map/command/GeoCommand.java index 82fe36c..a45bf75 100644 --- a/src/main/java/eu/m724/realweather/commands/GeoCommand.java +++ b/src/main/java/eu/m724/realweather/map/command/GeoCommand.java @@ -1,4 +1,9 @@ -package eu.m724.realweather.commands; +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.map.command; import org.bukkit.Location; import org.bukkit.command.Command; @@ -6,31 +11,29 @@ import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; import eu.m724.wtapi.object.Coordinates; import net.md_5.bungee.api.ChatColor; public class GeoCommand implements CommandExecutor { - //private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache(); - private final Mapper mapper = GlobalConstants.getMapper(); + private final CoordinatesLocationConverter coordinatesLocationConverter; - @Override + public GeoCommand(CoordinatesLocationConverter coordinatesLocationConverter) { + this.coordinatesLocationConverter = coordinatesLocationConverter; + } + + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Player player = sender instanceof Player ? (Player) sender : null; if (args.length == 0) { if (player != null) { Location location = player.getLocation(); - Coordinates coordinates = mapper.locationToCoordinates(location); - - //Weather weather = playerWeatherCache.getWeather(player); - //String address = formatAddress(weather); + Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(location); colorize(player, ""); colorize(player, "&6Geolocation: &b%f&7, &b%f &7(lat, lon)", coordinates.latitude(), coordinates.longitude()); colorize(player, "&7In-game Position: &3%f&8, &3%f &8(x, z)", location.getX(), location.getZ()); - //colorize(player, "&7City: &3%s", address); colorize(player, ""); } else { sender.sendMessage("You can't run this command without arguments as console"); @@ -49,12 +52,12 @@ public class GeoCommand implements CommandExecutor { } Location location = new Location(null, x, 0, z); - Coordinates coordinates = mapper.locationToCoordinates(location); + Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(location); colorize(sender, ""); - colorize(sender, "&6In-game Position: &b%f&7, &b%f &7(x, z)", location.getX(), location.getZ()); - colorize(sender, "&7Geolocation: &3%f&8, &3%f &8(lat, lon)", coordinates.latitude(), coordinates.longitude()); - colorize(sender, "&7Input interpreted as position, because you separated it with a space"); + colorize(sender, "&6Geolocation: &b%f&8, &b%f &7(lat, lon)", coordinates.latitude(), coordinates.longitude()); + colorize(sender, "&7In-game Position: &3%f&7, &3%f &8(x, z)", location.getX(), location.getZ()); + colorize(sender, "&7Input interpreted as position, space used as separator"); colorize(sender, ""); } else { double latitude, longitude; @@ -69,12 +72,12 @@ public class GeoCommand implements CommandExecutor { } Coordinates coordinates = new Coordinates(latitude, longitude); - Location location = mapper.coordinatesToLocation(null, coordinates); + Location location = coordinatesLocationConverter.coordinatesToLocation(null, coordinates); colorize(sender, ""); colorize(sender, "&6In-game Position: &b%f&7, &b%f &7(x, z)", location.getX(), location.getZ()); colorize(sender, "&7Geolocation: &3%f&8, &3%f &8(lat, lon)", coordinates.latitude(), coordinates.longitude()); - colorize(sender, "&7Input interpreted as geolocation, because you separated with a comma"); + colorize(sender, "&7Input interpreted as geolocation, comma used as separator"); colorize(sender, ""); } diff --git a/src/main/java/eu/m724/realweather/mapper/Mapper.java b/src/main/java/eu/m724/realweather/mapper/Mapper.java deleted file mode 100644 index 01c8fbe..0000000 --- a/src/main/java/eu/m724/realweather/mapper/Mapper.java +++ /dev/null @@ -1,103 +0,0 @@ -package eu.m724.realweather.mapper; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import eu.m724.realweather.Configs; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; - -import eu.m724.wtapi.object.Coordinates; - -public class Mapper { - private final MapperConfig config = Configs.mapperConfig(); - private final List worlds = new ArrayList<>(); - - private final List> worldLoadConsumers = new ArrayList<>(); - private final List> worldUnloadConsumers = new ArrayList<>(); - // TODO game rules, I think I meant handling by this class - - /** - * Registers a consumer which will be called on world load - * @param consumer the consumer which will be called on world load - */ - public void registerWorldLoadConsumer(Consumer consumer) { - this.worldLoadConsumers.add(consumer); - } - - /** - * Registers a consumer which will be called on world unload - * @param consumer the consumer which will be called on world unload - */ - public void registerWorldUnloadConsumer(Consumer consumer) { - this.worldUnloadConsumers.add(consumer); - } - - /** - * Registers events handled by mapper.
- * This should be called once on plugin load. - * @param plugin the plugin to register events under - */ - public void registerEvents(Plugin plugin) { - plugin.getServer().getPluginManager().registerEvents(new MapperEventHandler(this), plugin); - } - - /** - * Converts a {@link Location} to {@link Coordinates}
- * The result is scaled and wrapped - * - * @param location the location to convert - * @return the coordinates - */ - public Coordinates locationToCoordinates(Location location) { - // it's <-90, 90> (inclusive), but there's no point to make it that way here, because it's easier, and nice precision is enough - double latitude = (-location.getZ() / config.scaleLatitude) % 90; - // here it's <-180, 180) so it's correct, and we don't need excuses - double longitude = (location.getX() / config.scaleLongitude) % 180; - - return new Coordinates(latitude, longitude); - } - - /** - * Converts {@link Coordinates} to a {@link Location}
- * The result is scaled, but not wrapped to world border or anything - * - * @param world the world of the location - * @param coordinates the coordinates to convert - * @return the location in {@code world} - */ - public Location coordinatesToLocation(World world, Coordinates coordinates) { - double x = coordinates.longitude() * config.scaleLongitude; - double z = -coordinates.latitude() * config.scaleLatitude; - - return new Location(world, x, 0, z); - } - - public Coordinates getPoint() { - return config.point; - } - - public List getWorlds() { - return this.worlds; - } - - boolean loadWorld(World world) { - boolean loaded = config.worlds.contains(world.getName()) ^ config.worldBlacklist; - - if (loaded) { - worlds.add(world); - worldLoadConsumers.forEach(consumer -> consumer.accept(world)); - } - - return loaded; - } - - void unloadWorld(World world) { - if (worlds.remove(world)) { - worldUnloadConsumers.forEach(consumer -> consumer.accept(world)); - } - } - -} diff --git a/src/main/java/eu/m724/realweather/mapper/MapperConfig.java b/src/main/java/eu/m724/realweather/mapper/MapperConfig.java deleted file mode 100644 index ae0ea6f..0000000 --- a/src/main/java/eu/m724/realweather/mapper/MapperConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package eu.m724.realweather.mapper; - -import java.util.List; - -import org.bukkit.configuration.ConfigurationSection; - -import eu.m724.wtapi.object.Coordinates; - -public class MapperConfig { - public boolean worldBlacklist; - public List worlds; - - public double scaleLatitude; - public double scaleLongitude; - - public Coordinates point; - - public static MapperConfig fromConfiguration(ConfigurationSection configuration) { - MapperConfig mapperConfig = new MapperConfig(); - - mapperConfig.worldBlacklist = configuration.getBoolean("worldBlacklist"); - mapperConfig.worlds = configuration.getStringList("worlds"); - - mapperConfig.scaleLatitude = configuration.getDouble("dimensions.latitude"); - mapperConfig.scaleLongitude = configuration.getDouble("dimensions.longitude"); - - mapperConfig.point = new Coordinates( - configuration.getDouble("point.latitude"), - configuration.getDouble("point.longitude") - ); - - return mapperConfig; - } -} diff --git a/src/main/java/eu/m724/realweather/mapper/MapperEventHandler.java b/src/main/java/eu/m724/realweather/mapper/MapperEventHandler.java deleted file mode 100644 index e2c1892..0000000 --- a/src/main/java/eu/m724/realweather/mapper/MapperEventHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package eu.m724.realweather.mapper; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldUnloadEvent; - -public class MapperEventHandler implements Listener { - private final Mapper mapper; - - public MapperEventHandler(Mapper mapper) { - this.mapper = mapper; - } - - @EventHandler - public void onWorldLoad(WorldLoadEvent e) { - mapper.loadWorld(e.getWorld()); - } - - @EventHandler - public void onWorldUnload(WorldUnloadEvent e) { - mapper.unloadWorld(e.getWorld()); - } -} diff --git a/src/main/java/eu/m724/realweather/thunder/AsyncLightningStrikeEvent.java b/src/main/java/eu/m724/realweather/thunder/AsyncLightningStrikeEvent.java new file mode 100644 index 0000000..4d06151 --- /dev/null +++ b/src/main/java/eu/m724/realweather/thunder/AsyncLightningStrikeEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.thunder; + +import eu.m724.wtapi.provider.thunder.TimedStrike; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called on a lightning strike. + */ +public class AsyncLightningStrikeEvent extends Event implements Cancellable { + private static final HandlerList HANDLERS = new HandlerList(); + + private final TimedStrike timedStrike; + + private boolean cancelled; + + public AsyncLightningStrikeEvent(TimedStrike timedStrike) { + super(true); + this.timedStrike = timedStrike; + } + + public TimedStrike getTimedStrike() { + return timedStrike; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/main/java/eu/m724/realweather/thunder/LightningListener.java b/src/main/java/eu/m724/realweather/thunder/LightningListener.java new file mode 100644 index 0000000..1773146 --- /dev/null +++ b/src/main/java/eu/m724/realweather/thunder/LightningListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.thunder; + +import eu.m724.realweather.DebugLogger; +import eu.m724.realweather.map.CoordinatesLocationConverter; +import eu.m724.realweather.map.WorldList; +import eu.m724.wtapi.provider.thunder.TimedStrike; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +public class LightningListener implements Listener { + private final CoordinatesLocationConverter coordinatesLocationConverter; + private final WorldList worldList; + + public LightningListener(CoordinatesLocationConverter coordinatesLocationConverter, WorldList worldList) { + this.coordinatesLocationConverter = coordinatesLocationConverter; + this.worldList = worldList; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onLightningStrike(AsyncLightningStrikeEvent event) { + if (event.isCancelled()) return; + + TimedStrike strike = event.getTimedStrike(); + + DebugLogger.finer("Strike: %f %f", strike.coordinates().latitude(), strike.coordinates().longitude()); + + worldList.getIncludedWorlds().forEach(w -> { + Location location = coordinatesLocationConverter.coordinatesToLocation(w, strike.coordinates()); + DebugLogger.finer("In %s that converts to: %d %d", w.getName(), location.getBlockX(), location.getBlockZ()); + + // World#isLoaded, Chunk#isLoaded and probably others using Chunk, load the chunk and always return true + if (w.isChunkLoaded(location.getBlockX() / 16, location.getBlockZ() / 16)) { + location.setY(w.getHighestBlockYAt(location) + 1); + w.strikeLightning(location); + DebugLogger.finer("Spawned lightning in %s on y level %d", w.getName(), location.getBlockY()); + } + }); + } +} diff --git a/src/main/java/eu/m724/realweather/thunder/StrikeTask.java b/src/main/java/eu/m724/realweather/thunder/StrikeTask.java deleted file mode 100644 index 6e14e2c..0000000 --- a/src/main/java/eu/m724/realweather/thunder/StrikeTask.java +++ /dev/null @@ -1,34 +0,0 @@ -package eu.m724.realweather.thunder; - -import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; -import eu.m724.wtapi.provider.thunder.TimedStrike; -import org.bukkit.Location; -import org.bukkit.scheduler.BukkitRunnable; - -public class StrikeTask extends BukkitRunnable { - private final Mapper mapper = GlobalConstants.getMapper(); - private final TimedStrike strike; - - public StrikeTask(TimedStrike strike) { - this.strike = strike; - } - - @Override - public void run() { - DebugLogger.info("strike: %f %f", 2, strike.coordinates().latitude(), strike.coordinates().longitude()); - - mapper.getWorlds().forEach(w -> { - Location location = mapper.coordinatesToLocation(w, strike.coordinates()); - DebugLogger.info("in %s that converts to: %d %d", 2, w.getName(), location.getBlockX(), location.getBlockZ()); - - // World#isLoaded, Chunk#isLoaded and probably others using Chunk, load the chunk and always return true - if (w.isChunkLoaded(location.getBlockX() / 16, location.getBlockZ() / 16)) { - location.setY(w.getHighestBlockYAt(location) + 1); - w.strikeLightning(location); - DebugLogger.info("spawned lightning in %s on y level %d", 2, w.getName(), location.getBlockY()); - } - }); - } -} diff --git a/src/main/java/eu/m724/realweather/thunder/ThunderConfig.java b/src/main/java/eu/m724/realweather/thunder/ThunderConfig.java index 9fbd146..0393ab1 100644 --- a/src/main/java/eu/m724/realweather/thunder/ThunderConfig.java +++ b/src/main/java/eu/m724/realweather/thunder/ThunderConfig.java @@ -1,23 +1,29 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.thunder; import org.bukkit.configuration.ConfigurationSection; /** + * Configuration of the thunder module * - * @param enabled is thunder module enabled - * @param provider The provider name, may or may not exist, if it doesn't, an error is thrown later - * @param refreshPeriod how often probe for strikes, in ticks + * @param enabled Whether the thunder module is enabled + * @param provider The provider name + * @param apiKey API key for the provider, null if not necessary */ public record ThunderConfig( boolean enabled, String provider, - int refreshPeriod + String apiKey ) { public static ThunderConfig fromConfiguration(ConfigurationSection configuration) { return new ThunderConfig( configuration.getBoolean("enabled"), configuration.getString("provider"), - configuration.getInt("refreshPeriod") + configuration.getString("apiKey") ); } } diff --git a/src/main/java/eu/m724/realweather/thunder/ThunderMaster.java b/src/main/java/eu/m724/realweather/thunder/ThunderMaster.java index ef6ad6a..8a484db 100644 --- a/src/main/java/eu/m724/realweather/thunder/ThunderMaster.java +++ b/src/main/java/eu/m724/realweather/thunder/ThunderMaster.java @@ -1,43 +1,67 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.thunder; -import eu.m724.realweather.Configs; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; -import eu.m724.wtapi.provider.exception.NoSuchProviderException; -import org.bukkit.plugin.Plugin; - import eu.m724.realweather.DebugLogger; +import eu.m724.realweather.RealWeatherPlugin; +import eu.m724.wtapi.provider.exception.NoSuchProviderException; + import eu.m724.wtapi.provider.Providers; import eu.m724.wtapi.provider.exception.ProviderException; import eu.m724.wtapi.provider.thunder.ThunderProvider; public class ThunderMaster { - private final Plugin plugin = GlobalConstants.getPlugin(); - private final ThunderConfig config = Configs.thunderConfig(); - private final Mapper mapper = GlobalConstants.getMapper(); + private final RealWeatherPlugin plugin; + private final String providerName; + private final String apiKey; private ThunderProvider provider; - - /** + + public ThunderMaster(RealWeatherPlugin plugin, ThunderConfig config) { + this.plugin = plugin; + this.providerName = config.provider(); + this.apiKey = config.apiKey(); + } + + /** * initializes, tests and starts + * * @throws ProviderException if provider initialization failed * @throws NoSuchProviderException config issue */ - public void init(Plugin plugin) throws ProviderException, NoSuchProviderException { - if (!config.enabled()) - return; - - provider = Providers.getThunderProvider(config.provider(), null); + public void init() throws ProviderException, NoSuchProviderException { + provider = Providers.getThunderProvider(providerName, apiKey); - provider.registerStrikeConsumer(strike -> new StrikeTask(strike).runTaskLaterAsynchronously(plugin, 0)); + // TODO is this good? Probably not + provider.registerStrikeConsumer(strike -> { + plugin.getServer().getPluginManager().callEvent( + new AsyncLightningStrikeEvent(strike) + ); + }); + + provider.registerEventConsumer(event -> { + DebugLogger.fine("Thunder provider says: %s", event.message()); + + if (event.exception() != null) { + DebugLogger.severe("Thunder provider exception: %s", event.message()); + DebugLogger.severe(" " + event.exception()); + } + }); provider.start(); - DebugLogger.info("thunderprovider started", 3); - - DebugLogger.info("thunder loaded", 1); + + DebugLogger.finer("Done initializing"); } public long getLatency() { return provider.getLatency(); } + + // TODO should this be exposed? + public String getProviderName() { + return providerName; + } } diff --git a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java index e35729b..ce0595e 100644 --- a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java +++ b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java @@ -1,37 +1,45 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.time; +import eu.m724.realweather.map.WorldList; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; import eu.m724.wtapi.object.Coordinates; public class AsyncPlayerTimeTask extends BukkitRunnable { - private final Server server = GlobalConstants.getPlugin().getServer(); - private final Mapper mapper = GlobalConstants.getMapper(); - private final TimeConverter timeConverter; + private final Server server; + private final CoordinatesLocationConverter coordinatesLocationConverter; + private final WorldList worldList; - AsyncPlayerTimeTask(TimeConverter timeConverter) { + AsyncPlayerTimeTask(TimeConverter timeConverter, Server server, CoordinatesLocationConverter coordinatesLocationConverter, WorldList worldList) { this.timeConverter = timeConverter; + this.server = server; + this.coordinatesLocationConverter = coordinatesLocationConverter; + this.worldList = worldList; } @Override public void run() { for (Player player : server.getOnlinePlayers()) { if (!player.hasPermission("realweather.dynamic")) continue; - if (!mapper.getWorlds().contains(player.getWorld())) continue; + if (!worldList.isWorldIncluded(player.getWorld())) continue; - Coordinates coordinates = mapper.locationToCoordinates(player.getLocation()); + Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(player.getLocation()); long time = timeConverter.calculateZoneOffset(coordinates.longitude()); long ticks = timeConverter.millisToTicks(time); player.setPlayerTime(ticks, true); - DebugLogger.info("Time for %s: %d", 2, player.getName(), time); + DebugLogger.finer("Time for %s: %d", player.getName(), time); } } } diff --git a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java index fd6d1a1..b4208d3 100644 --- a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java +++ b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java @@ -1,23 +1,30 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.time; +import eu.m724.realweather.map.WorldList; import org.bukkit.scheduler.BukkitRunnable; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; /** * This does world time, player time is basically offset of this, like timezone +0 */ public class SyncTimeUpdateTask extends BukkitRunnable { - private final Mapper mapper = GlobalConstants.getMapper(); - private final TimeConverter timeConverter; + private final WorldList worldList; + private final long zoneOffset; - SyncTimeUpdateTask(TimeConverter timeConverter, boolean dynamic) { + SyncTimeUpdateTask(TimeConverter timeConverter, WorldList worldList, CoordinatesLocationConverter coordinatesLocationConverter, boolean dynamic) { this.timeConverter = timeConverter; - this.zoneOffset = !dynamic ? timeConverter.calculateZoneOffset(mapper.getPoint().longitude()) : 0; + this.worldList = worldList; + + this.zoneOffset = !dynamic ? timeConverter.calculateZoneOffset(coordinatesLocationConverter.getStaticPoint().longitude()) : 0; } @Override @@ -27,10 +34,9 @@ public class SyncTimeUpdateTask extends BukkitRunnable { long ticks = timeConverter.millisToTicks(time + zoneOffset); - DebugLogger.info("Updating time: %d", 2, ticks); + DebugLogger.finer("Updating world time: %d", ticks); - mapper.getWorlds().forEach(world -> world.setFullTime(ticks)); - // TODO add world handlers to mapper and don't calculate time each run + worldList.getIncludedWorlds().forEach(world -> world.setFullTime(ticks)); + // TODO don't calculate time each run } - } diff --git a/src/main/java/eu/m724/realweather/time/TimeConfig.java b/src/main/java/eu/m724/realweather/time/TimeConfig.java index c88fe5f..ab9eef3 100644 --- a/src/main/java/eu/m724/realweather/time/TimeConfig.java +++ b/src/main/java/eu/m724/realweather/time/TimeConfig.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.time; import org.bukkit.configuration.ConfigurationSection; diff --git a/src/main/java/eu/m724/realweather/time/TimeConverter.java b/src/main/java/eu/m724/realweather/time/TimeConverter.java index 3acb6b6..619f25c 100644 --- a/src/main/java/eu/m724/realweather/time/TimeConverter.java +++ b/src/main/java/eu/m724/realweather/time/TimeConverter.java @@ -1,15 +1,24 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.time; public class TimeConverter { - public final double scale; + private final double scale; public TimeConverter(double scale) { + if (scale <= 0.0) { + throw new IllegalArgumentException("Scale must be greater than 0.0"); + } + this.scale = scale; } /** * Divides time by predefined scale
- * ...slowing it down + * ...slowing it down (or speeding up) * * @param time unix milliseconds * @return scaled unix milliseconds @@ -48,4 +57,8 @@ public class TimeConverter { public long calculateZoneOffset(double longitude) { return (long) (longitude * 240000); } + + public double getScale() { + return scale; + } } \ No newline at end of file diff --git a/src/main/java/eu/m724/realweather/time/TimeMaster.java b/src/main/java/eu/m724/realweather/time/TimeMaster.java index 8edbf97..b1c3a3b 100644 --- a/src/main/java/eu/m724/realweather/time/TimeMaster.java +++ b/src/main/java/eu/m724/realweather/time/TimeMaster.java @@ -1,45 +1,62 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.time; -import eu.m724.realweather.Configs; -import org.bukkit.GameRule; -import org.bukkit.plugin.Plugin; +import eu.m724.realweather.RealWeatherPlugin; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; +import eu.m724.realweather.map.WorldList; public class TimeMaster { - private final Mapper mapper = GlobalConstants.getMapper(); - private final Plugin plugin = GlobalConstants.getPlugin(); - private final TimeConfig timeConfig = Configs.timeConfig(); + private final RealWeatherPlugin plugin; + private final TimeConverter timeConverter; - // TODO I don't want to initialize this here - private final TimeConverter timeConverter = new TimeConverter(timeConfig.scale()); + private final boolean dynamic; + + public TimeMaster(RealWeatherPlugin plugin, TimeConfig timeConfig) { + this.plugin = plugin; + + this.timeConverter = new TimeConverter(timeConfig.scale()); + this.dynamic = timeConfig.dynamic(); + } + + public void init() { + long period = timeConverter.calculateUpdatePeriod(); + DebugLogger.fine("Updates every %d ticks", period); + + if (timeConverter.getScale() * period != 72.0) { + // TODO does it matter in practice? + DebugLogger.warning("Time scale is not optimal. Time might be inaccurate or choppy."); + } + + WorldList worldList = plugin.getWorldList(); + CoordinatesLocationConverter coordinatesLocationConverter = plugin.getCoordinatesLocationConverter(); + + new SyncTimeUpdateTask(timeConverter, worldList, coordinatesLocationConverter, dynamic) + .runTaskTimer(plugin, 0, period); + + if (dynamic) { + // Not period because this is offset + new AsyncPlayerTimeTask(timeConverter, plugin.getServer(), coordinatesLocationConverter, worldList) + .runTaskTimerAsynchronously(plugin, 0, 5 * 20); // 5 seconds + } + + // TODO replace that + // coordinatesLocationConverter.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)); + // coordinatesLocationConverter.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, true)); + + DebugLogger.finer("Done initializing"); + } - // TODO this is only used once public TimeConverter getTimeConverter() { return timeConverter; } - - public void init() { - if (!timeConfig.enabled()) - return; - - long period = timeConverter.calculateUpdatePeriod(); - - if (timeConfig.scale() * period != 72.0) { - // TODO log this properly - DebugLogger.info("Warning: RealTime scale is not optimal. Time will be out of sync.", 0); - } - - new SyncTimeUpdateTask(timeConverter, timeConfig.dynamic()).runTaskTimer(plugin, 0, period); - - if (timeConfig.dynamic()) - new AsyncPlayerTimeTask(timeConverter).runTaskTimerAsynchronously(plugin, 0, 60); // 5 seconds - - mapper.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)); - mapper.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, true)); - - DebugLogger.info("time loaded, update every %d ticks", 1, period); + + public boolean isDynamic() { + return dynamic; } } diff --git a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java b/src/main/java/eu/m724/realweather/time/command/LocalTimeCommand.java similarity index 73% rename from src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java rename to src/main/java/eu/m724/realweather/time/command/LocalTimeCommand.java index 7c894f5..522d1f5 100644 --- a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java +++ b/src/main/java/eu/m724/realweather/time/command/LocalTimeCommand.java @@ -1,29 +1,34 @@ -package eu.m724.realweather.commands; +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.time.command; import java.time.Duration; -import eu.m724.realweather.Configs; +import eu.m724.realweather.map.CoordinatesLocationConverter; import eu.m724.realweather.time.TimeConverter; +import eu.m724.realweather.time.TimeMaster; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; -import eu.m724.realweather.time.TimeConfig; import eu.m724.wtapi.object.Coordinates; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ComponentBuilder; public class LocalTimeCommand implements CommandExecutor { - private final Mapper mapper = GlobalConstants.getMapper(); - private final TimeConfig timeConfig = Configs.timeConfig(); + private final TimeMaster timeMaster; private final TimeConverter timeConverter; + private final CoordinatesLocationConverter coordinatesLocationConverter; - public LocalTimeCommand(TimeConverter timeConverter) { - this.timeConverter = timeConverter; + public LocalTimeCommand(TimeMaster timeMaster, CoordinatesLocationConverter coordinatesLocationConverter) { + this.coordinatesLocationConverter = coordinatesLocationConverter; + this.timeMaster = timeMaster; + this.timeConverter = timeMaster.getTimeConverter(); } @Override @@ -46,8 +51,8 @@ public class LocalTimeCommand implements CommandExecutor { sender.spigot().sendMessage(component); - if (timeConfig.dynamic() && player != null && player.hasPermission("realweather.dynamic")) { - Coordinates coordinates = mapper.locationToCoordinates(player.getLocation()); + if (timeMaster.isDynamic() && player != null && player.hasPermission("realweather.dynamic")) { + Coordinates coordinates = coordinatesLocationConverter.locationToCoordinates(player.getLocation()); long offsetTime = timeConverter.calculateZoneOffset(coordinates.longitude()); long offsetTimeTicks = timeConverter.millisToTicks(offsetTime); diff --git a/src/main/java/eu/m724/realweather/updater/PluginUpdater.java b/src/main/java/eu/m724/realweather/updater/PluginUpdater.java index 7108a9b..3ae76ff 100644 --- a/src/main/java/eu/m724/realweather/updater/PluginUpdater.java +++ b/src/main/java/eu/m724/realweather/updater/PluginUpdater.java @@ -1,11 +1,14 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.updater; import eu.m724.jarupdater.environment.ConstantEnvironment; import eu.m724.jarupdater.updater.Updater; import eu.m724.jarupdater.verify.SignatureVerifier; import eu.m724.jarupdater.verify.Verifier; -import eu.m724.realweather.Configs; -import org.bukkit.plugin.Plugin; import eu.m724.jarupdater.download.Downloader; import eu.m724.jarupdater.download.SimpleDownloader; @@ -13,25 +16,23 @@ import eu.m724.jarupdater.environment.Environment; import eu.m724.jarupdater.live.GiteaMetadataDAO; import eu.m724.jarupdater.live.MetadataDAO; import eu.m724.jarupdater.live.MetadataFacade; +import eu.m724.realweather.RealWeatherPlugin; import java.io.File; import java.io.IOException; import java.io.InputStream; public class PluginUpdater extends Updater { - private final UpdaterConfig updaterConfig = Configs.updaterConfig(); - - final Plugin plugin; - - PluginUpdater(Plugin plugin, Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier) { + private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier) { super(environment, metadataProvider, downloader, verifier); - this.plugin = plugin; } - - public static PluginUpdater build(Plugin plugin, File file) { + + public static PluginUpdater build(File file, String channel) { + RealWeatherPlugin plugin = RealWeatherPlugin.getInstance(); + Environment environment = new ConstantEnvironment( plugin.getDescription().getVersion(), - Configs.updaterConfig().channel(), + channel, file.toPath() ); @@ -46,13 +47,6 @@ public class PluginUpdater extends Updater { throw new RuntimeException(e); } - return new PluginUpdater(plugin, environment, metadataFacade, downloader, verifier); - } - - public void init() { - if (!updaterConfig.alert()) return; - - UpdateNotifier updateNotifier = new UpdateNotifier(this, (version) -> {}); - updateNotifier.register(); + return new PluginUpdater(environment, metadataFacade, downloader, verifier); } } diff --git a/src/main/java/eu/m724/realweather/updater/UpdateNotifier.java b/src/main/java/eu/m724/realweather/updater/UpdateNotifier.java index ed9bc23..69f44a2 100644 --- a/src/main/java/eu/m724/realweather/updater/UpdateNotifier.java +++ b/src/main/java/eu/m724/realweather/updater/UpdateNotifier.java @@ -1,8 +1,14 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.updater; import java.util.concurrent.CompletionException; -import java.util.function.Consumer; +import eu.m724.realweather.RealWeatherPlugin; +import net.md_5.bungee.api.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -14,55 +20,51 @@ import eu.m724.jarupdater.object.Version; import eu.m724.realweather.DebugLogger; public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO move this to jarupdater + private final Plugin plugin = RealWeatherPlugin.getInstance(); + private final PluginUpdater updater; - private final Consumer updateConsumer; - private final Plugin plugin; private Version latestVersion; - public UpdateNotifier(PluginUpdater updater, Consumer updateConsumer) { + public UpdateNotifier(PluginUpdater updater) { this.updater = updater; - this.updateConsumer = updateConsumer; - this.plugin = updater.plugin; } public void register() { - this.runTaskTimerAsynchronously(updater.plugin, 0, 432000); // 6h + this.runTaskTimerAsynchronously(plugin, 0, 6 * 3600 * 20); // 6h plugin.getServer().getPluginManager().registerEvents(this, plugin); } @Override public void run() { - DebugLogger.info("update task running", 2); + DebugLogger.finer("Updater running"); try { latestVersion = updater.getLatestVersion().join(); } catch (CompletionException e) { - Throwable ex = e.getCause(); - DebugLogger.info("Error trying to contact update server: %s", 0, ex.getMessage()); - - if (DebugLogger.getDebugLevel() >= 1) - e.printStackTrace(); + DebugLogger.warning("Error trying to contact update server:"); + DebugLogger.warning(" %s", e.getCause().toString()); } - if (latestVersion == null) return; - DebugLogger.info("RealWeather is outdated. /rwadmin update", 0); + if (latestVersion == null) { + DebugLogger.fine("Plugin is up to date"); + return; + } + + DebugLogger.warning("RealWeather is outdated. /rwadmin update"); - for (Player player : updater.plugin.getServer().getOnlinePlayers()) { + for (Player player : plugin.getServer().getOnlinePlayers()) { if (player.hasPermission("realweather.update.notify")) { - player.sendMessage("RealWeather is outdated. /rwadmin update"); + player.sendMessage(ChatColor.GOLD + "RealWeather is outdated. /rwadmin update"); } } - - updateConsumer.accept(latestVersion); - } @EventHandler public void onPlayerJoin(PlayerJoinEvent e) { Player player = e.getPlayer(); if (latestVersion != null && player.hasPermission("realweather.update.notify")) { - player.sendMessage("RealWeather is outdated. /rwadmin update"); + player.sendMessage(ChatColor.GOLD + "RealWeather is outdated. /rwadmin update"); } } } diff --git a/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java b/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java deleted file mode 100644 index 19a4d3d..0000000 --- a/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package eu.m724.realweather.updater; - -import org.bukkit.configuration.ConfigurationSection; - -/** - * - * @param alert alert admins about updates - * @param channel update channel - */ -public record UpdaterConfig( - boolean alert, // this is different because I can't use notify in records sadly - String channel -) { - public static UpdaterConfig fromConfiguration(ConfigurationSection configuration) { - return new UpdaterConfig( - configuration.getBoolean("notify"), - configuration.getString("channel") - ); - } -} diff --git a/src/main/java/eu/m724/realweather/updater/command/UpdateCommand.java b/src/main/java/eu/m724/realweather/updater/command/UpdateCommand.java new file mode 100644 index 0000000..8607730 --- /dev/null +++ b/src/main/java/eu/m724/realweather/updater/command/UpdateCommand.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.updater.command; + +import java.nio.file.NoSuchFileException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import eu.m724.realweather.DebugLogger; +import eu.m724.realweather.updater.PluginUpdater; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.command.CommandSender; + +import org.bukkit.entity.Player; + +public class UpdateCommand { + private final PluginUpdater updater; + + private boolean updatePending = false; + + public UpdateCommand(PluginUpdater updater) { + this.updater = updater; + } + + private void sendChangelogMessage(CommandSender sender, String changelogUrl) { + if (changelogUrl != null) { + if (sender instanceof Player) { + TextComponent textComponent = new TextComponent("Click here to open changelog"); + textComponent.setUnderlined(true); + textComponent.setColor(ChatColor.AQUA); + textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(changelogUrl))); + textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, changelogUrl)); + sender.spigot().sendMessage(textComponent); + } else { + sender.sendMessage("Changelog: " + changelogUrl); + } + } + } + + public void updateCommand(CommandSender sender, String[] args) { + sender.sendMessage(ChatColor.GRAY + "Please wait..."); + sender.sendMessage(ChatColor.GRAY + "Channel: " + updater.getEnvironment().getChannel()); + + if (updatePending) { + sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "(!) Server restart required"); + } + + String action = args.length > 1 ? args[1] : null; // remember this function is proxied + + if (action == null) { + updater.getLatestVersion().thenAccept(metadata -> { + updater.getCurrentVersion().thenAccept(metadata2 -> { + sender.sendMessage(ChatColor.GOLD + "You're on RealWeather" + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp())); + sendChangelogMessage(sender, metadata2.getChangelogUrl()); + }).exceptionally(e -> { + sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details."); + DebugLogger.severe("Error retrieving information about current version:"); + DebugLogger.severe(" " + e); + return null; + }); + + if (metadata != null) { + sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "An update is available!"); + sender.sendMessage(ChatColor.AQUA + "RealWeather " + metadata.getLabel() + ChatColor.GOLD + " released " + formatDate(metadata.getTimestamp())); + sendChangelogMessage(sender, metadata.getChangelogUrl()); + sender.sendMessage(ChatColor.GOLD + "To download: /rwadmin update download"); + } else { + sender.sendMessage(ChatColor.GRAY + "No new updates"); + } + }).exceptionally(e -> { + sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details."); + DebugLogger.severe("Error checking for update:"); + DebugLogger.severe(" " + e); + return null; + }); + } else { + if (action.equals("download")) { + sender.sendMessage(ChatColor.GRAY + "Started download"); + + updater.downloadLatestVersion().thenAccept(file -> { + sender.sendMessage(ChatColor.GREEN + "Download finished, install with /rwadmin update install"); // TODO make this clickable + }).exceptionally(e -> { + sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details."); + DebugLogger.severe("Error downloading update:"); + DebugLogger.severe(" " + e); + return null; + }); + } else if (action.equals("install")) { + try { + updater.installLatestVersion().thenAccept(v -> { + sender.sendMessage(ChatColor.GREEN + "Installation completed, restart server to apply."); + updatePending = true; + }).exceptionally(e -> { + sender.sendMessage(ChatColor.RED + "An error has occurred, see console for details."); + DebugLogger.severe("Error installing update:"); + DebugLogger.severe(" " + e); + return null; + }); + } catch (NoSuchFileException e) { + sender.sendMessage(ChatColor.YELLOW + "Download the update first: /rwadmin update download"); + } + } + } + } + + private String formatDate(long timestamp) { + return LocalDate.ofEpochDay(timestamp / 86400).format(DateTimeFormatter.ofPattern("dd.MM.yyyy")); + } + +} \ No newline at end of file diff --git a/src/main/java/eu/m724/realweather/weather/DynamicWeatherRetriever.java b/src/main/java/eu/m724/realweather/weather/DynamicWeatherRetriever.java index e84284f..c0ca7cc 100644 --- a/src/main/java/eu/m724/realweather/weather/DynamicWeatherRetriever.java +++ b/src/main/java/eu/m724/realweather/weather/DynamicWeatherRetriever.java @@ -1,123 +1,87 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.weather; +import java.time.Duration; +import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.*; import java.util.concurrent.CompletableFuture; +import eu.m724.realweather.RealWeatherPlugin; import eu.m724.realweather.api.weather.AsyncPlayerWeatherUpdateEvent; import eu.m724.wtapi.provider.weather.WeatherQueryResult; -import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; import eu.m724.wtapi.object.Coordinates; import eu.m724.wtapi.object.Weather; import eu.m724.wtapi.provider.weather.WeatherProvider; -public class DynamicWeatherRetriever extends BukkitRunnable implements Listener { +public class DynamicWeatherRetriever extends BukkitRunnable { + private final RealWeatherPlugin plugin; private final WeatherProvider weatherProvider; + private final PlayerWeatherStore playerWeatherStore; - private final Mapper mapper = GlobalConstants.getMapper(); - private final Plugin plugin = GlobalConstants.getPlugin(); - private final Server server = plugin.getServer(); - private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache(); - - // when to next update all players - private long nextUpdate = 0; - // players that need update asap - private final Set neededUpdate = new HashSet<>(); + private final CoordinatesLocationConverter coordinatesLocationConverter; + private final List worlds; - public DynamicWeatherRetriever(WeatherProvider weatherProvider) { + public DynamicWeatherRetriever(RealWeatherPlugin plugin, WeatherProvider weatherProvider, PlayerWeatherStore playerWeatherStore) { + this.plugin = plugin; this.weatherProvider = weatherProvider; - } + this.playerWeatherStore = playerWeatherStore; - private record CoordinatesResult( - Map coordinatesPlayersMap, - int coordinatesCount, - int playerCount - ) {} - - private CoordinatesResult makeCoordinates(Collection players) { - Map map = new HashMap<>(); - int coordinatesCount = 0; - int playerCount = 0; - - long now = System.currentTimeMillis(); - - for (Player player : players) { - if (!player.hasPermission("realweather.dynamic")) continue; - if (!mapper.getWorlds().contains(player.getWorld())) continue; - - Long lastUpdate = playerWeatherCache.getLastUpdate(player); - if (lastUpdate != null && now - lastUpdate < 10000) continue; - - Coordinates coordinates = mapper.locationToCoordinates(player.getLocation()); - Coordinates closestCoordinates = null; - - for (Coordinates potential : map.keySet()) { - //double distance = Math.sqrt(Math.pow(potential.latitude - coordinates.latitude, 2) + Math.pow(potential.longitude - potential.latitude, 2)); - // TODO setup for "bundling" that is one request for close players - } - - if (closestCoordinates != null) { - Player[] oldPlayerArray = map.get(coordinates); - Player[] newPlayerArray = Arrays.copyOf(oldPlayerArray, oldPlayerArray.length + 1); - newPlayerArray[oldPlayerArray.length] = player; - map.put(coordinates, newPlayerArray); - } else { - map.put(coordinates, new Player[] { player }); - coordinatesCount++; - } - - playerCount++; - } - - return new CoordinatesResult(map, coordinatesCount, playerCount); + this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter(); + this.worlds = plugin.getWorldList().getIncludedWorlds(); } @Override public void run() { - DebugLogger.info("Weather retrieval", 3); - long now = System.currentTimeMillis(); + DebugLogger.finer("Updating weather"); + LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC); - CoordinatesResult coordinates; - if (now > nextUpdate) { - coordinates = makeCoordinates(server.getOnlinePlayers()); - // calculate acceptable request rate based on weather provider quota and active players - float hourly = (float) weatherProvider.getQuota().getHourlyQuota() / coordinates.coordinatesCount(); - nextUpdate = now + Math.max(60000, (long) (3600000 / hourly)); - DebugLogger.info("Next update in %d", 3, nextUpdate); - } else { // immediate update for those that need it right now - if (neededUpdate.isEmpty()) return; - DebugLogger.info("Players in need of update: %d", 2, neededUpdate.size()); - coordinates = makeCoordinates(neededUpdate); - neededUpdate.clear(); - } - Coordinates[] coordinatesArray = coordinates.coordinatesPlayersMap().keySet().toArray(Coordinates[]::new); + float maxHourlyUpdates = (float) weatherProvider.getQuota().getHourlyQuota() / plugin.getServer().getOnlinePlayers().size(); + long updateDelay = Math.max(60, (long) (3600 / maxHourlyUpdates)); - if (coordinatesArray.length == 0) { - DebugLogger.info("nothing to update, dynamic retriever done", 3); + DebugLogger.finer("Update delay: %d seconds", updateDelay); + + Player[] playersToUpdate = plugin.getServer().getOnlinePlayers().stream().filter(player -> { + LocalDateTime lastUpdate = playerWeatherStore.getLastUpdate(player); + + if (!player.hasPermission("realweather.dynamic")) return false; + if (!worlds.contains(player.getWorld())) return false; + + if (lastUpdate == null) return true; + DebugLogger.finer("Player %s's last update: %s", player.getName(), lastUpdate.toString()); + if (Duration.between(lastUpdate, now).getSeconds() > updateDelay) return true; + // TODO also by distance + + return false; + }).toArray(Player[]::new); + + if (playersToUpdate.length == 0) { + DebugLogger.finer("Nobody needs updating"); return; } + Coordinates[] coordinatesArray = Arrays.stream(playersToUpdate).map(player -> + coordinatesLocationConverter.locationToCoordinates(player.getLocation()) + ).toArray(Coordinates[]::new); + CompletableFuture weathersFuture = weatherProvider.getWeather(coordinatesArray); WeatherQueryResult result = weathersFuture.join(); if (result.exception() != null) { - DebugLogger.info("An error occurred trying to retrieve weather data", 0); - - if (DebugLogger.getDebugLevel() > 0) { - result.exception().printStackTrace(); - } + DebugLogger.severe("An error has occurred retrieving weather data"); + DebugLogger.warning(" " + result.exception()); return; } @@ -126,22 +90,16 @@ public class DynamicWeatherRetriever extends BukkitRunnable implements Listener for (int i=0; i weathers = new HashMap<>(); - HashMap lastUpdates = new HashMap<>(); - - void put(Player player, Weather weather, Long lastUpdate) { - weathers.put(player, weather); - lastUpdates.put(player, lastUpdate); - } - - public Weather getWeather(Player player) { - return weathers.get(player); - } - - public Long getLastUpdate(Player player) { - return lastUpdates.get(player); - } -} diff --git a/src/main/java/eu/m724/realweather/weather/PlayerWeatherStore.java b/src/main/java/eu/m724/realweather/weather/PlayerWeatherStore.java new file mode 100644 index 0000000..4445cc5 --- /dev/null +++ b/src/main/java/eu/m724/realweather/weather/PlayerWeatherStore.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + +package eu.m724.realweather.weather; + +import java.time.LocalDateTime; +import java.util.HashMap; + +import org.bukkit.entity.Player; + +import eu.m724.wtapi.object.Weather; + +public class PlayerWeatherStore { + private final HashMap weathers = new HashMap<>(); + + void put(Player player, Weather weather) { + weathers.put(player, weather); + } + + public Weather getWeather(Player player) { + return weathers.get(player); + } + + public LocalDateTime getLastUpdate(Player player) { + Weather weather = weathers.get(player); + return weather != null ? weather.timestamp() : null; + } +} diff --git a/src/main/java/eu/m724/realweather/weather/StaticWeatherRetriever.java b/src/main/java/eu/m724/realweather/weather/StaticWeatherRetriever.java index f4f624c..5f82a12 100644 --- a/src/main/java/eu/m724/realweather/weather/StaticWeatherRetriever.java +++ b/src/main/java/eu/m724/realweather/weather/StaticWeatherRetriever.java @@ -1,37 +1,42 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.weather; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; +import eu.m724.realweather.RealWeatherPlugin; import eu.m724.realweather.api.weather.AsyncGlobalWeatherUpdateEvent; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.CoordinatesLocationConverter; import eu.m724.wtapi.object.Coordinates; import eu.m724.wtapi.object.Weather; import eu.m724.wtapi.provider.weather.WeatherProvider; import eu.m724.wtapi.provider.weather.WeatherQueryResult; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; public class StaticWeatherRetriever extends BukkitRunnable { - private final Plugin plugin = GlobalConstants.getPlugin(); - private final Mapper mapper = GlobalConstants.getMapper(); - + private final RealWeatherPlugin plugin; private final WeatherProvider weatherProvider; + private final CoordinatesLocationConverter coordinatesLocationConverter; - public StaticWeatherRetriever(WeatherProvider weatherProvider) { + public StaticWeatherRetriever(RealWeatherPlugin plugin, WeatherProvider weatherProvider) { + this.plugin = plugin; this.weatherProvider = weatherProvider; + this.coordinatesLocationConverter = plugin.getCoordinatesLocationConverter(); } @Override public void run() { - Coordinates point = mapper.getPoint(); + DebugLogger.finer("Updating weather"); + + Coordinates point = coordinatesLocationConverter.getStaticPoint(); WeatherQueryResult result = weatherProvider.getWeather(point).join(); if (result.exception() != null) { - DebugLogger.info("An error occurred trying to retrieve weather data", 0); - - if (DebugLogger.getDebugLevel() > 0) - result.exception().printStackTrace(); + DebugLogger.severe("An error has occurred retrieving weather data"); + DebugLogger.warning(" " + result.exception()); return; } @@ -43,6 +48,6 @@ public class StaticWeatherRetriever extends BukkitRunnable { plugin.getServer().getPluginManager().callEvent(event); - DebugLogger.info("static weather retriever is done", 3); + DebugLogger.fine("Done updating weather"); } } diff --git a/src/main/java/eu/m724/realweather/weather/WeatherChanger.java b/src/main/java/eu/m724/realweather/weather/WeatherChanger.java index 0f2b644..97e603a 100644 --- a/src/main/java/eu/m724/realweather/weather/WeatherChanger.java +++ b/src/main/java/eu/m724/realweather/weather/WeatherChanger.java @@ -1,10 +1,14 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.weather; import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; import eu.m724.realweather.api.weather.AsyncGlobalWeatherUpdateEvent; import eu.m724.realweather.api.weather.AsyncPlayerWeatherUpdateEvent; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.map.WorldList; import eu.m724.wtapi.object.Weather; import org.bukkit.WeatherType; import org.bukkit.entity.Player; @@ -12,26 +16,35 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -// TODO make weather more comprehensive +// TODO make weather more intricate public class WeatherChanger implements Listener { - private final Mapper mapper = GlobalConstants.getMapper(); + private final WorldList worldList; + + public WeatherChanger(WorldList worldList) { + this.worldList = worldList; + } @EventHandler(priority = EventPriority.LOWEST) public void onGlobalWeatherUpdate(AsyncGlobalWeatherUpdateEvent event) { + if (event.isCancelled()) return; + Weather weather = event.getWeather(); - DebugLogger.info("Changing weather static", 3); - mapper.getWorlds().forEach(w -> { - DebugLogger.info("Changing weather static in world %s", 2, w.getName()); + DebugLogger.finer("Changing weather static"); + + worldList.getIncludedWorlds().forEach(w -> { if (weather.thundering()) { + DebugLogger.finer("Changing weather static in world %s to thunder", w.getName()); w.setClearWeatherDuration(0); w.setWeatherDuration(120000); w.setThunderDuration(120000); } else if (weather.raining() || weather.snowing()) { + DebugLogger.finer("Changing weather static in world %s to rain", w.getName()); w.setClearWeatherDuration(0); w.setWeatherDuration(120000); w.setThunderDuration(0); } else { + DebugLogger.finer("Changing weather static in world %s to clear", w.getName()); w.setClearWeatherDuration(120000); w.setWeatherDuration(0); w.setThunderDuration(0); @@ -41,14 +54,16 @@ public class WeatherChanger implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onPlayerWeatherUpdate(AsyncPlayerWeatherUpdateEvent event) { + if (event.isCancelled()) return; + Player player = event.getPlayer(); Weather weather = event.getWeather(); - DebugLogger.info("Changing weather for player %s", 2, player.getName()); - if (weather.thundering() || weather.snowing() || weather.raining()) { + DebugLogger.finer("Changing weather for player %s to downfall", player.getName()); player.setPlayerWeather(WeatherType.DOWNFALL); } else { + DebugLogger.finer("Changing weather for player %s to clear", player.getName()); player.setPlayerWeather(WeatherType.CLEAR); } } diff --git a/src/main/java/eu/m724/realweather/weather/WeatherConfig.java b/src/main/java/eu/m724/realweather/weather/WeatherConfig.java index ffcd497..a76230f 100644 --- a/src/main/java/eu/m724/realweather/weather/WeatherConfig.java +++ b/src/main/java/eu/m724/realweather/weather/WeatherConfig.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.weather; import org.bukkit.configuration.ConfigurationSection; @@ -5,15 +10,15 @@ import org.bukkit.configuration.ConfigurationSection; /** * Configuration of the weather module * - * @param enabled Is weather module enabled - * @param provider The provider name, may or may not exist, if it doesn't, an error is thrown later - * @param apiKey API key for the provider + * @param enabled Whether the weather module is enabled + * @param provider The provider name + * @param apiKey API key for the provider, null if not necessary * @param dynamic dynamic mode, weather is per player or global */ public record WeatherConfig( boolean enabled, String provider, - String apiKey, // TODO don't expose that, I mean it's only used in one place in init + String apiKey, // TODO don't expose that, it's only used in one place in init boolean dynamic ) { public static WeatherConfig fromConfiguration(ConfigurationSection configuration) { diff --git a/src/main/java/eu/m724/realweather/weather/WeatherMaster.java b/src/main/java/eu/m724/realweather/weather/WeatherMaster.java index 13b13f1..d07a269 100644 --- a/src/main/java/eu/m724/realweather/weather/WeatherMaster.java +++ b/src/main/java/eu/m724/realweather/weather/WeatherMaster.java @@ -1,49 +1,74 @@ +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ + package eu.m724.realweather.weather; -import eu.m724.realweather.Configs; -import eu.m724.wtapi.provider.exception.NoSuchProviderException; -import org.bukkit.GameRule; -import org.bukkit.plugin.Plugin; - import eu.m724.realweather.DebugLogger; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.mapper.Mapper; +import eu.m724.realweather.RealWeatherPlugin; +import eu.m724.wtapi.provider.exception.NoSuchProviderException; + import eu.m724.wtapi.provider.Providers; import eu.m724.wtapi.provider.exception.ProviderException; import eu.m724.wtapi.provider.weather.WeatherProvider; public class WeatherMaster { - private final WeatherConfig config = Configs.weatherConfig(); - private final Mapper mapper = GlobalConstants.getMapper(); - - /** + private final RealWeatherPlugin plugin; + private final boolean dynamic; + + private final String providerName; + private final String apiKey; + + private final PlayerWeatherStore playerWeatherStore = new PlayerWeatherStore(); + + public WeatherMaster(RealWeatherPlugin plugin, WeatherConfig weatherConfig) { + this.plugin = plugin; + this.dynamic = weatherConfig.dynamic(); + this.providerName = weatherConfig.provider(); + this.apiKey = weatherConfig.apiKey(); + } + + /** * initializes, tests and starts * @throws ProviderException if provider initialization failed * @throws NoSuchProviderException config issue */ - public void init(Plugin plugin) throws ProviderException, NoSuchProviderException { - if (!config.enabled()) { - DebugLogger.info("weather module is disabled", 1); - return; - } - - WeatherProvider provider = Providers.getWeatherProvider(config.provider(), config.apiKey()); + public void init() throws ProviderException, NoSuchProviderException { + WeatherProvider provider = Providers.getWeatherProvider(providerName, apiKey); provider.init(); - if (config.dynamic()) { - DynamicWeatherRetriever retriever = new DynamicWeatherRetriever(provider); + if (dynamic) { + DebugLogger.finer("Weather is dynamic"); + + DynamicWeatherRetriever retriever = new DynamicWeatherRetriever(plugin, provider, playerWeatherStore); retriever.runTaskTimerAsynchronously(plugin,0, 200); - plugin.getServer().getPluginManager().registerEvents(retriever, plugin); } else { - StaticWeatherRetriever retriever = new StaticWeatherRetriever(provider); + DebugLogger.finer("Weather is static"); + + StaticWeatherRetriever retriever = new StaticWeatherRetriever(plugin, provider); retriever.runTaskTimerAsynchronously(plugin,0, 60000); } - plugin.getServer().getPluginManager().registerEvents(new WeatherChanger(), plugin); - - mapper.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, false)); - mapper.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, true)); - - DebugLogger.info("weather loaded", 1); + plugin.getServer().getPluginManager().registerEvents(new WeatherChanger(plugin.getWorldList()), plugin); + + DebugLogger.finer("Done initializing"); + + // TODO replace that + // coordinatesLocationConverter.registerWorldLoadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, false)); + // coordinatesLocationConverter.registerWorldUnloadConsumer(world -> world.setGameRule(GameRule.DO_WEATHER_CYCLE, true)); + } + + public boolean isDynamic() { + return dynamic; + } + + // TODO should this be exposed? + public String getProviderName() { + return providerName; + } + + public PlayerWeatherStore getPlayerWeatherStore() { + return playerWeatherStore; } } diff --git a/src/main/java/eu/m724/realweather/commands/LocalWeatherCommand.java b/src/main/java/eu/m724/realweather/weather/command/LocalWeatherCommand.java similarity index 65% rename from src/main/java/eu/m724/realweather/commands/LocalWeatherCommand.java rename to src/main/java/eu/m724/realweather/weather/command/LocalWeatherCommand.java index d810c20..c7b4d8c 100644 --- a/src/main/java/eu/m724/realweather/commands/LocalWeatherCommand.java +++ b/src/main/java/eu/m724/realweather/weather/command/LocalWeatherCommand.java @@ -1,35 +1,42 @@ -package eu.m724.realweather.commands; +/* + * Copyright (c) 2025 RealWeather Authors + * RealWeather is licensed under the GNU General Public License. See the LICENSE.md file in the project root for the full license text. + */ -import java.time.Instant; -import java.time.ZoneOffset; +package eu.m724.realweather.weather.command; + +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import eu.m724.realweather.weather.PlayerWeatherStore; import net.md_5.bungee.api.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import eu.m724.realweather.GlobalConstants; -import eu.m724.realweather.weather.PlayerWeatherCache; import eu.m724.wtapi.object.Weather; public class LocalWeatherCommand implements CommandExecutor { - private final PlayerWeatherCache playerWeatherCache = GlobalConstants.getPlayerWeatherCache(); + private final PlayerWeatherStore playerWeatherStore; - @Override + public LocalWeatherCommand(PlayerWeatherStore playerWeatherStore) { + this.playerWeatherStore = playerWeatherStore; + } + + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (!(sender instanceof Player player)) { sender.sendMessage("You must be a player to use this command"); return true; } - Weather weather = playerWeatherCache.getWeather(player); + Weather weather = playerWeatherStore.getWeather(player); if (weather != null) { - long lastUpdate = playerWeatherCache.getLastUpdate(player); + LocalDateTime lastUpdate = playerWeatherStore.getLastUpdate(player); colorize(sender, "&6Weather for: &b%f&7, &b%f &7(lat, lon)\n", weather.coordinates().latitude(), weather.coordinates().longitude()); @@ -48,10 +55,9 @@ public class LocalWeatherCommand implements CommandExecutor { colorize(sender, "&6Cloud cover (cloudiness): &b%.0f&7%%", weather.cloudCoverPercentage() * 100); colorize(sender, "&6Relative humidity: &b%.0f&7%%", weather.relativeHumidityPercentage() * 100); - colorize(sender, "&6Last update: &b%s UTC\n", formatTime(lastUpdate)); - + colorize(sender, "&6Last update: &b%s UTC\n", lastUpdate.format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } else { - colorize(sender, "&6No weather for you, try again in a second"); + colorize(sender, "&6No weather for you yet, try again in a few seconds"); } return true; @@ -60,9 +66,4 @@ public class LocalWeatherCommand implements CommandExecutor { private void colorize(CommandSender sender, String text, Object... format) { sender.sendMessage(ChatColor.translateAlternateColorCodes('&', text.formatted(format))); } - - private String formatTime(long timestamp) { - return DateTimeFormatter.ofPattern("HH:mm:ss").format(Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.UTC)); - } - } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a45d69f..75f7749 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,22 +2,11 @@ ### GENERAL SETTINGS ### ############################ -# Master switch -enabled: false +# Not much to be found here! Explore the other files. updater: # Notify players and console about plugin updates # This also controls automatic checking # You can still update with /rwadmin update # Relevant permission node: realweather.update.notify - notify: true - # stable for stable releases - # testing for latest builds (untested hence the name) - # As there's no release yet, stable will just error - channel: testing - -# 0 - no debug -# 1 - debug loading modules -# 2 - also debug processing conditions -# 3 - also log tasks running, this will spam -debug: 0 \ No newline at end of file + notify: true \ No newline at end of file diff --git a/src/main/resources/map.yml b/src/main/resources/map.yml index b030cc4..dfc8512 100644 --- a/src/main/resources/map.yml +++ b/src/main/resources/map.yml @@ -2,21 +2,20 @@ ### MAP SETTINGS ### ############################ -# true if the list below is a blacklist, false otherwise -worldBlacklist: false worlds: - world +isBlacklist: false dimensions: - # Blocks per 1 deg, can't be decimal - # latitude = -Z, longitude = X (to match with F3) - # The default (111000) assumes 1 block = 1 meter - latitude: 111000 # -9990000 to 9990000 Z - longitude: 111000 # -19980000 to 19980000 X + # Blocks per 1 deg, must be integer + # latitude+ = Z-, longitude+ = X+ (to match with F3) + # The default (111,000) assumes 1 block = 1 meter + latitude: 111_000 # -9,990,000 to 9,990,000 Z. Wraps beyond that + longitude: 111_000 # -19,980,000 to 19,980,000 X # To make it span world border to world border: - # latitude: 333333 - # longitude: 166666 + # latitude: 333_333 + # longitude: 166_666 # For `static` mode point: diff --git a/src/main/resources/modules/thunder.yml b/src/main/resources/modules/thunder.yml index 5293ffc..755e2ce 100644 --- a/src/main/resources/modules/thunder.yml +++ b/src/main/resources/modules/thunder.yml @@ -6,10 +6,7 @@ enabled: false # Currently only blitzortung provider: blitzortung +# No API key needed -# How often should we poll for updates and spawn lightning -# This is a synchronous task -# Exaggerating, if you put it too low you'll have lag, -# But if you put it too high you'll have lag spikes and weird lightning -# In ticks, default 100 is 5 seconds so reduce if lightning seems weird, in my testing even 5 ticks is fine -refreshPeriod: 100 +# Lightning is DYNAMIC. +# Settings are in map.yml diff --git a/src/main/resources/modules/time.yml b/src/main/resources/modules/time.yml index bbc768f..ce6d647 100644 --- a/src/main/resources/modules/time.yml +++ b/src/main/resources/modules/time.yml @@ -2,20 +2,19 @@ ### TIME SETTINGS ### ############################ -# Warning: this removes sleep -# No, it's not a bug. It would de-synchronize, and can you skip time IRL? -# Can you believe that I actually used to consider this a bug? +# Warning: this removes sleep. No, not a bug, as you can't skip time IRL. enabled: false -# How this plugin affects your world: +# How time is applied: # - static (false): time is the same across the world -# - dynamic (true): static + local time for each player, however it's only cosmetic so it will not match mobs spawning etc +# - dynamic (true): local time based on player's location. However, it's only cosmetic, so it will not match mobs spawning, etc. # Settings for both are in map.yml dynamic: true -# x in game day cycles in 1 irl day cycle +# Real days per in-game day # 2.0 - time goes 2x SLOWER # 0.5 - time goes 2x FASTER -# If modified, time will no longer be in sync with real life +# If modified, time will no longer be in sync with real life. Unreal time. +# The division 72 / scale should equal an integer! scale: 1.0 \ No newline at end of file diff --git a/src/main/resources/modules/weather.yml b/src/main/resources/modules/weather.yml index 84a2ed4..94e906f 100644 --- a/src/main/resources/modules/weather.yml +++ b/src/main/resources/modules/weather.yml @@ -13,8 +13,8 @@ enabled: false provider: openmeteo # No API key needed -# How this plugin affects your world: +# How weather is applied: # - static (false): weather is the same across the world -# - dynamic (true): weather is per player, however it's only cosmetic so it will not match mobs spawning etc -# settings for both are in map.yml +# - dynamic (true): weather is based on player's location. However, it's only cosmetic, so it will not match mobs spawning, etc. +# Settings for both are in map.yml dynamic: true \ No newline at end of file