package fr.xephi.authme.settings; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.output.ConsoleLoggerFactory; import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.util.Locale; /** * Manager for spawn points. It loads spawn definitions from AuthMe and third-party plugins * and is responsible for returning the correct spawn point as per the settings. *

* The spawn priority setting defines from which sources and in which order the spawn point * should be taken from. In AuthMe, we can distinguish between the regular spawn and a "first spawn", * to which players will be teleported who have joined for the first time. */ public class SpawnLoader implements Reloadable { private final ConsoleLogger logger = ConsoleLoggerFactory.get(SpawnLoader.class); private final File authMeConfigurationFile; private final Settings settings; private final PluginHookService pluginHookService; private FileConfiguration authMeConfiguration; private String[] spawnPriority; private Location essentialsSpawn; private Location cmiSpawn; /** * Constructor. * * @param pluginFolder The AuthMe data folder * @param settings The setting instance * @param pluginHookService The plugin hooks instance */ @Inject SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) { File spawnFile = new File(pluginFolder, "spawn.yml"); FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); this.authMeConfigurationFile = spawnFile; this.settings = settings; this.pluginHookService = pluginHookService; reload(); } /** * (Re)loads the spawn file and relevant settings. */ @Override public void reload() { spawnPriority = settings.getProperty(RestrictionSettings.SPAWN_PRIORITY).split(","); authMeConfiguration = YamlConfiguration.loadConfiguration(authMeConfigurationFile); loadEssentialsSpawn(); } /** * Return the AuthMe spawn location. * * @return The location of the regular AuthMe spawn point */ public Location getSpawn() { return getLocationFromConfiguration(authMeConfiguration, "spawn"); } /** * Set the AuthMe spawn point. * * @param location The location to use * * @return True upon success, false otherwise */ public boolean setSpawn(Location location) { return setLocation("spawn", location); } /** * Return the AuthMe first spawn location. * * @return The location of the AuthMe spawn point for first timers */ public Location getFirstSpawn() { return getLocationFromConfiguration(authMeConfiguration, "firstspawn"); } /** * Set the AuthMe first spawn location. * * @param location The location to use * * @return True upon success, false otherwise */ public boolean setFirstSpawn(Location location) { return setLocation("firstspawn", location); } /** * Load the spawn point defined in EssentialsSpawn. */ public void loadEssentialsSpawn() { // EssentialsSpawn cannot run without Essentials, so it's fine to get the Essentials data folder File essentialsFolder = pluginHookService.getEssentialsDataFolder(); if (essentialsFolder == null) { return; } File essentialsSpawnFile = new File(essentialsFolder, "spawn.yml"); if (essentialsSpawnFile.exists()) { essentialsSpawn = getLocationFromConfiguration( YamlConfiguration.loadConfiguration(essentialsSpawnFile), "spawns.default"); } else { essentialsSpawn = null; logger.info("Essentials spawn file not found: '" + essentialsSpawnFile.getAbsolutePath() + "'"); } } /** * Unset the spawn point defined in EssentialsSpawn. */ public void unloadEssentialsSpawn() { essentialsSpawn = null; } /** * Load the spawn point defined in CMI. */ public void loadCmiSpawn() { File cmiFolder = pluginHookService.getCmiDataFolder(); if (cmiFolder == null) { return; } File cmiConfig = new File(cmiFolder, "config.yml"); if (cmiConfig.exists()) { cmiSpawn = getLocationFromCmiConfiguration(YamlConfiguration.loadConfiguration(cmiConfig)); } else { cmiSpawn = null; logger.info("CMI config file not found: '" + cmiConfig.getAbsolutePath() + "'"); } } /** * Unset the spawn point defined in CMI. */ public void unloadCmiSpawn() { cmiSpawn = null; } /** * Return the spawn location for the given player. The source of the spawn location varies * depending on the spawn priority setting. * * @param player The player to retrieve the spawn point for * * @return The spawn location, or the default spawn location upon failure * * @see RestrictionSettings#SPAWN_PRIORITY */ public Location getSpawnLocation(Player player) { if (player == null || player.getWorld() == null) { return null; } World world = player.getWorld(); Location spawnLoc = null; for (String priority : spawnPriority) { switch (priority.toLowerCase(Locale.ROOT).trim()) { case "default": if (world.getSpawnLocation() != null) { if (!isValidSpawnPoint(world.getSpawnLocation())) { for (World spawnWorld : Bukkit.getWorlds()) { if (isValidSpawnPoint(spawnWorld.getSpawnLocation())) { world = spawnWorld; break; } } logger.warning("Seems like AuthMe is unable to find a proper spawn location. " + "Set a location with the command '/authme setspawn'"); } spawnLoc = world.getSpawnLocation(); } break; case "multiverse": if (settings.getProperty(HooksSettings.MULTIVERSE)) { spawnLoc = pluginHookService.getMultiverseSpawn(world); } break; case "essentials": spawnLoc = essentialsSpawn; break; case "cmi": spawnLoc = cmiSpawn; break; case "authme": spawnLoc = getSpawn(); break; default: // ignore } if (spawnLoc != null) { logger.debug("Spawn location determined as `{0}` for world `{1}`", spawnLoc, world.getName()); return spawnLoc; } } logger.debug("Fall back to default world spawn location. World: `{0}`", world.getName()); return world.getSpawnLocation(); // return default location } /** * Checks if a given location is a valid spawn point [!= (0,0,0)]. * * @param location The location to check * * @return True upon success, false otherwise */ private boolean isValidSpawnPoint(Location location) { if (location.getX() == 0 && location.getY() == 0 && location.getZ() == 0) { return false; } return true; } /** * Save the location under the given prefix. * * @param prefix The prefix to save the spawn under * @param location The location to persist * * @return True upon success, false otherwise */ private boolean setLocation(String prefix, Location location) { if (location != null && location.getWorld() != null) { authMeConfiguration.set(prefix + ".world", location.getWorld().getName()); authMeConfiguration.set(prefix + ".x", location.getX()); authMeConfiguration.set(prefix + ".y", location.getY()); authMeConfiguration.set(prefix + ".z", location.getZ()); authMeConfiguration.set(prefix + ".yaw", location.getYaw()); authMeConfiguration.set(prefix + ".pitch", location.getPitch()); return saveAuthMeConfig(); } return false; } private boolean saveAuthMeConfig() { try { authMeConfiguration.save(authMeConfigurationFile); return true; } catch (IOException e) { logger.logException("Could not save spawn config (" + authMeConfigurationFile + ")", e); } return false; } /** * Return player's location if player is alive, or player's spawn location if dead. * * @param player player to retrieve * * @return location of the given player if alive, spawn location if dead. */ public Location getPlayerLocationOrSpawn(Player player) { if (player.getHealth() <= 0.0) { return getSpawnLocation(player); } return player.getLocation(); } /** * Build a {@link Location} object from the given path in the file configuration. * * @param configuration The file configuration to read from * @param pathPrefix The path to get the spawn point from * * @return Location corresponding to the values in the path */ private static Location getLocationFromConfiguration(FileConfiguration configuration, String pathPrefix) { if (containsAllSpawnFields(configuration, pathPrefix)) { String prefix = pathPrefix + "."; String worldName = configuration.getString(prefix + "world"); World world = Bukkit.getWorld(worldName); if (!StringUtils.isBlank(worldName) && world != null) { return new Location(world, configuration.getDouble(prefix + "x"), configuration.getDouble(prefix + "y"), configuration.getDouble(prefix + "z"), getFloat(configuration, prefix + "yaw"), getFloat(configuration, prefix + "pitch")); } } return null; } /** * Build a {@link Location} object based on the CMI configuration. * * @param configuration The CMI file configuration to read from * * @return Location corresponding to the values in the path */ private static Location getLocationFromCmiConfiguration(FileConfiguration configuration) { final String pathPrefix = "Spawn.Main"; if (isLocationCompleteInCmiConfig(configuration, pathPrefix)) { String prefix = pathPrefix + "."; String worldName = configuration.getString(prefix + "World"); World world = Bukkit.getWorld(worldName); if (!StringUtils.isBlank(worldName) && world != null) { return new Location(world, configuration.getDouble(prefix + "X"), configuration.getDouble(prefix + "Y"), configuration.getDouble(prefix + "Z"), getFloat(configuration, prefix + "Yaw"), getFloat(configuration, prefix + "Pitch")); } } return null; } /** * Return whether the file configuration contains all fields necessary to define a spawn * under the given path. * * @param configuration The file configuration to use * @param pathPrefix The path to verify * * @return True if all spawn fields are present, false otherwise */ private static boolean containsAllSpawnFields(FileConfiguration configuration, String pathPrefix) { String[] fields = {"world", "x", "y", "z", "yaw", "pitch"}; for (String field : fields) { if (!configuration.contains(pathPrefix + "." + field)) { return false; } } return true; } /** * Return whether the CMI file configuration contains all spawn fields under the given path. * * @param cmiConfiguration The file configuration from CMI * @param pathPrefix The path to verify * * @return True if all spawn fields are present, false otherwise */ private static boolean isLocationCompleteInCmiConfig(FileConfiguration cmiConfiguration, String pathPrefix) { String[] fields = {"World", "X", "Y", "Z", "Yaw", "Pitch"}; for (String field : fields) { if (!cmiConfiguration.contains(pathPrefix + "." + field)) { return false; } } return true; } /** * Retrieve a property as a float from the given file configuration. * * @param configuration The file configuration to use * @param path The path of the property to retrieve * * @return The float */ private static float getFloat(FileConfiguration configuration, String path) { Object value = configuration.get(path); // This behavior is consistent with FileConfiguration#getDouble return (value instanceof Number) ? ((Number) value).floatValue() : 0; } }