diff --git a/pom.xml b/pom.xml index ba3d0a4..c0d7d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ eu.m724 wtapi - 0.8.0 + 0.8.3 eu.m724 @@ -46,6 +46,13 @@ 3.0.3 compile + + + junit + junit + 4.13.2 + test + diff --git a/realweather.iml b/realweather.iml index a589521..7f2ab86 100644 --- a/realweather.iml +++ b/realweather.iml @@ -1,5 +1,10 @@ + + + + + diff --git a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java index f21ab4d..f6e0764 100644 --- a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java +++ b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java @@ -135,7 +135,7 @@ public class RealWeatherPlugin extends JavaPlugin { getCommand("rwadmin").setExecutor(new AdminCommand(updater)); getCommand("geo").setExecutor(new GeoCommand()); - if (GlobalConstants.timeConfig.enabled) + if (GlobalConstants.timeConfig.enabled()) getCommand("localtime").setExecutor(new LocalTimeCommand()); if (GlobalConstants.weatherConfig.enabled) @@ -149,7 +149,7 @@ public class RealWeatherPlugin extends JavaPlugin { GlobalConstants.thunderConfig.enabled ? GlobalConstants.thunderConfig.provider : "off" )); metrics.addCustomChart(new SimplePie("real_time", () -> - GlobalConstants.timeConfig.enabled ? "on" : "off" + GlobalConstants.timeConfig.enabled() ? "on" : "off" )); DebugLogger.info("ended loading", 1); diff --git a/src/main/java/eu/m724/realweather/commands/AdminCommand.java b/src/main/java/eu/m724/realweather/commands/AdminCommand.java index a945dea..75648ef 100644 --- a/src/main/java/eu/m724/realweather/commands/AdminCommand.java +++ b/src/main/java/eu/m724/realweather/commands/AdminCommand.java @@ -63,13 +63,13 @@ public class AdminCommand implements CommandExecutor { } componentBuilder.append("\nTime: ").color(ChatColor.GOLD); - componentBuilder.append(timeConfig.enabled ? enabledComponent : disabledComponent); + componentBuilder.append(timeConfig.enabled() ? enabledComponent : disabledComponent); - if (timeConfig.enabled) { + if (timeConfig.enabled()) { componentBuilder.append(" Scale: ").color(ChatColor.GOLD); - componentBuilder.append(Double.toString(timeConfig.scale) + "\n").color(ChatColor.AQUA); + componentBuilder.append(Double.toString(timeConfig.scale()) + "\n").color(ChatColor.AQUA); - long worldTime = timeConfig.calculateWorldTimeSeconds(); + long worldTime = 0; // TODO timeConfig.calculateWorldTimeSeconds(); Duration worldTimeDuration = Duration.ofSeconds(worldTime); String worldTimeFormatted = String.format("%d:%02d:%02d\n", @@ -81,7 +81,7 @@ public class AdminCommand implements CommandExecutor { componentBuilder.append(worldTimeFormatted).color(ChatColor.AQUA); componentBuilder.append(" Dynamic: ").color(ChatColor.GOLD); - componentBuilder.append(timeConfig.dynamic ? enabledComponent : disabledComponent); + componentBuilder.append(timeConfig.dynamic() ? enabledComponent : disabledComponent); } componentBuilder.append("\nThunder: ").color(ChatColor.GOLD); diff --git a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java b/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java index 358ad27..f0050bd 100644 --- a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java +++ b/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java @@ -23,7 +23,7 @@ public class LocalTimeCommand implements CommandExecutor { public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Player player = sender instanceof Player ? (Player) sender : null; - long worldTime = timeConfig.calculateWorldTimeSeconds(); + long worldTime = 0; // TODO timeConfig.calculateWorldTimeSeconds(); Duration worldTimeDuration = Duration.ofSeconds(worldTime); String worldTimeFormatted = String.format("%d:%02d:%02d", @@ -37,10 +37,10 @@ public class LocalTimeCommand implements CommandExecutor { sender.spigot().sendMessage(component); - if (timeConfig.dynamic && player != null && player.hasPermission("realweather.dynamic")) { + if (timeConfig.dynamic() && player != null && player.hasPermission("realweather.dynamic")) { Coordinates coordinates = mapper.locationToCoordinates(player.getLocation()); - long offsetTime = timeConfig.calculateTimeOffsetSeconds(coordinates.longitude); + long offsetTime = 0; // TODO timeConfig.calculateTimeOffsetSeconds(coordinates.longitude); boolean negative = offsetTime < 0; Duration localTimeDuration = worldTimeDuration.plus(offsetTime, ChronoUnit.SECONDS); diff --git a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java index 740f2c4..43b2f88 100644 --- a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java +++ b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java @@ -10,12 +10,14 @@ import eu.m724.realweather.mapper.Mapper; import eu.m724.wtapi.object.Coordinates; public class AsyncPlayerTimeTask extends BukkitRunnable { - private Server server = GlobalConstants.getPlugin().getServer(); - private Mapper mapper = GlobalConstants.getMapper(); + private final Server server = GlobalConstants.getPlugin().getServer(); + private final Mapper mapper = GlobalConstants.getMapper(); - private TimeConfig timeConfig; + private final TimeConverter timeConverter; + private final TimeConfig timeConfig; - AsyncPlayerTimeTask(TimeConfig timeConfig) { + AsyncPlayerTimeTask(TimeConverter timeConverter, TimeConfig timeConfig) { + this.timeConverter = timeConverter; this.timeConfig = timeConfig; } @@ -24,9 +26,22 @@ public class AsyncPlayerTimeTask extends BukkitRunnable { for (Player player : server.getOnlinePlayers()) { if (!player.hasPermission("realweather.dynamic")) continue; if (!mapper.getWorlds().contains(player.getWorld())) continue; - + Coordinates coordinates = mapper.locationToCoordinates(player.getLocation()); - long offsetTicks = timeConfig.calculateTimeOffsetTicks(coordinates.longitude); + + long time = System.currentTimeMillis(); + time = timeConverter.scale(time); + + if (timeConfig.twilight()) { + time = timeConverter.offsetTwilight(time, mapper.getPoint()); + } + + time = timeConverter.calculateZoneOffset(coordinates.longitude); + long ticks = timeConverter.millisToTicks(time); + + + long offsetTicks = timeConverter.calculateZoneOffset(coordinates.longitude); + offsetTicks = timeConverter.millisToTicks(offsetTicks); player.setPlayerTime(Math.floorMod(offsetTicks, 24000), true); DebugLogger.info("Time for %s: %d", 2, player.getName(), offsetTicks); diff --git a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java index da46348..f9f7cc0 100644 --- a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java +++ b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java @@ -1,5 +1,6 @@ package eu.m724.realweather.time; +import eu.m724.wtapi.object.Coordinates; import org.bukkit.scheduler.BukkitRunnable; import eu.m724.realweather.DebugLogger; @@ -7,17 +8,26 @@ import eu.m724.realweather.GlobalConstants; import eu.m724.realweather.mapper.Mapper; public class SyncTimeUpdateTask extends BukkitRunnable { - private TimeConfig timeConfig; - private Mapper mapper = GlobalConstants.getMapper(); - - SyncTimeUpdateTask(TimeConfig timeConfig) { + private final Mapper mapper = GlobalConstants.getMapper(); + + private final TimeConverter timeConverter; + private final TimeConfig timeConfig; + + SyncTimeUpdateTask(TimeConverter timeConverter, TimeConfig timeConfig) { + this.timeConverter = timeConverter; this.timeConfig = timeConfig; } @Override public void run() { - // TODO double? - long ticks = timeConfig.calculateWorldTimeTicks(); + long time = System.currentTimeMillis(); + time = timeConverter.scale(time); + if (timeConfig.twilight()) { + time = timeConverter.offsetTwilight(time, mapper.getPoint()); + } + + long ticks = timeConverter.millisToTicks(time); + DebugLogger.info("Updating time: %d", 2, ticks); mapper.getWorlds().forEach(world -> world.setFullTime(ticks)); diff --git a/src/main/java/eu/m724/realweather/time/TimeConfig.java b/src/main/java/eu/m724/realweather/time/TimeConfig.java index af8eaaa..49c1e64 100644 --- a/src/main/java/eu/m724/realweather/time/TimeConfig.java +++ b/src/main/java/eu/m724/realweather/time/TimeConfig.java @@ -2,54 +2,23 @@ package eu.m724.realweather.time; import org.bukkit.configuration.ConfigurationSection; -public class TimeConfig { - public boolean enabled; - - // state is per player - public boolean dynamic; - public boolean twilight; - // x day cycles in 1 irl day - public double scale; - +import java.util.UUID; + +public record TimeConfig( + boolean enabled, + + boolean dynamic, + boolean twilight, + + double scale +) { public static TimeConfig fromConfiguration(ConfigurationSection configuration) { - TimeConfig timeConfig = new TimeConfig(); + boolean enabled = configuration.getBoolean("enabled"); + + boolean dynamic = configuration.getBoolean("dynamic"); + boolean twilight = configuration.getBoolean("twilight"); + double scale = configuration.getDouble("scale"); - timeConfig.enabled = configuration.getBoolean("enabled"); - - timeConfig.dynamic = configuration.getBoolean("dynamic"); - timeConfig.twilight = configuration.getBoolean("twilight"); - timeConfig.scale = configuration.getDouble("scale"); - - return timeConfig; - } - - public long calculateWorldTimeSeconds() { - long now = (long) (System.currentTimeMillis() * scale / 1000) % 86400; - - return now; - } - - public long calculateWorldTimeTicks() { - long now = calculateWorldTimeSeconds(); - long ticks = Math.floorMod(now / 72 * 20 - 6000, 24000); - - return ticks; - } - - /** - * this method is mostly for info - * @param longitude - * @return - */ - public long calculateTimeOffsetSeconds(double longitude) { - long offset = (long) ((longitude / 15) * 3600 * scale); - - return offset; - } - - public long calculateTimeOffsetTicks(double longitude) { - long offsetTicks = (long) ((longitude / 15) * 1000 * scale); - - return offsetTicks; + return new TimeConfig(enabled, dynamic, twilight, scale); } } diff --git a/src/main/java/eu/m724/realweather/time/TimeConverter.java b/src/main/java/eu/m724/realweather/time/TimeConverter.java new file mode 100644 index 0000000..17d6c5f --- /dev/null +++ b/src/main/java/eu/m724/realweather/time/TimeConverter.java @@ -0,0 +1,148 @@ +package eu.m724.realweather.time; + +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; +import eu.m724.wtapi.object.TwilightCalendar; +import eu.m724.wtapi.provider.twilight.TwilightTimeCache; +import eu.m724.wtapi.provider.twilight.TwilightTimeProvider; +import eu.m724.wtapi.provider.twilight.impl.approximate.ApproximateTwilightTimeCache; +import eu.m724.wtapi.provider.twilight.impl.approximate.ApproximateTwilightTimeProvider; +import org.checkerframework.checker.units.qual.A; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; + +public class TimeConverter { + public final double scale; + // TODO make this configurable + private final ApproximateTwilightTimeProvider provider = new ApproximateTwilightTimeProvider(); + + public TimeConverter(double scale) { + this.scale = scale; + } + + /** + * Divides time by predefined scale
+ * ...slowing it down + * + * @param time unix milliseconds + * @return scaled unix milliseconds + */ + public long scale(long time) { + return (long) (time / scale); + } + + /** + * Converts unix timestamp to in game ticks + * + * @param time unix millis + * @return in-game ticks + */ + public long millisToTicks(long time) { + return Math.floorMod(time / 3600 - 6000, 24000); + } + + /** + * Calculates how often (or how rarely) we can update time
+ * That is, to not do stuff when we don't have to
+ * In ticks, because it's used with Bukkit scheduler + * + * @return update period IN TICKS + */ + public long calculateUpdatePeriod() { + return (long) (72 / scale); + } + + /** + * Calculates time offset (in millis) for specified longitude + * + * @param longitude the longitude + * @return milliseconds of offset from the center of the world (0) + */ + public long calculateZoneOffset(double longitude) { + return (long) (longitude * 3600000); + } + + // TODO optimize + public long offsetTwilight(long unixTimestampMs, Coordinates coordinates) { + // Constants + final long MILLIS_PER_DAY = 86400000; + final long SCALED_SUNRISE_MS = 6 * 3600000; // 06:00 in milliseconds + final long SCALED_SUNSET_MS = 18 * 3600000; // 18:00 in milliseconds + final long SCALED_DAY_LENGTH_MS = SCALED_SUNSET_MS - SCALED_SUNRISE_MS; + + // Convert Unix timestamp to Instant and LocalDate + Instant instant = Instant.ofEpochMilli(unixTimestampMs); + LocalDate date = LocalDate.ofInstant(instant, ZoneOffset.UTC); + + // Get twilight information for the date + TwilightCalendar calendar = provider.getCalendar(date); + Twilight twilight = calendar.twilight(coordinates); + + System.out.println("Twilight converted date: " + twilight.date()); + System.out.println("Sunrise: " + twilight.sunrise()); + System.out.println("Sunset: " + twilight.sunset()); + + if (twilight.isPolarDay()) { + long daytime = unixTimestampMs % 86400000; + if (daytime < 21600000) { + unixTimestampMs += 43200000; + } else if (daytime > 64800000) { + unixTimestampMs += 43200000; + } + System.out.println("Polar day"); + + return unixTimestampMs; + } else if (twilight.isPolarNight()) { + long daytime = unixTimestampMs % 86400000; + + if (daytime > 21600000 && daytime < 64800000) { + unixTimestampMs += 43200000; + } + + System.out.println("Polar night"); + + return unixTimestampMs; + } + + long sunriseMs = twilight.sunrise().toInstant(ZoneOffset.UTC).toEpochMilli(); + long sunsetMs = twilight.sunset().toInstant(ZoneOffset.UTC).toEpochMilli(); + + // Handle edge cases + if (unixTimestampMs == sunriseMs) return SCALED_SUNRISE_MS; + if (unixTimestampMs == sunsetMs) return SCALED_SUNSET_MS; + + long startTimeMs, endTimeMs, scaledStartMs; + + if (unixTimestampMs >= sunriseMs && unixTimestampMs < sunsetMs) { + // Daytime + startTimeMs = sunriseMs; + endTimeMs = sunsetMs; + scaledStartMs = SCALED_SUNRISE_MS; + } else { + // Nighttime + if (unixTimestampMs >= sunsetMs) { + // After sunset + startTimeMs = sunsetMs; + endTimeMs = calendar.tomorrow().twilight(coordinates).sunrise().toInstant(ZoneOffset.UTC).toEpochMilli(); + } else { + // Before sunrise + Twilight yesterdayTwilight = calendar.yesterday().twilight(coordinates); + startTimeMs = yesterdayTwilight.sunset().toInstant(ZoneOffset.UTC).toEpochMilli(); + endTimeMs = sunriseMs; + } + scaledStartMs = SCALED_SUNSET_MS; + } + + double durationMs = endTimeMs - startTimeMs; + double progress = (unixTimestampMs - startTimeMs) / durationMs; + long scaledOffsetMs = scaledStartMs + (long) (progress * SCALED_DAY_LENGTH_MS); + + // Ensure the result is within the same day (0 to MILLIS_PER_DAY - 1) + return scaledOffsetMs % MILLIS_PER_DAY; + } +} \ 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 44bcd08..edb5107 100644 --- a/src/main/java/eu/m724/realweather/time/TimeMaster.java +++ b/src/main/java/eu/m724/realweather/time/TimeMaster.java @@ -7,31 +7,35 @@ import eu.m724.realweather.DebugLogger; import eu.m724.realweather.GlobalConstants; import eu.m724.realweather.mapper.Mapper; +import java.time.LocalTime; + public class TimeMaster { - private TimeConfig timeConfig; - private Mapper mapper = GlobalConstants.getMapper(); - private Plugin plugin = GlobalConstants.getPlugin(); + private final Mapper mapper = GlobalConstants.getMapper(); + private final Plugin plugin = GlobalConstants.getPlugin(); + + private final TimeConfig timeConfig; + private final TimeConverter timeConverter; public TimeMaster(TimeConfig timeConfig) { this.timeConfig = timeConfig; + this.timeConverter = new TimeConverter(timeConfig.scale()); } - public void init() { - if (!timeConfig.enabled) + if (!timeConfig.enabled()) return; - long period = (long) (72 / timeConfig.scale); + long period = timeConverter.calculateUpdatePeriod(); - if (timeConfig.scale * Math.floor(period) != 72.0) { + 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(timeConfig).runTaskTimer(plugin, 0, period); + new SyncTimeUpdateTask(timeConverter, timeConfig).runTaskTimer(plugin, 0, period); - if (timeConfig.dynamic) - new AsyncPlayerTimeTask(timeConfig).runTaskTimerAsynchronously(plugin, 0, period); + if (timeConfig.dynamic()) + new AsyncPlayerTimeTask(timeConverter, timeConfig).runTaskTimerAsynchronously(plugin, 0, period); // TODO maybe use a different period? // TODO also make it on player join diff --git a/src/main/resources/modules/time.yml b/src/main/resources/modules/time.yml index 8840092..54ebb61 100644 --- a/src/main/resources/modules/time.yml +++ b/src/main/resources/modules/time.yml @@ -22,5 +22,4 @@ twilight: true # x in game day cycles in 1 irl day cycle # Time will no longer be in sync -# Can be decimal scale: 1.0 \ No newline at end of file diff --git a/src/test/java/TimeConverterTest.java b/src/test/java/TimeConverterTest.java new file mode 100644 index 0000000..698fd53 --- /dev/null +++ b/src/test/java/TimeConverterTest.java @@ -0,0 +1,38 @@ +import eu.m724.realweather.time.TimeConverter; +import eu.m724.wtapi.object.Coordinates; +import eu.m724.wtapi.object.Twilight; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +public class TimeConverterTest { + @Test + public void testTimeConverter() { + TimeConverter timeConverter = new TimeConverter(2.0); + System.out.println("Scale: " + timeConverter.scale); + long time = LocalDateTime.now().withHour(12).toInstant(ZoneOffset.UTC).toEpochMilli(); + System.out.println("Time: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC)); + time = timeConverter.scale(time); + System.out.println("Time scaled: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC)); + System.out.println("Those times are UTC so add 7 hours"); + time = timeConverter.offsetTwilight(time, new Coordinates(42, 69)); // TODO change numbers before committing + + time = timeConverter.millisToTicks(time); + System.out.println("Day ticks: " + time); + + System.out.println(); + + System.out.println("Scale: " + timeConverter.scale); + time = LocalDateTime.now().withHour(12).toInstant(ZoneOffset.UTC).toEpochMilli(); + System.out.println("Time: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC)); + time = timeConverter.scale(time); + System.out.println("Time scaled: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC)); + System.out.println("Those times are UTC so add 7 hours"); + time = timeConverter.offsetTwilight(time, new Coordinates(70, 0)); // TODO change numbers before committing + + time = timeConverter.millisToTicks(time); + System.out.println("Day ticks: " + time); + } +}