this commit implements local real time,
fixes the time formula,
adds logging,
and more refactoring
This commit is contained in:
Minecon724 2024-01-20 12:03:21 +00:00
parent ab6df79cae
commit 59cfca2168
16 changed files with 231 additions and 127 deletions

13
TODO.md
View file

@ -1,7 +1,14 @@
Milestone: yesterday
- fix bugs
Milestone: 0.5.1
- local maxmind - local maxmind
- account for real sun movement, not just time
- readd metrics - readd metrics
- fix realtime
- cache cleaning - 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 - tests

View file

@ -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);
}
}

View file

@ -22,6 +22,8 @@ public class RW extends JavaPlugin {
saveDefaultConfig(); saveDefaultConfig();
config = getConfig(); config = getConfig();
SubLogger.init(getLogger());
WorldMap.init( WorldMap.init(
config.getConfigurationSection("map") config.getConfigurationSection("map")
); );

View file

@ -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);
}
}

View file

@ -30,7 +30,7 @@ public class WorldMap {
Type type; Type type;
try { try {
type = Type.valueOf(config.getString("type")); type = Type.valueOf(config.getString("type").toUpperCase());
} catch (NullPointerException e) { } catch (NullPointerException e) {
throw new IllegalArgumentException("Invalid type"); throw new IllegalArgumentException("Invalid type");
} }

View file

@ -1,9 +1,7 @@
package pl.minecon724.realweather.map.exceptions; package pl.minecon724.realweather.map.exceptions;
public class GeoIPException extends Exception { public class GeoIPException extends Exception {
public GeoIPException(String message) {
public GeoIPException(String string) { super(message);
super(string);
} }
} }

View file

@ -1,24 +1,26 @@
package pl.minecon724.realweather.realtime; package pl.minecon724.realweather.realtime;
import java.util.List; import java.util.List;
import java.util.logging.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.map.exceptions.GeoIPException; import pl.minecon724.realweather.map.exceptions.GeoIPException;
public class PlayerTimeSyncTask extends BukkitRunnable { public class PlayerTimeSyncTask extends BukkitRunnable {
private WorldMap worldMap = WorldMap.getInstance(); 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; private List<World> worlds;
public PlayerTimeSyncTask(List<World> worlds) { public PlayerTimeSyncTask(double scale, List<World> worlds) {
this.scale = scale;
this.worlds = worlds; this.worlds = worlds;
} }
@ -26,6 +28,7 @@ public class PlayerTimeSyncTask extends BukkitRunnable {
public void run() { public void run() {
for (Player player : Bukkit.getOnlinePlayers()) { for (Player player : Bukkit.getOnlinePlayers()) {
if (!worlds.contains(player.getWorld())) { if (!worlds.contains(player.getWorld())) {
subLogger.info("resetting %s's time as they're in an excluded world", player.getName());
player.resetPlayerTime(); player.resetPlayerTime();
continue; continue;
} }
@ -35,21 +38,37 @@ public class PlayerTimeSyncTask extends BukkitRunnable {
try { try {
coordinates = worldMap.getCoordinates(player); coordinates = worldMap.getCoordinates(player);
} catch (GeoIPException e) { } catch (GeoIPException e) {
logger.warning( subLogger.info("Unable to determine GeoIP for %s (%s)",
String.format("Unable to determine GeoIP for %s (%s)", player.getAddress().getHostString(), e.getMessage());
player.getAddress().getHostString()));
continue; continue;
} }
// longitude /*
// / 15 as 15 degrees is 1 hour * reasoning here:
// * 60 to minutes * earth is divided into timezones each covering 15 deg longitude
// * 60 again to seconds * so first we find how many hours to offset
// * 20 to ticks * a day is 24h (no way)
long offset = (long) (coordinates.longitude / 15 * 72000); * 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);
} }
} }

View file

@ -1,8 +1,12 @@
package pl.minecon724.realweather.realtime; package pl.minecon724.realweather.realtime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.bukkit.GameRule;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -14,17 +18,18 @@ import pl.minecon724.realweather.RW;
import pl.minecon724.realweather.weather.exceptions.DisabledException; import pl.minecon724.realweather.weather.exceptions.DisabledException;
public class RealTimeCommander implements Listener { public class RealTimeCommander implements Listener {
RW plugin; private RW plugin;
List<String> worldNames; private List<String> worldNames;
double scale; private double scale;
ZoneId timezone; private ZoneId timezone;
boolean perPlayer; private boolean perPlayer;
volatile List<World> worlds; private volatile List<World> worlds = new ArrayList<>();
private Map<World, Boolean> savedGamerule = new HashMap<>();
RealTimeTask task; private RealTimeTask task;
PlayerTimeSyncTask playerTimeSyncTask; private PlayerTimeSyncTask playerTimeSyncTask;
public RealTimeCommander(RW plugin) { public RealTimeCommander(RW plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -50,12 +55,16 @@ public class RealTimeCommander implements Listener {
} }
public void start() { 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) { 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) { public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld(); World world = event.getWorld();
if (worldNames.contains(world.getName())) if (worldNames.contains(world.getName())) {
worlds.add(world); worlds.add(world);
savedGamerule.put(world, world.getGameRuleValue(GameRule.DO_DAYLIGHT_CYCLE));
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
}
} }
@EventHandler @EventHandler
@ -72,5 +85,8 @@ public class RealTimeCommander implements Listener {
World world = event.getWorld(); World world = event.getWorld();
worlds.remove(world); worlds.remove(world);
if (savedGamerule.containsKey(world)) {
world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, savedGamerule.remove(world));
}
} }
} }

View file

@ -7,33 +7,37 @@ import java.util.List;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.SubLogger;
public class RealTimeTask extends BukkitRunnable { public class RealTimeTask extends BukkitRunnable {
double timeScale; private SubLogger subLogger = new SubLogger("timer");
double scale;
ZoneId timezone; ZoneId timezone;
List<World> worlds; List<World> worlds;
public RealTimeTask(double timeScale, ZoneId timezone, List<World> worlds) { public RealTimeTask(double scale, ZoneId timezone, List<World> worlds) {
this.timeScale = timeScale; this.scale = scale;
this.timezone = timezone; this.timezone = timezone;
this.worlds = worlds; this.worlds = worlds;
} }
@Override @Override
public void run() { public void run() {
long now = ZonedDateTime.now(timezone).toInstant().getEpochSecond(); double now = ZonedDateTime.now(timezone).toInstant().getEpochSecond();
now *= timeScale; now /= 3600; // to hour
now %= 86400;
// day irl is 86400 secs // explaination in PlayerTimeSyncTask line 47
// in game, its 1200 secs now *= 1000; // reallife s to mc ticks
// to align, 86400 / 1200 = 72 now -= 6000; // 0t is actually 6:00
// then we convert to ticks by multiplying 20 (1s = 20t)
// we subtract 24000 - 18000 = 6000 because 18000 is midnight now *= scale; // scale
double time = (now / 72.0) * 20 - 6000; now %= 24000;
time %= 24000;
long time = (long) now;
for (World w : worlds) { for (World w : worlds) {
w.setFullTime((long)time); w.setFullTime(time);
subLogger.info("Updated time for %s (to %d)", w.getName(), time);
} }
} }
} }

View file

@ -1,14 +1,16 @@
package pl.minecon724.realweather.weather; package pl.minecon724.realweather.weather;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitRunnable; 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.Coordinates;
import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.map.WorldMap.Type; import pl.minecon724.realweather.map.WorldMap.Type;
@ -19,28 +21,42 @@ import pl.minecon724.realweather.weather.provider.Provider;
public class GetStateTask extends BukkitRunnable { public class GetStateTask extends BukkitRunnable {
Logger logger = Logger.getLogger("weather scheduler"); private SubLogger subLogger = new SubLogger("weather updater");
Provider provider; private RW plugin;
WorldMap worldMap; private Provider provider;
private WorldMap worldMap;
State storedState; private State storedState;
Map<Player, State> playerStoredState = new HashMap<>(); private Map<Player, State> playerStoredState = new HashMap<>();
PluginManager pluginManager = Bukkit.getPluginManager(); private PluginManager pluginManager = Bukkit.getPluginManager();
public GetStateTask( public GetStateTask(
RW plugin,
Provider provider, Provider provider,
WorldMap worldMap WorldMap worldMap
) { ) {
this.plugin = plugin;
this.provider = provider; this.provider = provider;
this.worldMap = worldMap; this.worldMap = worldMap;
} }
// That's a lot of variables private void callEvent(Player player, State storedState, State state) {
new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
pluginManager.callEvent(
new WeatherSyncEvent(player, storedState, state)
);
}
}.runTask(plugin);
}
@Override
public void run() {
try {
if (worldMap.getType() == Type.POINT) { if (worldMap.getType() == Type.POINT) {
Coordinates coordinates; Coordinates coordinates;
try { try {
@ -49,10 +65,7 @@ public class GetStateTask extends BukkitRunnable {
State state = provider.request_state(coordinates); State state = provider.request_state(coordinates);
if (!state.equals(storedState)) { if (!state.equals(storedState)) {
pluginManager.callEvent( callEvent(null, storedState, state);
new WeatherSyncEvent(null, storedState, state)
);
storedState = state; storedState = state;
} }
@ -63,7 +76,7 @@ public class GetStateTask extends BukkitRunnable {
try { try {
coordinates = worldMap.getCoordinates(player); coordinates = worldMap.getCoordinates(player);
} catch (GeoIPException e) { } catch (GeoIPException e) {
logger.info("GeoIP error for " + player.getName()); subLogger.info("GeoIP error for %s", player.getName());
e.printStackTrace(); e.printStackTrace();
continue; continue;
} }
@ -71,17 +84,17 @@ public class GetStateTask extends BukkitRunnable {
State state = provider.request_state(coordinates); State state = provider.request_state(coordinates);
if (!state.equals(playerStoredState.get(player))) { if (!state.equals(playerStoredState.get(player))) {
pluginManager.callEvent( callEvent(player,
new WeatherSyncEvent(
player,
playerStoredState.get(player), playerStoredState.get(player),
state) state);
);
playerStoredState.put(player, state); playerStoredState.put(player, state);
} }
} }
} }
} catch (IOException e) {
subLogger.info("Error updating: %s", e.getMessage());
}
} }
} }

View file

@ -3,7 +3,6 @@ package pl.minecon724.realweather.weather;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; 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.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.event.world.WorldUnloadEvent;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.weather.WeatherState.State; import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.events.WeatherSyncEvent; import pl.minecon724.realweather.weather.events.WeatherSyncEvent;
public class WeatherChanger implements Listener { public class WeatherChanger implements Listener {
private List<String> worldNames; private List<String> worldNames;
private List<World> worlds = new ArrayList<>(); private List<World> worlds = new ArrayList<>();
private SubLogger subLogger = new SubLogger("weatherchanger");
public WeatherChanger(List<String> worldNames) { public WeatherChanger(List<String> worldNames) {
this.worldNames = worldNames; this.worldNames = worldNames;
@ -25,9 +26,13 @@ public class WeatherChanger implements Listener {
@EventHandler @EventHandler
public void onWorldLoad(WorldLoadEvent event) { public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld(); 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); worlds.add(world);
subLogger.info("World %s has been registered", world.getName());
}
} }
@EventHandler @EventHandler
@ -35,6 +40,7 @@ public class WeatherChanger implements Listener {
World world = event.getWorld(); World world = event.getWorld();
worlds.remove(world); worlds.remove(world);
subLogger.info("World %s unloaded", world.getName());
} }
@EventHandler @EventHandler
@ -42,12 +48,11 @@ public class WeatherChanger implements Listener {
Player player = event.getPlayer(); Player player = event.getPlayer();
State state = event.getState(); State state = event.getState();
if (player != null) { if (player != null) {
player.sendMessage("local: " + state.getCondition().name() + " " subLogger.info("new weather for %s: %s %s", player.getName(), state.getCondition().name(), state.getLevel().name());
+ state.getLevel().name() + " " + state.getSimple().name());
} else { } else {
Bukkit.getServer().broadcastMessage("global: " + state.getCondition().name() + " " subLogger.info("new weather: %s %s", state.getCondition().name(), state.getLevel().name());
+ state.getLevel().name() + " " + state.getSimple().name());
} }
} }

View file

@ -1,25 +1,30 @@
package pl.minecon724.realweather.weather; package pl.minecon724.realweather.weather;
import java.io.IOException;
import java.util.List; import java.util.List;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import pl.minecon724.realweather.RW; 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;
import pl.minecon724.realweather.weather.exceptions.DisabledException; import pl.minecon724.realweather.weather.exceptions.DisabledException;
import pl.minecon724.realweather.weather.provider.Provider; import pl.minecon724.realweather.weather.provider.Provider;
public class WeatherCommander { public class WeatherCommander {
private WorldMap worldMap = WorldMap.getInstance(); private WorldMap worldMap = WorldMap.getInstance();
RW plugin; private RW plugin;
boolean enabled; private boolean enabled;
List<String> worldNames; private List<String> worldNames;
String providerName; private String providerName;
Provider provider; private Provider provider;
ConfigurationSection providerConfig; private ConfigurationSection providerConfig;
GetStateTask getStateTask; private GetStateTask getStateTask;
private SubLogger subLogger = new SubLogger("weather");
public WeatherCommander(RW plugin) { public WeatherCommander(RW plugin) {
this.plugin = plugin; this.plugin = plugin;
@ -52,13 +57,23 @@ public class WeatherCommander {
throw new IllegalArgumentException("Invalid provider: " + providerName); throw new IllegalArgumentException("Invalid provider: " + providerName);
provider.init(); 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( plugin.getServer().getPluginManager().registerEvents(
new WeatherChanger(worldNames), plugin); new WeatherChanger(worldNames), plugin);
subLogger.info("done", new Object[0]);
} }
public void start() { public void start() {
getStateTask = new GetStateTask(provider, worldMap); getStateTask = new GetStateTask(plugin, provider, worldMap);
getStateTask.runTaskTimerAsynchronously(plugin, 0, 1200); getStateTask.runTaskTimerAsynchronously(plugin, 0, 1200);
subLogger.info("started", new Object[0]);
} }
} }

View file

@ -1,12 +1,14 @@
package pl.minecon724.realweather.weather.provider; package pl.minecon724.realweather.weather.provider;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import pl.minecon724.realweather.map.Coordinates; 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(); JSONObject json = new JSONObject();
try { try {
@ -48,10 +50,17 @@ public class OpenWeatherMapProvider implements Provider {
} }
is.close(); is.close();
json = new JSONObject(sb.toString()); 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") int stateId;
try {
stateId = json.getJSONArray("weather")
.getJSONObject(0).getInt("id"); .getJSONObject(0).getInt("id");
} catch (JSONException e) {
throw new IOException("Invalid data from openweathermap");
}
// Here comes the mess // Here comes the mess
Condition condition = Condition.CLEAR; Condition condition = Condition.CLEAR;
@ -150,6 +159,7 @@ public class OpenWeatherMapProvider implements Provider {
level = ConditionLevel.EXTREME; level = ConditionLevel.EXTREME;
} }
} }
State state = new State(condition, level); State state = new State(condition, level);
return state; return state;
} }

View file

@ -1,10 +1,12 @@
package pl.minecon724.realweather.weather.provider; package pl.minecon724.realweather.weather.provider;
import java.io.IOException;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.weather.WeatherState; import pl.minecon724.realweather.weather.WeatherState;
public interface Provider { public interface Provider {
public void init(); public void init();
public WeatherState.State request_state(Coordinates coordinates); public WeatherState.State request_state(Coordinates coordinates) throws IOException;
public String getName(); public String getName();
} }

View file

@ -53,12 +53,11 @@ time:
# "auto" to use server's timezone # "auto" to use server's timezone
# Alternatively choose one of these: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List # 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 # WARNING: it is purely cosmetical
timezone: 'auto' timezone: 'auto'
# x day cycles / 24 hrs # x day cycles / 24 hrs
# basically how many Minecraft days during a real day
scale: 1.0 scale: 1.0
# time based on... time? # time based on... time?

View file

@ -1,6 +1,7 @@
name: RealWeather name: RealWeather
version: ${project.version} version: ${project.version}
api-version: 1.16 api-version: 1.16
load: STARTUP
author: Minecon724 author: Minecon724
main: pl.minecon724.realweather.RW main: pl.minecon724.realweather.RW
libraries: libraries: