/* * Copyright or © or Copr. Amaury Carrade (2014 - 2016) * * http://amaury.carrade.eu * * This software is governed by the CeCILL-B license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL-B * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". * * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. * * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, * that may mean that it is complicated to manipulate, and that also * therefore means that it is reserved for developers and experienced * professionals having in-depth computer knowledge. Users are therefore * encouraged to load and test the software's suitability as regards their * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-B license and that you accept its terms. */ package eu.carrade.amaury.UHCReloaded.spawns.generators; import eu.carrade.amaury.UHCReloaded.UHCReloaded; import eu.carrade.amaury.UHCReloaded.spawns.exceptions.CannotGenerateSpawnPointsException; import eu.carrade.amaury.UHCReloaded.utils.UHUtils; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import java.util.HashSet; import java.util.Random; import java.util.Set; public class CircularSpawnPointsGenerator implements SpawnPointsGenerator { private final UHCReloaded p = UHCReloaded.get(); /** * Generates spawn points in concentric circles. * * @param world The world where the spawn points will be generated. * @param spawnCount The number of spawn points to generate. * @param regionDiameter The diameter of the region where the spawn points will be generated.<br> * This is limited by the size of the map. This will be seen as the diameter of a circular or * of a squared map, following the shape of the world set in the configuration. * @param minimalDistanceBetweenTwoPoints The minimal distance between two points. * @param xCenter The x coordinate of the point in the center of the region where the points will be generated. * @param zCenter The z coordinate of the point in the center of the region where the points will be generated. * @param avoidWater True if the generation have to avoid the water. * * @return The spawn points generated. * * @throws CannotGenerateSpawnPointsException In case of fail. */ @Override public Set<Location> generate(final World world, final int spawnCount, final int regionDiameter, final int minimalDistanceBetweenTwoPoints, final double xCenter, final double zCenter, final boolean avoidWater) throws CannotGenerateSpawnPointsException { // We starts the generation on a smaller grid, to avoid false outside tests if the point is on the edge final int usedRegionDiameter = regionDiameter - 1; int countGeneratedPoints = 0; final Set<Location> generatedPoints = new HashSet<>(); int currentCircleDiameter = usedRegionDiameter; // The generation loop. Each step generates a circle. generationLoop: while (currentCircleDiameter >= minimalDistanceBetweenTwoPoints) { // First step. We want to know if all the points left can be in one circle. // We calculates the maximal number of points in a circle, taking into account the // minimal distance between two points. // The link between the angle between two points and the fly distance between them // is, where R is the radius, d the fly distance, and a the angle: // a = 2 Arcsin((d/2)/R) // (Just draw the situation, you'll see.) final double denseCircleAngle = 2 * Math.asin(((double) minimalDistanceBetweenTwoPoints / 2) / ((double) currentCircleDiameter / 2)); final int pointsPerDenseCircles = (int) Math.floor(2 * Math.PI / denseCircleAngle); final double angleBetweenTwoPoints; // Not all the points can be in this circle. We generate the densiest circle. if (pointsPerDenseCircles < spawnCount - countGeneratedPoints) { angleBetweenTwoPoints = 2 * Math.PI / ((double) pointsPerDenseCircles); } // All the remaining points can be in this circle. We generates the less dense circle with // these points. else { angleBetweenTwoPoints = 2 * Math.PI / ((double) (spawnCount - countGeneratedPoints)); } // Let's generate these points. final double startAngle = (new Random()).nextDouble() * 2 * Math.PI; double currentAngle = startAngle; while (currentAngle <= 2 * Math.PI - angleBetweenTwoPoints + startAngle) { // The coordinates of a point in the circle. // Cf. your trigonometry! ;) Location point = new Location( world, (currentCircleDiameter / 2) * Math.cos(currentAngle) + xCenter, 0, (currentCircleDiameter / 2) * Math.sin(currentAngle) + zCenter ); currentAngle += angleBetweenTwoPoints; // Just in case if (!p.getBorderManager().isInsideBorder(point, regionDiameter)) { continue; } final Block surfaceAirBlock = world.getHighestBlockAt(point); final Block surfaceBlock = surfaceAirBlock.getRelative(BlockFace.DOWN); // Safe spot available? if (!UHUtils.isSafeSpot(surfaceAirBlock.getLocation())) { continue; // not safe: nope } // Not above the water? if (avoidWater) { if (surfaceBlock.getType() == Material.WATER || surfaceBlock.getType() == Material.STATIONARY_WATER) { continue; } } generatedPoints.add(point); countGeneratedPoints++; if (countGeneratedPoints >= spawnCount) { break generationLoop; } } // So, this circle is done. // We prepares the next one. currentCircleDiameter -= 2 * minimalDistanceBetweenTwoPoints; } // Generation done or failed (not enough space)? if (generatedPoints.size() < spawnCount) { // Failed! throw new CannotGenerateSpawnPointsException("Cannot generate the spawn point in circles: not enough space"); } else { return generatedPoints; } } }