diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 045d1ee974..e937c18d08 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -24,6 +24,7 @@ import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.MigrationService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.service.bungeecord.BungeeReceiver; import fr.xephi.authme.service.yaml.YamlParseException; import fr.xephi.authme.settings.Settings; @@ -316,6 +317,8 @@ public void onDisable() { // Wait for tasks and close data source new TaskCloser(this, database).run(); + injector.getIfAvailable(SpectateLoginService.class).removeArmorstands(); + // Disabled correctly Consumer infoLogMethod = logger == null ? getLogger()::info : logger::info; infoLogMethod.accept("AuthMe " + this.getDescription().getVersion() + " disabled!"); diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java index 34e731961f..b866ca7211 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboPlayer.java @@ -1,6 +1,7 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.task.MessageTask; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.scheduler.BukkitTask; @@ -22,18 +23,20 @@ public class LimboPlayer { private final Location loc; private final float walkSpeed; private final float flySpeed; + private final GameMode gameMode; private BukkitTask timeoutTask = null; private MessageTask messageTask = null; private LimboPlayerState state = LimboPlayerState.PASSWORD_REQUIRED; public LimboPlayer(Location loc, boolean operator, Collection groups, boolean fly, float walkSpeed, - float flySpeed) { + float flySpeed, GameMode gameMode) { this.loc = loc; this.operator = operator; this.groups = new ArrayList<>(groups); // prevent bug #2413 this.canFly = fly; this.walkSpeed = walkSpeed; this.flySpeed = flySpeed; + this.gameMode = gameMode; } /** @@ -45,6 +48,15 @@ public Location getLocation() { return loc; } + /** + * Return the player's original gamemode. + * + * @return The player's gamemode + */ + public GameMode getGameMode() { + return gameMode; + } + /** * Return whether the player is an operator or not (i.e. whether he is an OP). * diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java index c8b469224e..14dd4ebe25 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboService.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboService.java @@ -119,6 +119,7 @@ public void restoreData(Player player) { logger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName); } else { player.setOp(limbo.isOperator()); + player.setGameMode(limbo.getGameMode()); settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo); settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo); settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo); diff --git a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java index a3edb164f4..8bae6965e6 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java +++ b/src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java @@ -6,6 +6,7 @@ import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.LimboSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -13,6 +14,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import static fr.xephi.authme.util.Utils.isCollectionEmpty; import static java.util.stream.Collectors.toList; @@ -44,6 +46,7 @@ LimboPlayer createLimboPlayer(Player player, boolean isRegistered, Location loca boolean flyEnabled = player.getAllowFlight(); float walkSpeed = player.getWalkSpeed(); float flySpeed = player.getFlySpeed(); + GameMode gameMode = player.getGameMode(); Collection playerGroups = permissionsManager.hasGroupSupport() ? permissionsManager.getGroups(player) : Collections.emptyList(); @@ -52,7 +55,7 @@ LimboPlayer createLimboPlayer(Player player, boolean isRegistered, Location loca .collect(toList()); logger.debug("Player `{0}` has groups `{1}`", player.getName(), String.join(", ", groupNames)); - return new LimboPlayer(location, isOperator, playerGroups, flyEnabled, walkSpeed, flySpeed); + return new LimboPlayer(location, isOperator, playerGroups, flyEnabled, walkSpeed, flySpeed, gameMode); } /** @@ -97,10 +100,11 @@ LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) { boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly(); float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed()); float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed()); + GameMode gameMode = Objects.isNull(newLimbo.getGameMode()) ? oldLimbo.getGameMode() : newLimbo.getGameMode(); Collection groups = getLimboGroups(oldLimbo.getGroups(), newLimbo.getGroups()); Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation()); - return new LimboPlayer(location, isOperator, groups, canFly, walkSpeed, flySpeed); + return new LimboPlayer(location, isOperator, groups, canFly, walkSpeed, flySpeed, gameMode); } private static Location firstNotNull(Location first, Location second) { diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java index 6dea20efe6..b45a4d281b 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerDeserializer.java @@ -10,6 +10,7 @@ import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.UserGroup; import fr.xephi.authme.service.BukkitService; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; @@ -23,6 +24,7 @@ import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY; import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED; +import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GAMEMODE; import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUPS; import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP; import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION; @@ -64,8 +66,10 @@ public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeseriali boolean canFly = getBoolean(jsonObject, CAN_FLY); float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED); float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED); + int gameModeId = getNumberFromElement(jsonObject.get(GAMEMODE), JsonElement::getAsInt, 0); + GameMode gameMode = GameMode.getByValue(gameModeId); - return new LimboPlayer(loc, operator, groups, canFly, walkSpeed, flySpeed); + return new LimboPlayer(loc, operator, groups, canFly, walkSpeed, flySpeed, gameMode); } private Location deserializeLocation(JsonObject jsonObject) { diff --git a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java index eaad86b7e5..e67d18019f 100644 --- a/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java +++ b/src/main/java/fr/xephi/authme/data/limbo/persistence/LimboPlayerSerializer.java @@ -31,6 +31,7 @@ class LimboPlayerSerializer implements JsonSerializer { static final String CAN_FLY = "can-fly"; static final String WALK_SPEED = "walk-speed"; static final String FLY_SPEED = "fly-speed"; + static final String GAMEMODE = "gamemode"; private static final Gson GSON = new Gson(); @@ -66,6 +67,7 @@ public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializati obj.addProperty(CAN_FLY, limboPlayer.isCanFly()); obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed()); obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed()); + obj.addProperty(GAMEMODE, limboPlayer.getGameMode().getValue()); return obj; } } diff --git a/src/main/java/fr/xephi/authme/listener/PlayerListener.java b/src/main/java/fr/xephi/authme/listener/PlayerListener.java index 53ea908f05..678209c8c5 100644 --- a/src/main/java/fr/xephi/authme/listener/PlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/PlayerListener.java @@ -11,6 +11,7 @@ import fr.xephi.authme.service.AntiBotService; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.JoinMessageService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.settings.Settings; @@ -19,6 +20,7 @@ import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.ChatColor; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -49,6 +51,8 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerShearEntityEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.inventory.InventoryView; import javax.inject.Inject; @@ -91,6 +95,8 @@ public class PlayerListener implements Listener { private PermissionsManager permissionsManager; @Inject private QuickCommandsProtectionManager quickCommandsProtectionManager; + @Inject + private SpectateLoginService spectateLoginService; // Lowest priority to apply fast protection checks @EventHandler(priority = EventPriority.LOWEST) @@ -376,6 +382,32 @@ public void onPlayerRespawn(PlayerRespawnEvent event) { if (spawn != null && spawn.getWorld() != null) { event.setRespawnLocation(spawn); } + + if (settings.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN) + && spectateLoginService.hasStand(event.getPlayer())) { + bukkitService.runTaskLater(() -> spectateLoginService.createStand(event.getPlayer()), 1L); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onToggleSneak(PlayerToggleSneakEvent event) { + if (listenerService.shouldCancelEvent(event.getPlayer()) + && (settings.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN) + || spectateLoginService.hasStand(event.getPlayer()))) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onTeleport(PlayerTeleportEvent event) { + if (listenerService.shouldCancelEvent(event.getPlayer()) + && event.getCause() == PlayerTeleportEvent.TeleportCause.SPECTATE + && event.getPlayer().getGameMode() == GameMode.SPECTATOR + && (settings.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN) + || spectateLoginService.hasStand(event.getPlayer()))) { + spectateLoginService.updateTarget(event.getPlayer()); + event.setCancelled(true); + } } /* diff --git a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java index d5cf1cef53..d7d7d3454c 100644 --- a/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java +++ b/src/main/java/fr/xephi/authme/process/join/AsynchronousJoin.java @@ -14,6 +14,7 @@ import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.service.SessionService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.service.ValidationService; import fr.xephi.authme.service.bungeecord.BungeeSender; import fr.xephi.authme.service.bungeecord.MessageType; @@ -83,6 +84,9 @@ public class AsynchronousJoin implements AsynchronousProcess { @Inject private ProxySessionManager proxySessionManager; + @Inject + private SpectateLoginService spectateLoginService; + AsynchronousJoin() { } @@ -199,6 +203,13 @@ private void processJoinSync(Player player, boolean isAuthAvailable) { int blindTimeOut = (registrationTimeout <= 0) ? 99999 : registrationTimeout; player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, blindTimeOut, 2)); } + + if (service.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN)) { + // The delay is necessary in order to make sure that the player is teleported to spawn + // and after authorization appears in the same place + bukkitService.runTaskLater(() -> spectateLoginService.createStand(player), 1); + } + commandManager.runCommandsOnJoin(player); }); } diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java index 74946039ae..82e233946f 100644 --- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java @@ -12,11 +12,13 @@ import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.JoinMessageService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.service.bungeecord.BungeeSender; import fr.xephi.authme.settings.WelcomeMessageConfiguration; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; +import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffectType; @@ -58,6 +60,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess { @Inject private PermissionsManager permissionsManager; + @Inject + private SpectateLoginService spectateLoginService; + ProcessSyncPlayerLogin() { } @@ -99,6 +104,11 @@ public void processPlayerLogin(Player player, boolean isFirstLogin, List player.removePotionEffect(PotionEffectType.BLINDNESS); } + if (commonService.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN) + || spectateLoginService.hasStand(player)) { + spectateLoginService.removeStand(player); + } + // The Login event now fires (as intended) after everything is processed bukkitService.callEvent(new LoginEvent(player)); diff --git a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncPlayerLogout.java b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncPlayerLogout.java index 46fd7fefc0..1331975e40 100644 --- a/src/main/java/fr/xephi/authme/process/logout/ProcessSyncPlayerLogout.java +++ b/src/main/java/fr/xephi/authme/process/logout/ProcessSyncPlayerLogout.java @@ -9,6 +9,7 @@ import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.properties.RegistrationSettings; @@ -44,6 +45,9 @@ public class ProcessSyncPlayerLogout implements SynchronousProcess { @Inject private CommandManager commandManager; + @Inject + private SpectateLoginService spectateLoginService; + ProcessSyncPlayerLogout() { } @@ -65,6 +69,10 @@ public void processSyncLogout(Player player) { service.send(player, MessageKey.LOGOUT_SUCCESS); logger.info(player.getName() + " logged out"); + + if (service.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN)) { + spectateLoginService.createStand(player); + } } private void applyLogoutEffect(Player player) { diff --git a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncPlayerQuit.java b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncPlayerQuit.java index fad8b74734..7c1403befe 100644 --- a/src/main/java/fr/xephi/authme/process/quit/ProcessSyncPlayerQuit.java +++ b/src/main/java/fr/xephi/authme/process/quit/ProcessSyncPlayerQuit.java @@ -2,7 +2,10 @@ import fr.xephi.authme.data.limbo.LimboService; import fr.xephi.authme.process.SynchronousProcess; +import fr.xephi.authme.service.CommonService; +import fr.xephi.authme.service.SpectateLoginService; import fr.xephi.authme.settings.commandconfig.CommandManager; +import fr.xephi.authme.settings.properties.RestrictionSettings; import org.bukkit.entity.Player; import javax.inject.Inject; @@ -10,6 +13,12 @@ public class ProcessSyncPlayerQuit implements SynchronousProcess { + @Inject + private CommonService service; + + @Inject + private SpectateLoginService spectateLoginService; + @Inject private LimboService limboService; @@ -26,6 +35,11 @@ public void processSyncQuit(Player player, boolean wasLoggedIn) { if (wasLoggedIn) { commandManager.runCommandsOnLogout(player); } else { + if (service.getProperty(RestrictionSettings.SPECTATE_STAND_LOGIN) + || spectateLoginService.hasStand(player)) { + spectateLoginService.removeStand(player); + } + limboService.restoreData(player); player.saveData(); // #1238: Speed is sometimes not restored properly } diff --git a/src/main/java/fr/xephi/authme/service/SpectateLoginService.java b/src/main/java/fr/xephi/authme/service/SpectateLoginService.java new file mode 100644 index 0000000000..f4c2385900 --- /dev/null +++ b/src/main/java/fr/xephi/authme/service/SpectateLoginService.java @@ -0,0 +1,109 @@ +package fr.xephi.authme.service; + +import fr.xephi.authme.settings.properties.RestrictionSettings; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import javax.inject.Inject; +import java.util.HashMap; +import java.util.Map; + +/** + * Sets the player gamemode to Spectator, puts the player in an invisible armorstand and fixes the direction of the view + */ +public class SpectateLoginService { + + private Map armorStands = new HashMap<>(); + private Map gameModeMap = new HashMap<>(); + + @Inject + private CommonService service; + + /** + * Creates a stand for the player + * + * @param player the player + */ + public void createStand(Player player) { + if (player.isDead()) { + return; + } + Location location = player.getLocation(); + ArmorStand stand = spawnStand(location); + + armorStands.put(player, stand); + gameModeMap.put(player, player.getGameMode()); + + player.setGameMode(GameMode.SPECTATOR); + player.setSpectatorTarget(stand); + } + + /** + * Updates spectator target for the player + * + * @param player the player + */ + public void updateTarget(Player player) { + ArmorStand stand = armorStands.get(player); + if (stand != null) { + player.setSpectatorTarget(stand); + } + } + + /** + * Removes the player's stand and deletes effects + * + * @param player the player + */ + public void removeStand(Player player) { + ArmorStand stand = armorStands.get(player); + if (stand != null) { + + stand.remove(); + player.setSpectatorTarget(null); + player.setGameMode(gameModeMap.get(player)); + + gameModeMap.remove(player); + armorStands.remove(player); + } + } + + /** + * Removes all armorstands and restores player gamemode + */ + public void removeArmorstands() { + for (Player player : armorStands.keySet()) { + removeStand(player); + } + + gameModeMap.clear(); + armorStands.clear(); + } + + public boolean hasStand(Player player) { + return armorStands.containsKey(player); + } + + /** + * Spawns stand to given location + * + * @param loc spawn location + * @return spawned armorStand + */ + protected ArmorStand spawnStand(Location loc) { + double pitch = service.getProperty(RestrictionSettings.SPECTATE_HEAD_PITCH); + double yaw = service.getProperty(RestrictionSettings.SPECTATE_HEAD_YAW); + Location location = new Location(loc.getWorld(), loc.getX(), loc.getY(), loc.getBlockZ(), + (float) yaw, (float) pitch); + + ArmorStand stand = location.getWorld().spawn(location, ArmorStand.class); + + stand.setGravity(false); + stand.setAI(false); + stand.setInvisible(true); + + return stand; + } + +} diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java index 8794ba4473..5a612dd5c6 100644 --- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java +++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java @@ -193,6 +193,22 @@ public final class RestrictionSettings implements SettingsHolder { public static final Property> UNRESTRICTED_INVENTORIES = newLowercaseStringSetProperty("settings.unrestrictions.UnrestrictedInventories"); + @Comment({ + "While in unregistered/logged out state, should players be set to spectator mode and", + "forced to spectate within a spawn point invisible armor stand, for 0 movement and head", + "pitch + yaw? may be more effective than 'allowMovement' at locking the player in place." + }) + public static final Property SPECTATE_STAND_LOGIN = + newProperty("settings.restrictions.spectateStandLogin.enabled", false); + + @Comment("Head Yaw position (rotation of X head) for 'spectateStandLogin'.") + public static final Property SPECTATE_HEAD_YAW = + newProperty("settings.restrictions.spectateStandLogin.headYaw", 0.0f); + + @Comment("Head Pitch position (rotation of Y head) for 'spectateStandLogin'.") + public static final Property SPECTATE_HEAD_PITCH = + newProperty("settings.restrictions.spectateStandLogin.headPitch", 0.0f); + private RestrictionSettings() { } diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java index 5cbbb1ffe7..1bfaf36617 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboPlayerTaskManagerTest.java @@ -106,7 +106,7 @@ public void shouldCancelExistingMessageTask() { String name = "rats"; Player player = mock(Player.class); given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = new LimboPlayer(null, true, Collections.singletonList(new UserGroup("grp")), false, 0.1f, 0.0f); + LimboPlayer limboPlayer = new LimboPlayer(null, true, Collections.singletonList(new UserGroup("grp")), false, 0.1f, 0.0f, player.getGameMode()); MessageTask existingMessageTask = mock(MessageTask.class); limboPlayer.setMessageTask(existingMessageTask); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(8); @@ -129,7 +129,7 @@ public void shouldInitializeMessageTaskWithCaptchaMessage() { String name = "race"; Player player = mock(Player.class); given(player.getName()).willReturn(name); - LimboPlayer limboPlayer = new LimboPlayer(null, true, Collections.singletonList(new UserGroup("grp")), false, 0.1f, 0.0f); + LimboPlayer limboPlayer = new LimboPlayer(null, true, Collections.singletonList(new UserGroup("grp")), false, 0.1f, 0.0f, player.getGameMode()); given(settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL)).willReturn(12); given(registrationCaptchaManager.isCaptchaRequired(name)).willReturn(true); String captcha = "M032"; @@ -180,7 +180,7 @@ public void shouldNotRegisterTimeoutTaskForZeroTimeout() { public void shouldCancelExistingTimeoutTask() { // given Player player = mock(Player.class); - LimboPlayer limboPlayer = new LimboPlayer(null, false, Collections.emptyList(), true, 0.3f, 0.1f); + LimboPlayer limboPlayer = new LimboPlayer(null, false, Collections.emptyList(), true, 0.3f, 0.1f, player.getGameMode()); BukkitTask existingTask = mock(BukkitTask.class); limboPlayer.setTimeoutTask(existingTask); given(settings.getProperty(RestrictionSettings.TIMEOUT)).willReturn(18); diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java index 418ce698cc..88accd0c30 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceHelperTest.java @@ -1,6 +1,7 @@ package fr.xephi.authme.data.limbo; import fr.xephi.authme.TestHelper; +import org.bukkit.GameMode; import org.bukkit.Location; import org.junit.BeforeClass; import org.junit.Test; @@ -37,9 +38,11 @@ public static void initLogger() { public void shouldMergeLimboPlayers() { // given Location newLocation = mock(Location.class); - LimboPlayer newLimbo = new LimboPlayer(newLocation, false, Collections.singletonList(new UserGroup("grp-new")), false, 0.0f, 0.0f); + GameMode newGameMode = GameMode.SPECTATOR; + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, Collections.singletonList(new UserGroup("grp-new")), false, 0.0f, 0.0f, newGameMode); Location oldLocation = mock(Location.class); - LimboPlayer oldLimbo = new LimboPlayer(oldLocation, true, Collections.singletonList(new UserGroup("grp-old")), true, 0.1f, 0.8f); + GameMode oldGameMode = GameMode.CREATIVE; + LimboPlayer oldLimbo = new LimboPlayer(oldLocation, true, Collections.singletonList(new UserGroup("grp-old")), true, 0.1f, 0.8f, oldGameMode); // when LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); @@ -51,14 +54,15 @@ public void shouldMergeLimboPlayers() { assertThat(result.isCanFly(), equalTo(true)); assertThat(result.getWalkSpeed(), equalTo(0.1f)); assertThat(result.getFlySpeed(), equalTo(0.8f)); + assertThat(result.getGameMode(), equalTo(newGameMode)); } @Test public void shouldFallBackToNewLimboForMissingData() { // given Location newLocation = mock(Location.class); - LimboPlayer newLimbo = new LimboPlayer(newLocation, false, Collections.singletonList(new UserGroup("grp-new")), true, 0.3f, 0.0f); - LimboPlayer oldLimbo = new LimboPlayer(null, false, Collections.emptyList(), false, 0.1f, 0.1f); + LimboPlayer newLimbo = new LimboPlayer(newLocation, false, Collections.singletonList(new UserGroup("grp-new")), true, 0.3f, 0.0f, GameMode.CREATIVE); + LimboPlayer oldLimbo = new LimboPlayer(null, false, Collections.emptyList(), false, 0.1f, 0.1f, null); // when LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo); @@ -70,6 +74,7 @@ public void shouldFallBackToNewLimboForMissingData() { assertThat(result.isCanFly(), equalTo(true)); assertThat(result.getWalkSpeed(), equalTo(0.3f)); assertThat(result.getFlySpeed(), equalTo(0.1f)); + assertThat(result.getGameMode(), equalTo(oldLimbo.getGameMode())); } @Test diff --git a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java index 44839c13bd..3b685760ec 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/LimboServiceTest.java @@ -243,7 +243,7 @@ private static Player newPlayer(String name, boolean isOp, float walkSpeed, bool private static LimboPlayer convertToLimboPlayer(Player player, Location location, Collection groups) { return new LimboPlayer(location, player.isOp(), groups, player.getAllowFlight(), - player.getWalkSpeed(), player.getFlySpeed()); + player.getWalkSpeed(), player.getFlySpeed(), player.getGameMode()); } private Map getLimboMap() { diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandlerTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandlerTest.java index f223bdf1c1..6e57b4a4e2 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandlerTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/DistributedFilesPersistenceHandlerTest.java @@ -157,10 +157,10 @@ public void shouldAddPlayer() { // given Player uuidToAdd1 = mockPlayerWithUuid(UNKNOWN_UUID); Location location1 = mockLocation("1world"); - LimboPlayer limbo1 = new LimboPlayer(location1, false, Collections.singletonList(new UserGroup("group-1")), true, 0.1f, 0.2f); + LimboPlayer limbo1 = new LimboPlayer(location1, false, Collections.singletonList(new UserGroup("group-1")), true, 0.1f, 0.2f, uuidToAdd1.getGameMode()); Player uuidToAdd2 = mockPlayerWithUuid(UNKNOWN_UUID2); Location location2 = mockLocation("2world"); - LimboPlayer limbo2 = new LimboPlayer(location2, true, Collections.emptyList(), false, 0.0f, 0.25f); + LimboPlayer limbo2 = new LimboPlayer(location2, true, Collections.emptyList(), false, 0.0f, 0.25f, uuidToAdd2.getGameMode()); // when persistenceHandler.saveLimboPlayer(uuidToAdd1, limbo1); diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandlerTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandlerTest.java index 77af856773..f83798b19d 100644 --- a/src/test/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandlerTest.java +++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/IndividualFilesPersistenceHandlerTest.java @@ -115,7 +115,7 @@ public void shouldSavePlayerData() { World world = mock(World.class); given(world.getName()).willReturn("player-world"); Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f); - LimboPlayer limbo = new LimboPlayer(location, true, Collections.singletonList(new UserGroup("primary-grp")), true, 1.2f, 0.8f); + LimboPlayer limbo = new LimboPlayer(location, true, Collections.singletonList(new UserGroup("primary-grp")), true, 1.2f, 0.8f, player.getGameMode()); // when handler.saveLimboPlayer(player, limbo); diff --git a/src/test/java/fr/xephi/authme/service/SpectateLoginServiceTest.java b/src/test/java/fr/xephi/authme/service/SpectateLoginServiceTest.java new file mode 100644 index 0000000000..11aa208386 --- /dev/null +++ b/src/test/java/fr/xephi/authme/service/SpectateLoginServiceTest.java @@ -0,0 +1,108 @@ +package fr.xephi.authme.service; + + +import org.bukkit.GameMode; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Test for {@link SpectateLoginService}. + */ +@RunWith(MockitoJUnitRunner.class) +public class SpectateLoginServiceTest { + private SpectateLoginService spectateLoginService; + + @Mock + private Player mockPlayer; + + @Mock + private ArmorStand mockArmorStand; + + @Before + public void setUp() { + spectateLoginService = new SpectateLoginService(); + } + + @Test + public void testCreateStand() { + when(mockPlayer.isDead()).thenReturn(false); + + spectateLoginService.createStand(mockPlayer); + + verify(mockPlayer).setGameMode(GameMode.SPECTATOR); + + verify(mockPlayer).setSpectatorTarget(mockArmorStand); + } + + @Test + public void testCreateStandPlayerDead() { + when(mockPlayer.isDead()).thenReturn(true); + + spectateLoginService.createStand(mockPlayer); + + verify(mockPlayer, never()).setGameMode(any()); + verify(mockPlayer, never()).setSpectatorTarget(any()); + } + + @Test + public void testUpdateTarget() { + spectateLoginService.createStand(mockPlayer); + + spectateLoginService.updateTarget(mockPlayer); + + verify(mockPlayer).setSpectatorTarget(mockArmorStand); + } + + @Test + public void testUpdateTargetNoStand() { + when(mockPlayer.isDead()).thenReturn(false); + + spectateLoginService.updateTarget(mockPlayer); + + verify(mockPlayer, never()).setSpectatorTarget(any()); + } + + @Test + public void testRemoveStand() { + spectateLoginService.createStand(mockPlayer); + + spectateLoginService.removeStand(mockPlayer); + + verify(mockArmorStand).remove(); + verify(mockPlayer).setSpectatorTarget(null); + verify(mockPlayer).setGameMode(GameMode.SURVIVAL); + } + + @Test + public void testRemoveStandNoStand() { + when(mockPlayer.isDead()).thenReturn(false); + + spectateLoginService.removeStand(mockPlayer); + + verify(mockArmorStand, never()).remove(); + verify(mockPlayer, never()).setSpectatorTarget(any()); + verify(mockPlayer, never()).setGameMode(any()); + } + + @Test + public void testHasStand() { + spectateLoginService.createStand(mockPlayer); + + assertTrue(spectateLoginService.hasStand(mockPlayer)); + } + + @Test + public void testHasStandNoStand() { + when(mockPlayer.isDead()).thenReturn(false); + + assertFalse(spectateLoginService.hasStand(mockPlayer)); + } +}