diff --git a/src/main/java/net/pupskuchen/timecontrol/event/player/PlayerBed.java b/src/main/java/net/pupskuchen/timecontrol/event/player/PlayerBed.java index 6ca2a4f..6037a00 100644 --- a/src/main/java/net/pupskuchen/timecontrol/event/player/PlayerBed.java +++ b/src/main/java/net/pupskuchen/timecontrol/event/player/PlayerBed.java @@ -48,6 +48,8 @@ public void onPlayerBedEnter(final PlayerBedEnterEvent event) { skipper = new NightSkipper(plugin, world); this.worldSkippers.put(worldName, skipper); } + + skipper.scheduleSkip(); } @EventHandler diff --git a/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipScheduler.java b/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipScheduler.java new file mode 100644 index 0000000..70239dc --- /dev/null +++ b/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipScheduler.java @@ -0,0 +1,48 @@ +package net.pupskuchen.timecontrol.nightskipping; + +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import net.pupskuchen.timecontrol.TimeControl; + +class SkipAttempt extends BukkitRunnable { + private final NightSkipper skipper; + + public SkipAttempt(final NightSkipper skipper) { + this.skipper = skipper; + } + + @Override + public void run() { + this.skipper.skipNight(); + } +} + + +class NightSkipScheduler { + private final TimeControl plugin; + private final NightSkipper skipper; + private final int sleepTicksToWake; + + private BukkitTask skipTask; + + public NightSkipScheduler(final TimeControl plugin, final NightSkipper skipper, + final int sleepTicksToWake) { + this.plugin = plugin; + this.skipper = skipper; + this.sleepTicksToWake = sleepTicksToWake; + } + + public void scheduleSkip() { + this.cancel(); + skipTask = new SkipAttempt(skipper).runTaskLater(plugin, sleepTicksToWake); + } + + public void cancel() { + if (skipTask == null) { + return; + } + + skipTask.cancel(); + skipTask = null; + } +} diff --git a/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipper.java b/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipper.java index 12e3a18..500f058 100644 --- a/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipper.java +++ b/src/main/java/net/pupskuchen/timecontrol/nightskipping/NightSkipper.java @@ -19,21 +19,48 @@ public class NightSkipper { private static final int SKIP_PERCENTAGE_FALLBACK = 100; private final World world; - private final ConfigHandler config; + private final int skipPercentage; private final TCLogger logger; + private final NightSkipScheduler skipScheduler; public NightSkipper(final TimeControl plugin, final World world) { this.world = world; - this.config = plugin.getConfigHandler(); this.logger = plugin.getTCLogger(); + this.skipPercentage = getSkipPercentage(plugin.getConfigHandler()); + this.skipScheduler = createSkipScheduler(plugin, world); } - private int getSkipPercentage() { + public void scheduleSkip() { + if (skipScheduler == null || skipThresholdMet(false)) { + return; + } + + skipScheduler.scheduleSkip(); + logger.debug("Scheduled night skip for world \"%s\".", world.getName()); + } + + private void cancelScheduledSkip() { + if (skipScheduler != null) { + skipScheduler.cancel(); + } + } + + private NightSkipScheduler createSkipScheduler(final TimeControl plugin, final World world) { + if (!world.isGameRule("playersSleepingPercentage")) { + // In older versions, players aren't going to leave their beds by themselves, + // so we have to manually schedule the skip. + return new NightSkipScheduler(plugin, this, SKIPPABLE_SLEEP_TICKS + 1); + } + + return null; + } + + private int getSkipPercentage(final ConfigHandler config) { if (!config.isPercentageEnabled(world)) { try { return world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE); } catch (NoSuchFieldError e) { - logger.warn("Could not fetch game-rule value 'playersSleepingPercentage!" + logger.warn("Failed to read game rule 'playersSleepingPercentage!" + " Please enable players-sleeping-percentage in the plugin configuration."); logger.warn("Using fallback percentage of %d %%.", SKIP_PERCENTAGE_FALLBACK); @@ -44,33 +71,32 @@ private int getSkipPercentage() { return config.getConfigPercentage(world); } - private boolean skipThresholdMet() { - final int skipPercentage = getSkipPercentage(); - + private boolean skipThresholdMet(final boolean onlyFullyRested) { if (skipPercentage <= 0) { return true; + } else if (skipPercentage > 100) { + return false; } + final int sleepTickThreshold = onlyFullyRested ? SKIPPABLE_SLEEP_TICKS : 1; final List players = world.getPlayers(); final int sleeping = (int) players.stream() - .filter((player) -> player.getSleepTicks() >= SKIPPABLE_SLEEP_TICKS).count(); + .filter((player) -> player.getSleepTicks() >= sleepTickThreshold).count(); final float sleepingPercentage = ((float) sleeping / players.size()) * 100; return sleepingPercentage >= skipPercentage; } - private boolean shouldSkipNight() { + public void skipNight() { if (!TimeUtil.sleepAllowed(world)) { - return false; + return; } - final boolean thresholdMet = skipThresholdMet(); - - return thresholdMet; - } + if (!skipThresholdMet(true)) { + if (!skipThresholdMet(false)) { + cancelScheduledSkip(); + } - public void skipNight() { - if (!shouldSkipNight()) { return; } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4e4bf4f..609d13d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: ${project.name} main: net.pupskuchen.timecontrol.TimeControl version: ${project.version} -api-version: 1.15 +api-version: 1.14 author: pupskuchen website: https://github.com/Pupskuchen/spigot-TimeControl load: STARTUP diff --git a/src/test/java/net/pupskuchen/timecontrol/event/player/PlayerBedTest.java b/src/test/java/net/pupskuchen/timecontrol/event/player/PlayerBedTest.java index b170d60..666a03b 100644 --- a/src/test/java/net/pupskuchen/timecontrol/event/player/PlayerBedTest.java +++ b/src/test/java/net/pupskuchen/timecontrol/event/player/PlayerBedTest.java @@ -98,7 +98,7 @@ public void createSkipperAndStartGuard() { NightSkipper skipper = mock.constructed().get(0); verify(logger, times(1)).debug("%s (@ %s) entered a bed at %d", "somePlayerName", "someWorld", 15000L); - verifyNoInteractions(skipper); + verify(skipper).scheduleSkip(); } } @@ -119,7 +119,7 @@ public void restartExistingGuard() { NightSkipper skipper = mock.constructed().get(0); verify(logger, times(2)).debug("%s (@ %s) entered a bed at %d", "somePlayerName", "someWorld", 15000L); - verifyNoInteractions(skipper); + verify(skipper, times(2)).scheduleSkip(); } } diff --git a/src/test/java/net/pupskuchen/timecontrol/nightskipping/NightSkipperTest.java b/src/test/java/net/pupskuchen/timecontrol/nightskipping/NightSkipperTest.java index c46e96e..595c50e 100644 --- a/src/test/java/net/pupskuchen/timecontrol/nightskipping/NightSkipperTest.java +++ b/src/test/java/net/pupskuchen/timecontrol/nightskipping/NightSkipperTest.java @@ -1,5 +1,6 @@ package net.pupskuchen.timecontrol.nightskipping; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -35,8 +36,6 @@ public class NightSkipperTest { @Mock final World world = mock(World.class); - private NightSkipper skipper; - List> players = List.of(this.getPlayers(0, 50, 0), this.getPlayers(100, 50, 0), this.getPlayers(100, 100, 50), this.getPlayers(100, 100, 100)); @@ -44,7 +43,6 @@ public class NightSkipperTest { public void setup() { when(plugin.getTCLogger()).thenReturn(logger); when(plugin.getConfigHandler()).thenReturn(configManager); - skipper = new NightSkipper(plugin, world); } private Player getPlayer(int sleepTicks) { @@ -58,10 +56,14 @@ private List getPlayers(int... sleepTicks) { @Test public void skipNightNoSleepAllowed() { + when(world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE)).thenReturn( 50); + final NightSkipper skipper = new NightSkipper(plugin, world); + try (MockedStatic mock = mockStatic(TimeUtil.class)) { mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(false); skipper.skipNight(); - verifyNoInteractions(world); + verify(world, times(0)).setTime(anyLong()); + verify(world, times(0)).setTime(anyInt()); verifyNoInteractions(logger); } } @@ -72,6 +74,8 @@ public void skipNightThresholdNotMet() { when(world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE)).thenReturn( 50); when(world.getPlayers()).thenReturn(players.get(0)); + final NightSkipper skipper = new NightSkipper(plugin, world); + try(MockedStatic mock = mockStatic(TimeUtil.class)) { mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true); skipper.skipNight(); @@ -86,6 +90,8 @@ public void skipNightByGameRule() { when(world.getPlayers()).thenReturn(players.get(2)); when(world.getName()).thenReturn("fancy-world"); + final NightSkipper skipper = new NightSkipper(plugin, world); + try(MockedStatic mock = mockStatic(TimeUtil.class)) { mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true); mock.when(() -> TimeUtil.getWakeTime(world)).thenReturn(123); @@ -103,6 +109,7 @@ public void skipNightByByFallback() { when(world.getPlayers()).thenReturn(players.get(2), players.get(3)); when(world.getName()).thenReturn("fancy-world"); + final NightSkipper skipper = new NightSkipper(plugin, world); try(MockedStatic mock = mockStatic(TimeUtil.class)) { mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true); @@ -111,9 +118,9 @@ public void skipNightByByFallback() { verify(world, times(0)).setTime(anyLong()); skipper.skipNight(); verify(world, times(1)).setTime(123); - verify(logger, times(2)).warn("Could not fetch game-rule value 'playersSleepingPercentage!" + - " Please enable players-sleeping-percentage in the plugin configuration."); - verify(logger, times(2)).warn("Using fallback percentage of %d %%.", 100); + verify(logger, times(1)).warn("Failed to read game rule 'playersSleepingPercentage!" + + " Please enable players-sleeping-percentage in the plugin configuration."); + verify(logger, times(1)).warn("Using fallback percentage of %d %%.", 100); verify(logger, times(1)).info("Skipped the night on world \"%s\".", "fancy-world"); } @@ -126,6 +133,8 @@ public void skipNightByByConfiguration() { when(world.getPlayers()).thenReturn(players.get(2)); when(world.getName()).thenReturn("fancy-world"); + final NightSkipper skipper = new NightSkipper(plugin, world); + try(MockedStatic mock = mockStatic(TimeUtil.class)) { mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true); mock.when(() -> TimeUtil.getWakeTime(world)).thenReturn(123);