Skip to content

Commit

Permalink
fix: skip night when everyone sleeps 1.13
Browse files Browse the repository at this point in the history
  • Loading branch information
Pupskuchen committed Apr 13, 2023
1 parent 57953e5 commit 05ae5e1
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ public void onPlayerBedLeave(final PlayerBedLeaveEvent event) {
final Player player = event.getPlayer();
final World world = player.getWorld();

if (!config.isWorldEnabled(world) || !config.isNightSkippingEnabled(world)) {
if (player.isSleepingIgnored()
|| player.getSleepTicks() < NightSkipper.SKIPPABLE_SLEEP_TICKS
|| !config.isWorldEnabled(world) || !config.isNightSkippingEnabled(world)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.pupskuchen.timecontrol.nightskipping;

import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.GameRule;
import org.bukkit.World;
import org.bukkit.entity.Player;
Expand All @@ -15,33 +16,39 @@ public class NightSkipper {
* actually get kicked out after 100 ticks already. Close enough …
* https://minecraft.fandom.com/wiki/Bed#Sleeping
*/
private static final int SKIPPABLE_SLEEP_TICKS = 100;
public static final int SKIPPABLE_SLEEP_TICKS = 100;
private static final int SKIP_PERCENTAGE_FALLBACK = 100;

private final World world;
private final int skipPercentage;
private boolean warnIfPercentageUnconfigured = true;
private final ConfigHandler config;
private final TCLogger logger;
private final NightSkipScheduler skipScheduler;

/**
* In MC 1.13, when all players go to bed, the last player won't have to sleep as long for the
* night to be skipped. Therefore, if the required sleeping percentage is 100 %, we'll allow
* skipping right when the last person goes to bed - but only for 1.13.
* night to be skipped. Therefore, we'll allow skipping right when the last person goes to bed -
* but only if *everyone* is in bed and only for 1.13.
*/
private final boolean allowAllAsleepInstantSkip;

public NightSkipper(final TimeControl plugin, final World world) {
this.world = world;
this.logger = plugin.getTCLogger();
this.skipPercentage = getSkipPercentage(plugin.getConfigHandler());
this.config = plugin.getConfigHandler();
this.skipScheduler = createSkipScheduler(plugin, world);

this.allowAllAsleepInstantSkip =
plugin.getServer().getBukkitVersion().startsWith("1.13") && skipPercentage == 100;
this.allowAllAsleepInstantSkip = plugin.getServer().getBukkitVersion().startsWith("1.13");
}

public void scheduleSkip() {
if (skipScheduler == null || skipThresholdMet(false)) {
if (allowAllAsleepInstantSkip && getSleepingRelevantPlayers().size() > 1
&& getSleepingPercentage(false, 1) >= 100) {
skipNight(1);
return;
}

if (skipScheduler == null || skipThresholdMet(false, 0)) {
return;
}

Expand All @@ -65,16 +72,18 @@ private NightSkipScheduler createSkipScheduler(final TimeControl plugin, final W
return null;
}

private int getSkipPercentage(final ConfigHandler config) {
private int getSkipPercentage(final boolean warnIfUnconfigured) {
if (!config.isPercentageEnabled(world)) {
try {
return world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE);
} catch (NoSuchFieldError e) {
logger.warn(
"Failed to read game rule \"playersSleepingPercentage\" for world \"%s\"!",
world.getName());
logger.warn("Please enable players-sleeping-percentage in the plugin config.");
logger.warn("Using fallback percentage of %d %%.", SKIP_PERCENTAGE_FALLBACK);
if (warnIfUnconfigured) {
logger.warn(
"Failed to read game rule \"playersSleepingPercentage\" for world \"%s\"!",
world.getName());
logger.warn("Please enable players-sleeping-percentage in the plugin config.");
logger.warn("Using fallback percentage of %d %%.", SKIP_PERCENTAGE_FALLBACK);
}

return SKIP_PERCENTAGE_FALLBACK;
}
Expand All @@ -83,29 +92,46 @@ private int getSkipPercentage(final ConfigHandler config) {
return config.getConfigPercentage(world);
}

private boolean skipThresholdMet(final boolean onlyFullyRested) {
private List<Player> getSleepingRelevantPlayers() {
return world.getPlayers().stream().filter(player -> !player.isSleepingIgnored())
.collect(Collectors.toList());
}

private float getSleepingPercentage(final boolean onlyFullyRested,
final int sleepingCountOffset) {
final int sleepTickThreshold = onlyFullyRested ? SKIPPABLE_SLEEP_TICKS : 1;
final List<Player> players = getSleepingRelevantPlayers();
final int sleeping = (int) players.stream()
.filter((player) -> player.getSleepTicks() >= sleepTickThreshold).count()
+ sleepingCountOffset;

return ((float) sleeping / players.size()) * 100;
}

private boolean skipThresholdMet(final boolean onlyFullyRested, final int sleepingCountOffset) {
final int skipPercentage = getSkipPercentage(warnIfPercentageUnconfigured);
warnIfPercentageUnconfigured = false;

if (skipPercentage <= 0) {
return true;
} else if (skipPercentage > 100) {
return false;
}

final int sleepTickThreshold = onlyFullyRested ? SKIPPABLE_SLEEP_TICKS : 1;
final List<Player> players = world.getPlayers();
final int sleeping = (int) players.stream()
.filter((player) -> player.getSleepTicks() >= sleepTickThreshold).count();
final float sleepingPercentage = ((float) sleeping / players.size()) * 100;

return sleepingPercentage >= skipPercentage;
return getSleepingPercentage(onlyFullyRested, sleepingCountOffset) >= skipPercentage;
}

public void skipNight() {
skipNight(0);
}

private void skipNight(final int sleepingCountOffset) {
if (!TimeUtil.sleepAllowed(world)) {
return;
}

if (!skipThresholdMet(!allowAllAsleepInstantSkip)) {
if (!skipThresholdMet(false)) {
if (!skipThresholdMet(!allowAllAsleepInstantSkip, sleepingCountOffset)) {
if (!skipThresholdMet(false, sleepingCountOffset)) {
cancelScheduledSkip();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ public void restartExistingGuard() {

@Test
public void doNothingOnLeaveIfWorldDisabled() {
when(player.isSleepingIgnored()).thenReturn(false);
when(player.getSleepTicks()).thenReturn(100);
when(config.isWorldEnabled(world)).thenReturn(false);
this.stubLeaveEvent();
when(player.getWorld()).thenReturn(world);
Expand All @@ -159,6 +161,8 @@ public void doNothingOnLeaveIfWorldDisabled() {

@Test
public void doNothingOnLeaveIfNightSkippingDisabled() {
when(player.isSleepingIgnored()).thenReturn(false);
when(player.getSleepTicks()).thenReturn(100);
when(config.isWorldEnabled(world)).thenReturn(true);
when(config.isNightSkippingEnabled(world)).thenReturn(false);
this.stubLeaveEvent();
Expand All @@ -169,13 +173,40 @@ public void doNothingOnLeaveIfNightSkippingDisabled() {
verify(world, times(0)).getName();
}

@Test
public void doNothingOnLeaveIfPlayerSleepingIgnored() {
when(player.isSleepingIgnored()).thenReturn(true);
when(player.getWorld()).thenReturn(world);
this.stubLeaveEvent();

playerBed.onPlayerBedLeave(leaveEvent);

verify(world, times(0)).getName();
verifyNoInteractions(logger);
}

@Test
public void doNothingOnLeaveIfPlayerSleepingTooShort() {
when(player.isSleepingIgnored()).thenReturn(false);
when(player.getSleepTicks()).thenReturn(99);
when(player.getWorld()).thenReturn(world);
this.stubLeaveEvent();

playerBed.onPlayerBedLeave(leaveEvent);

verify(world, times(0)).getName();
verifyNoInteractions(logger);
}

@Test
public void doNothingOnLeaveIfNoSkipperExists() {
when(config.isWorldEnabled(world)).thenReturn(true);
when(config.isNightSkippingEnabled(world)).thenReturn(true);
this.stubLeaveEvent();
this.stubPlayer();
this.stubWorld();
when(player.isSleepingIgnored()).thenReturn(false);
when(player.getSleepTicks()).thenReturn(100);

try (MockedConstruction<NightSkipper> mock = mockConstruction(NightSkipper.class)) {
playerBed.onPlayerBedLeave(leaveEvent);
Expand All @@ -188,6 +219,8 @@ public void doNothingOnLeaveIfNoSkipperExists() {

@Test
public void skipNightOnLeave() {
when(player.isSleepingIgnored()).thenReturn(false);
when(player.getSleepTicks()).thenReturn(100);
when(config.isWorldEnabled(world)).thenReturn(true);
when(config.isNightSkippingEnabled(world)).thenReturn(true);
this.stubEnterEvent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import net.pupskuchen.timecontrol.util.TimeUtil;

/**
* Tests for 1.13 (manual skip scheduling)
* Tests for 1.13-1.16 (manual skip scheduling / no playersSleepingPercentage game rule)
*/
public class LegacyNightSkipperTest {
private final TimeControl plugin = mock(TimeControl.class);
Expand Down Expand Up @@ -63,9 +63,27 @@ public void scheduleNightSkip() {
mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true);

skipper.scheduleSkip();
server.getScheduler().performTicks(100);
verify(world, times(0)).setTime(anyLong());
verify(world).setTime(anyLong());
}
}

@Test
public void scheduleForSinglePlayer() {
Player player =
when(mock(Player.class).getSleepTicks()).thenReturn(0).thenReturn(100).getMock();
List<Player> worldPlayers = List.of(player);
when(config.isPercentageEnabled(world)).thenReturn(true);
when(config.getConfigPercentage(world)).thenReturn(50);
when(world.getPlayers()).thenReturn(worldPlayers);
final NightSkipper skipper = new NightSkipper(plugin, world);

try (MockedStatic<TimeUtil> mock = mockStatic(TimeUtil.class)) {
mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true);

skipper.scheduleSkip();
verify(world, times(0)).setTime(anyLong());;
server.getScheduler().performTicks(100);
verify(world, times(0)).setTime(anyLong());;
server.getScheduler().performOneTick();
verify(world).setTime(anyLong());
}
Expand All @@ -88,16 +106,51 @@ public void instantSkipWhenAllAsleep() {
}

@Test
public void dontScheduleWhenThresholdMet() {
public void noInstantSkipWhenNotEveryoneAsleep() {
when(config.isPercentageEnabled(world)).thenReturn(true);
when(config.getConfigPercentage(world)).thenReturn(100);
when(world.getPlayers()).thenReturn(players.get(2));

when(world.getPlayers()).thenReturn(players.get(0));
final NightSkipper skipper = new NightSkipper(plugin, world);

skipper.scheduleSkip();
server.getScheduler().performTicks(110);

verifyNoInteractions(logger);
verify(world, times(0)).setTime(anyLong());
}

@Test
public void dontScheduleWhenThresholdMet() {
when(config.isPercentageEnabled(world)).thenReturn(true);
when(config.getConfigPercentage(world)).thenReturn(50);
when(world.getPlayers()).thenReturn(players.get(2));
when(server.getBukkitVersion()).thenReturn("1.15-something");

try(MockedStatic<TimeUtil> mock = mockStatic(TimeUtil.class)) {
mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true);
final NightSkipper skipper = new NightSkipper(plugin, world);
skipper.scheduleSkip();
server.getScheduler().performTicks(110);

verifyNoInteractions(logger);
verify(world, times(0)).setTime(anyLong());
}
}

@Test
public void cancelScheduledSkip() {
when(config.isPercentageEnabled(world)).thenReturn(true);
when(config.getConfigPercentage(world)).thenReturn(100);
when(world.getPlayers()).thenReturn(players.get(2)).thenReturn(players.get(0));

try(MockedStatic<TimeUtil> mock = mockStatic(TimeUtil.class)) {
mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true);

final NightSkipper skipper = new NightSkipper(plugin, world);
skipper.scheduleSkip();
skipper.skipNight();
server.getScheduler().performTicks(110);

verify(world, times(0)).setTime(anyLong());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bukkit.GameRule;
import org.bukkit.Server;
import org.bukkit.World;
Expand Down Expand Up @@ -145,8 +146,6 @@ public void skipNightByByConfiguration() {

@Test
public void doNotScheduleSkip() {
when(configManager.isPercentageEnabled(world)).thenReturn(true);
when(configManager.getConfigPercentage(world)).thenReturn( 50);
final NightSkipper skipper = new NightSkipper(plugin, world);
reset(world);

Expand Down Expand Up @@ -187,4 +186,27 @@ public void neverAllowSkipAbove100Percentage() {
verifyNoInteractions(logger);
}
}

@Test
public void shouldIgnoreSleepingIgnoredPlayers() {
List<Player> notIgnored = getPlayers(100, 100);
List<Player> ignored = getPlayers(0, 10, 0).stream()
.map(player -> (Player)when(player.isSleepingIgnored()).thenReturn(true).getMock())
.collect(Collectors.toList());
List<Player> allPlayers = Stream.concat(notIgnored.stream(), ignored.stream()).collect(Collectors.toList());

when(configManager.isPercentageEnabled(world)).thenReturn(false);
when(world.getGameRuleValue(GameRule.PLAYERS_SLEEPING_PERCENTAGE)).thenReturn(100);
when(world.getName()).thenReturn("fancy-world");
when(world.getPlayers()).thenReturn(allPlayers);
final NightSkipper skipper = new NightSkipper(plugin, world);

try(MockedStatic<TimeUtil> mock = mockStatic(TimeUtil.class)) {
mock.when(() -> TimeUtil.sleepAllowed(world)).thenReturn(true);
mock.when(() -> TimeUtil.getWakeTime(world)).thenReturn(123);
skipper.skipNight();
verify(world).getPlayers();
verify(world, times(1)).setTime(123);
}
}
}

0 comments on commit 05ae5e1

Please sign in to comment.