From 59cfca216802746637e206ab545555b2eabec5ad Mon Sep 17 00:00:00 2001
From: Minecon724 <39800107+Minecon724@users.noreply.github.com>
Date: Sat, 20 Jan 2024 12:03:21 +0000
Subject: [PATCH] 0.5.0 rc this commit implements local real time, fixes the
 time formula, adds logging, and more refactoring

---
 TODO.md                                       | 13 ++-
 .../minecon724/realweather/ConfigUtils.java   | 16 ---
 .../java/pl/minecon724/realweather/RW.java    |  2 +
 .../pl/minecon724/realweather/SubLogger.java  | 29 ++++++
 .../minecon724/realweather/map/WorldMap.java  |  2 +-
 .../map/exceptions/GeoIPException.java        |  6 +-
 .../realtime/PlayerTimeSyncTask.java          | 45 ++++++---
 .../realtime/RealTimeCommander.java           | 40 +++++---
 .../realweather/realtime/RealTimeTask.java    | 34 ++++---
 .../realweather/weather/GetStateTask.java     | 97 +++++++++++--------
 .../realweather/weather/WeatherChanger.java   | 17 ++--
 .../realweather/weather/WeatherCommander.java | 31 ++++--
 .../provider/OpenWeatherMapProvider.java      | 18 +++-
 .../weather/provider/Provider.java            |  4 +-
 src/main/resources/config.yml                 |  3 +-
 src/main/resources/plugin.yml                 |  1 +
 16 files changed, 231 insertions(+), 127 deletions(-)
 delete mode 100644 src/main/java/pl/minecon724/realweather/ConfigUtils.java
 create mode 100644 src/main/java/pl/minecon724/realweather/SubLogger.java

diff --git a/TODO.md b/TODO.md
index e8af59c..74ccc5e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,7 +1,14 @@
+Milestone: yesterday
+- fix bugs
+
+Milestone: 0.5.1
 - local maxmind
-- account for real sun movement, not just time
 - readd metrics
-- fix realtime
 - cache cleaning
-- prevent packets
+
+Milestone: 0.6.0
+- account for real sun movement, not just time
+
+Milestone: future
+- weather simulator (weather is clientside rn and doesnt have things such as lightning or other effects)
 - tests
\ No newline at end of file
diff --git a/src/main/java/pl/minecon724/realweather/ConfigUtils.java b/src/main/java/pl/minecon724/realweather/ConfigUtils.java
deleted file mode 100644
index 44aea5d..0000000
--- a/src/main/java/pl/minecon724/realweather/ConfigUtils.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package pl.minecon724.realweather;
-
-import net.md_5.bungee.api.ChatColor;
-
-public class ConfigUtils {
-    public static String parsePlaceholders(String key, String value, String[] data) {
-        if (key.equals("messages.actionbarInfo")) {
-            value = value.replaceAll("%weather_full%", data[0] + " " + data[1]).replaceAll("%weather%", data[1]);
-        }
-        return value;
-    }
-
-    public static String color(String str) {
-        return ChatColor.translateAlternateColorCodes('&', str);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/pl/minecon724/realweather/RW.java b/src/main/java/pl/minecon724/realweather/RW.java
index a020b31..42e5b3d 100644
--- a/src/main/java/pl/minecon724/realweather/RW.java
+++ b/src/main/java/pl/minecon724/realweather/RW.java
@@ -22,6 +22,8 @@ public class RW extends JavaPlugin {
 		saveDefaultConfig();
 		config = getConfig();
 
+		SubLogger.init(getLogger());
+
 		WorldMap.init(
 			config.getConfigurationSection("map")
 		);
diff --git a/src/main/java/pl/minecon724/realweather/SubLogger.java b/src/main/java/pl/minecon724/realweather/SubLogger.java
new file mode 100644
index 0000000..8076e3f
--- /dev/null
+++ b/src/main/java/pl/minecon724/realweather/SubLogger.java
@@ -0,0 +1,29 @@
+package pl.minecon724.realweather;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class SubLogger {
+    private static Logger logger;
+    private String name;
+
+    static void init(Logger loger) {
+        logger = loger;
+    }
+
+    public SubLogger(String name) {
+        this.name = name;
+    }
+
+    public void log(Level level, String format, Object... args) {
+        Object[] combinedArgs = new Object[args.length + 1];
+        combinedArgs[0] = name;
+        System.arraycopy(args, 0, combinedArgs, 1, args.length);
+
+        logger.log(level, String.format("[%s] " + format, combinedArgs));
+    }
+
+    public void info(String format, Object... args) {
+        this.log(Level.INFO, format, args);
+    }
+}
diff --git a/src/main/java/pl/minecon724/realweather/map/WorldMap.java b/src/main/java/pl/minecon724/realweather/map/WorldMap.java
index d22b90d..fd4411d 100644
--- a/src/main/java/pl/minecon724/realweather/map/WorldMap.java
+++ b/src/main/java/pl/minecon724/realweather/map/WorldMap.java
@@ -30,7 +30,7 @@ public class WorldMap {
         Type type;
 
         try {
-            type = Type.valueOf(config.getString("type"));
+            type = Type.valueOf(config.getString("type").toUpperCase());
         } catch (NullPointerException e) {
             throw new IllegalArgumentException("Invalid type");
         }
diff --git a/src/main/java/pl/minecon724/realweather/map/exceptions/GeoIPException.java b/src/main/java/pl/minecon724/realweather/map/exceptions/GeoIPException.java
index 33ebb8d..736d751 100644
--- a/src/main/java/pl/minecon724/realweather/map/exceptions/GeoIPException.java
+++ b/src/main/java/pl/minecon724/realweather/map/exceptions/GeoIPException.java
@@ -1,9 +1,7 @@
 package pl.minecon724.realweather.map.exceptions;
 
 public class GeoIPException extends Exception {
-
-    public GeoIPException(String string) {
-        super(string);
+    public GeoIPException(String message) {
+        super(message);
     }
-    
 }
diff --git a/src/main/java/pl/minecon724/realweather/realtime/PlayerTimeSyncTask.java b/src/main/java/pl/minecon724/realweather/realtime/PlayerTimeSyncTask.java
index 394b234..77491ac 100644
--- a/src/main/java/pl/minecon724/realweather/realtime/PlayerTimeSyncTask.java
+++ b/src/main/java/pl/minecon724/realweather/realtime/PlayerTimeSyncTask.java
@@ -1,24 +1,26 @@
 package pl.minecon724.realweather.realtime;
 
 import java.util.List;
-import java.util.logging.Logger;
 
 import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 
+import pl.minecon724.realweather.SubLogger;
 import pl.minecon724.realweather.map.Coordinates;
 import pl.minecon724.realweather.map.WorldMap;
 import pl.minecon724.realweather.map.exceptions.GeoIPException;
 
 public class PlayerTimeSyncTask extends BukkitRunnable {
     private WorldMap worldMap = WorldMap.getInstance();
-    private Logger logger = Logger.getLogger("timezone sync");
+    private SubLogger subLogger = new SubLogger("playertime");
 
+    private double scale;
     private List<World> worlds;
 
-    public PlayerTimeSyncTask(List<World> worlds) {
+    public PlayerTimeSyncTask(double scale, List<World> worlds) {
+        this.scale = scale;
         this.worlds = worlds;
     }
 
@@ -26,6 +28,7 @@ public class PlayerTimeSyncTask extends BukkitRunnable {
     public void run() {
         for (Player player : Bukkit.getOnlinePlayers()) {
             if (!worlds.contains(player.getWorld())) {
+                subLogger.info("resetting %s's time as they're in an excluded world", player.getName());
                 player.resetPlayerTime();
                 continue;
             }
@@ -35,21 +38,37 @@ public class PlayerTimeSyncTask extends BukkitRunnable {
             try {
                 coordinates = worldMap.getCoordinates(player);
             } catch (GeoIPException e) {
-                logger.warning(
-                    String.format("Unable to determine GeoIP for %s (%s)",
-                    player.getAddress().getHostString()));
+                subLogger.info("Unable to determine GeoIP for %s (%s)",
+                    player.getAddress().getHostString(), e.getMessage());
 
                 continue;
             }
 
-            // longitude
-            // / 15 as 15 degrees is 1 hour
-            // * 60 to minutes
-            // * 60 again to seconds
-            // * 20 to ticks
-            long offset = (long) (coordinates.longitude / 15 * 72000);
+            /*
+             * reasoning here:
+             * earth is divided into timezones each covering 15 deg longitude
+             * so first we find how many hours to offset
+             * a day is 24h (no way)
+             * in minecraft its 24000 ticks
+             * so, 1h in ticks: 24000t / 24h = 1000t
+             * seconds in day: 24h * 3600s = 86400s
+             * seconds to ticks: 86400s * 20t = 1728000t
+             * day irl is 1728000t, in minecraft its 24000t
+             * for each minecraft tick, ticks irl: 1728000t / 24000t = 72t
+             * we divide offset by that to sync time
+             * then multiply by scale, thats obvious
+             */
 
-            player.setPlayerTime(offset, true);
+            double offset = coordinates.longitude / 15;
+            offset *= 1000;
+            offset *= scale;
+
+            // why no modulo? because we also modify day
+
+            long time = (long) offset;
+
+            player.setPlayerTime(time, true);
+            subLogger.info("%s's time is now off by %d ticks", player.getName(), time);
         }
     }
     
diff --git a/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java b/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java
index 608fb05..3cb4670 100644
--- a/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java
+++ b/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java
@@ -1,8 +1,12 @@
 package pl.minecon724.realweather.realtime;
 
 import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import org.bukkit.GameRule;
 import org.bukkit.World;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.event.EventHandler;
@@ -14,17 +18,18 @@ import pl.minecon724.realweather.RW;
 import pl.minecon724.realweather.weather.exceptions.DisabledException;
 
 public class RealTimeCommander implements Listener {
-    RW plugin;
+    private RW plugin;
 
-    List<String> worldNames;
-    double scale;
-    ZoneId timezone;
-    boolean perPlayer;
+    private List<String> worldNames;
+    private double scale;
+    private ZoneId timezone;
+    private boolean perPlayer;
 
-    volatile List<World> worlds;
+    private volatile List<World> worlds = new ArrayList<>();
+    private Map<World, Boolean> savedGamerule = new HashMap<>();
 
-    RealTimeTask task;
-    PlayerTimeSyncTask playerTimeSyncTask;
+    private RealTimeTask task;
+    private PlayerTimeSyncTask playerTimeSyncTask;
     
     public RealTimeCommander(RW plugin) {
         this.plugin = plugin;
@@ -50,12 +55,16 @@ public class RealTimeCommander implements Listener {
     }
 
     public void start() {
-        task = new RealTimeTask(scale, timezone, worlds);
+        // to save processing, run only when necessary
+        long period = (long) Math.ceil(72 / scale);
+        period = Math.max(period, 1);
 
-        task.runTaskTimer(plugin, 0, 1);
+        task = new RealTimeTask(scale, timezone, worlds);
+        task.runTaskTimer(plugin, 0, period);
 
         if (perPlayer) {
-            playerTimeSyncTask = new PlayerTimeSyncTask(worlds);
+            playerTimeSyncTask = new PlayerTimeSyncTask(scale, worlds);
+            playerTimeSyncTask.runTaskTimerAsynchronously(plugin, 0, 40);
         }
     }
 
@@ -63,8 +72,12 @@ public class RealTimeCommander implements Listener {
     public void onWorldLoad(WorldLoadEvent event) {
         World world = event.getWorld();
 
-        if (worldNames.contains(world.getName()))
+        if (worldNames.contains(world.getName())) {
             worlds.add(world);
+
+            savedGamerule.put(world, world.getGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE));
+            world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
+        }
     }
 
     @EventHandler
@@ -72,5 +85,8 @@ public class RealTimeCommander implements Listener {
         World world = event.getWorld();
 
         worlds.remove(world);
+        if (savedGamerule.containsKey(world)) {
+            world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, savedGamerule.remove(world));
+        }
     }
 }
diff --git a/src/main/java/pl/minecon724/realweather/realtime/RealTimeTask.java b/src/main/java/pl/minecon724/realweather/realtime/RealTimeTask.java
index 363b46e..36adf1d 100644
--- a/src/main/java/pl/minecon724/realweather/realtime/RealTimeTask.java
+++ b/src/main/java/pl/minecon724/realweather/realtime/RealTimeTask.java
@@ -7,33 +7,37 @@ import java.util.List;
 import org.bukkit.World;
 import org.bukkit.scheduler.BukkitRunnable;
 
+import pl.minecon724.realweather.SubLogger;
+
 public class RealTimeTask extends BukkitRunnable {
-    double timeScale;
+    private SubLogger subLogger = new SubLogger("timer");
+    double scale;
     ZoneId timezone;
     List<World> worlds;
 
-    public RealTimeTask(double timeScale, ZoneId timezone, List<World> worlds) {
-        this.timeScale = timeScale;
+    public RealTimeTask(double scale, ZoneId timezone, List<World> worlds) {
+        this.scale = scale;
         this.timezone = timezone;
         this.worlds = worlds;
     }
 
     @Override
     public void run() {
-        long now = ZonedDateTime.now(timezone).toInstant().getEpochSecond();
-        now *= timeScale;
-        now %= 86400;
+        double now = ZonedDateTime.now(timezone).toInstant().getEpochSecond();
+        now /= 3600; // to hour
 
-        // day irl is 86400 secs
-        // in game, its 1200 secs
-        // to align, 86400 / 1200 = 72
-        // then we convert to ticks by multiplying 20 (1s = 20t)
-        // we subtract 24000 - 18000 = 6000 because 18000 is midnight
-        double time = (now / 72.0) * 20 - 6000;
-        time %= 24000;
+        // explaination in PlayerTimeSyncTask line 47
+        now *= 1000; // reallife s to mc ticks
+        now -= 6000; // 0t is actually 6:00
+
+        now *= scale; // scale
+        now %= 24000;
+
+        long time = (long) now;
 
         for (World w : worlds) {
-            w.setFullTime((long)time);
+            w.setFullTime(time);
+            subLogger.info("Updated time for %s (to %d)", w.getName(), time);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java b/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java
index eeb8568..1cf3e3a 100644
--- a/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java
+++ b/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java
@@ -1,14 +1,16 @@
 package pl.minecon724.realweather.weather;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.logging.Logger;
 
 import org.bukkit.Bukkit;
 import org.bukkit.entity.Player;
 import org.bukkit.plugin.PluginManager;
 import org.bukkit.scheduler.BukkitRunnable;
 
+import pl.minecon724.realweather.RW;
+import pl.minecon724.realweather.SubLogger;
 import pl.minecon724.realweather.map.Coordinates;
 import pl.minecon724.realweather.map.WorldMap;
 import pl.minecon724.realweather.map.WorldMap.Type;
@@ -19,68 +21,79 @@ import pl.minecon724.realweather.weather.provider.Provider;
 
 public class GetStateTask extends BukkitRunnable {
 
-    Logger logger = Logger.getLogger("weather scheduler");
+    private SubLogger subLogger = new SubLogger("weather updater");
 
-    Provider provider;
-    WorldMap worldMap;
+    private RW plugin;
+    private Provider provider;
+    private WorldMap worldMap;
 
-    State storedState;
-    Map<Player, State> playerStoredState = new HashMap<>();
-    PluginManager pluginManager = Bukkit.getPluginManager();
+    private State storedState;
+    private Map<Player, State> playerStoredState = new HashMap<>();
+    private PluginManager pluginManager = Bukkit.getPluginManager();
 
     public GetStateTask(
+        RW plugin,
         Provider provider,
         WorldMap worldMap
     ) {
+        this.plugin = plugin;
         this.provider = provider;
         this.worldMap = worldMap;
     }
 
-    // That's a lot of variables
+    private void callEvent(Player player, State storedState, State state) {
+        new BukkitRunnable() {
+
+            @Override
+            public void run() {
+                pluginManager.callEvent(
+                    new WeatherSyncEvent(player, storedState, state)
+                );
+            }
+            
+        }.runTask(plugin);
+    }
 
     @Override
     public void run() {
-
-        if (worldMap.getType() == Type.POINT) {
-            Coordinates coordinates;
-            try {
-                coordinates = worldMap.getCoordinates(null);
-            } catch (GeoIPException e) { return; }
-            State state = provider.request_state(coordinates);
-            
-            if (!state.equals(storedState)) {
-                pluginManager.callEvent(
-                    new WeatherSyncEvent(null, storedState, state)
-                );
-
-                storedState = state;
-            }
-
-        } else {
-            for (Player player : Bukkit.getOnlinePlayers()) {
+        try {
+            if (worldMap.getType() == Type.POINT) {
                 Coordinates coordinates;
-
                 try {
-                    coordinates = worldMap.getCoordinates(player);
-                } catch (GeoIPException e) {
-                    logger.info("GeoIP error for " + player.getName());
-                    e.printStackTrace();
-                    continue;
-                }
-                
+                    coordinates = worldMap.getCoordinates(null);
+                } catch (GeoIPException e) { return; }
                 State state = provider.request_state(coordinates);
+                
+                if (!state.equals(storedState)) {
+                    callEvent(null, storedState, state);
+                    storedState = state;
+                }
 
-                if (!state.equals(playerStoredState.get(player))) {
-                    pluginManager.callEvent(
-                        new WeatherSyncEvent(
-                            player,
-                            playerStoredState.get(player),
-                            state)
-                    );
+            } else {
+                for (Player player : Bukkit.getOnlinePlayers()) {
+                    Coordinates coordinates;
 
-                    playerStoredState.put(player, state);
+                    try {
+                        coordinates = worldMap.getCoordinates(player);
+                    } catch (GeoIPException e) {
+                        subLogger.info("GeoIP error for %s", player.getName());
+                        e.printStackTrace();
+                        continue;
+                    }
+                    
+                    State state = provider.request_state(coordinates);
+
+                    if (!state.equals(playerStoredState.get(player))) {
+                        callEvent(player,
+                                playerStoredState.get(player),
+                                state);
+
+                        playerStoredState.put(player, state);
+                    }
                 }
             }
+        } catch (IOException e) {
+            subLogger.info("Error updating: %s", e.getMessage());
         }
         
     }
diff --git a/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java b/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java
index 744239f..d0f1a37 100644
--- a/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java
+++ b/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java
@@ -3,7 +3,6 @@ package pl.minecon724.realweather.weather;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
@@ -11,12 +10,14 @@ import org.bukkit.event.Listener;
 import org.bukkit.event.world.WorldLoadEvent;
 import org.bukkit.event.world.WorldUnloadEvent;
 
+import pl.minecon724.realweather.SubLogger;
 import pl.minecon724.realweather.weather.WeatherState.State;
 import pl.minecon724.realweather.weather.events.WeatherSyncEvent;
 
 public class WeatherChanger implements Listener {
     private List<String> worldNames;
     private List<World> worlds = new ArrayList<>();
+    private SubLogger subLogger = new SubLogger("weatherchanger");
 
     public WeatherChanger(List<String> worldNames) {
         this.worldNames = worldNames;
@@ -25,9 +26,13 @@ public class WeatherChanger implements Listener {
     @EventHandler
     public void onWorldLoad(WorldLoadEvent event) {
         World world = event.getWorld();
+        subLogger.info("World %s is loading", world.getName());
 
-        if (worldNames.contains(world.getName()))
+        if (worldNames.contains(world.getName())) {
             worlds.add(world);
+            subLogger.info("World %s has been registered", world.getName());
+
+        }
     }
 
     @EventHandler
@@ -35,6 +40,7 @@ public class WeatherChanger implements Listener {
         World world = event.getWorld();
 
         worlds.remove(world);
+        subLogger.info("World %s unloaded", world.getName());
     }
 
     @EventHandler
@@ -42,12 +48,11 @@ public class WeatherChanger implements Listener {
         Player player = event.getPlayer();
         State state = event.getState();
 
+
         if (player != null) {
-            player.sendMessage("local: " + state.getCondition().name() + " " 
-            + state.getLevel().name() + " " + state.getSimple().name());
+            subLogger.info("new weather for %s: %s %s", player.getName(), state.getCondition().name(), state.getLevel().name());
         } else {
-            Bukkit.getServer().broadcastMessage("global: " + state.getCondition().name() + " " 
-            + state.getLevel().name() + " " + state.getSimple().name());
+            subLogger.info("new weather: %s %s", state.getCondition().name(), state.getLevel().name());
         }
 
     }
diff --git a/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java b/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java
index 6d58e90..f3a7e08 100644
--- a/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java
+++ b/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java
@@ -1,25 +1,30 @@
 package pl.minecon724.realweather.weather;
 
+import java.io.IOException;
 import java.util.List;
 
 import org.bukkit.configuration.ConfigurationSection;
 
 import pl.minecon724.realweather.RW;
+import pl.minecon724.realweather.SubLogger;
+import pl.minecon724.realweather.map.Coordinates;
 import pl.minecon724.realweather.map.WorldMap;
 import pl.minecon724.realweather.weather.exceptions.DisabledException;
 import pl.minecon724.realweather.weather.provider.Provider;
 
 public class WeatherCommander {
     private WorldMap worldMap = WorldMap.getInstance();
-    RW plugin;
+    private RW plugin;
 
-    boolean enabled;
-    List<String> worldNames;
-    String providerName;
-    Provider provider;
-    ConfigurationSection providerConfig;
+    private boolean enabled;
+    private List<String> worldNames;
+    private String providerName;
+    private Provider provider;
+    private ConfigurationSection providerConfig;
 
-    GetStateTask getStateTask;
+    private GetStateTask getStateTask;
+
+    private SubLogger subLogger = new SubLogger("weather");
 
     public WeatherCommander(RW plugin) {
         this.plugin = plugin;
@@ -52,13 +57,23 @@ public class WeatherCommander {
             throw new IllegalArgumentException("Invalid provider: " + providerName);
         provider.init();
 
+        try {
+            provider.request_state(new Coordinates(0, 0));
+        } catch (IOException e) {
+            subLogger.info("Provider test failed, errors may occur", new Object[0]);
+            e.printStackTrace();
+        }
+
         plugin.getServer().getPluginManager().registerEvents(
             new WeatherChanger(worldNames), plugin);
+
+        subLogger.info("done", new Object[0]);
     }
 
     public void start() {
-        getStateTask = new GetStateTask(provider, worldMap);
+        getStateTask = new GetStateTask(plugin, provider, worldMap);
         
         getStateTask.runTaskTimerAsynchronously(plugin, 0, 1200);
+        subLogger.info("started", new Object[0]);
     }
 }
diff --git a/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java b/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java
index 82b9d90..bcb3c1b 100644
--- a/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java
+++ b/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java
@@ -1,12 +1,14 @@
 package pl.minecon724.realweather.weather.provider;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.nio.charset.Charset;
 
+import org.json.JSONException;
 import org.json.JSONObject;
 
 import pl.minecon724.realweather.map.Coordinates;
@@ -30,7 +32,7 @@ public class OpenWeatherMapProvider implements Provider {
 		}
 	}
 
-	public State request_state(Coordinates coordinates) {
+	public State request_state(Coordinates coordinates) throws IOException {
 		JSONObject json = new JSONObject();
 		
 		try {
@@ -48,10 +50,17 @@ public class OpenWeatherMapProvider implements Provider {
 			}
 			is.close();
 			json = new JSONObject(sb.toString());
-		} catch (Exception e) { e.printStackTrace(); }
+		} catch (Exception e) {
+			throw new IOException("Couldn't contact openweathermap");
+		}
 
-		int stateId = json.getJSONArray("weather")
-			.getJSONObject(0).getInt("id");
+		int stateId;
+		try {
+			stateId = json.getJSONArray("weather")
+				.getJSONObject(0).getInt("id");
+		} catch (JSONException e) {
+			throw new IOException("Invalid data from openweathermap");
+		}
 
 		// Here comes the mess
 		Condition condition = Condition.CLEAR;
@@ -150,6 +159,7 @@ public class OpenWeatherMapProvider implements Provider {
 					level = ConditionLevel.EXTREME;
 			}
 		}
+		
 		State state = new State(condition, level);
 		return state;
 	}
diff --git a/src/main/java/pl/minecon724/realweather/weather/provider/Provider.java b/src/main/java/pl/minecon724/realweather/weather/provider/Provider.java
index 91206e9..003cc7d 100644
--- a/src/main/java/pl/minecon724/realweather/weather/provider/Provider.java
+++ b/src/main/java/pl/minecon724/realweather/weather/provider/Provider.java
@@ -1,10 +1,12 @@
 package pl.minecon724.realweather.weather.provider;
 
+import java.io.IOException;
+
 import pl.minecon724.realweather.map.Coordinates;
 import pl.minecon724.realweather.weather.WeatherState;
 
 public interface Provider {
 	public void init();
-	public WeatherState.State request_state(Coordinates coordinates);
+	public WeatherState.State request_state(Coordinates coordinates) throws IOException;
 	public String getName();
 }
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 091921f..243ab27 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -53,12 +53,11 @@ time:
 
   # "auto" to use server's timezone
   # Alternatively choose one of these: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
-  # if "real" each player's timezone is varied based on where they are
-  # config from map.globe is used, also forces virtual
   # WARNING: it is purely cosmetical 
   timezone: 'auto'
 
   # x day cycles / 24 hrs
+  # basically how many Minecraft days during a real day
   scale: 1.0
 
   # time based on... time?
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 1842148..d4d1727 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,6 +1,7 @@
 name: RealWeather
 version: ${project.version}
 api-version: 1.16
+load: STARTUP
 author: Minecon724
 main: pl.minecon724.realweather.RW
 libraries: