/* * 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.borders.MapShape; 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.Set; public class GridSpawnPointsGenerator implements SpawnPointsGenerator { private final UHCReloaded p = UHCReloaded.get(); /** * Generates spawn points in a grid. * * @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; // To check the possibility of the generation, we calculates the maximal number of columns/rows // possible, based on the minimal distance between two points. final int maxColumnsCount = (int) Math.ceil(usedRegionDiameter / minimalDistanceBetweenTwoPoints); // The points are on a grid int neededColumnsCount = (int) Math.ceil(Math.sqrt(spawnCount)); if (p.getBorderManager().getMapShape() == MapShape.CIRCULAR) { // If the border is circular, the distance between two points needs to be decreased. // The space available is divided by PI/4, so the column count is multiplied by // this number. neededColumnsCount = (int) Math.ceil(neededColumnsCount / (Math.PI / 4)); } // IS impossible. if (neededColumnsCount > maxColumnsCount) { throw new CannotGenerateSpawnPointsException("Cannot generate spawn points on a grid: not enough space."); } // If the map is circular, the generation may be impossible, because this check was // performed for a squared map. // The test will be done after the generation. // We generates the points on a grid in squares, starting by the biggest square. final int distanceBetweenTwoPoints = (int) ((double) usedRegionDiameter / ((double) (neededColumnsCount))); // Check related to the case the column count was increased. if (distanceBetweenTwoPoints < minimalDistanceBetweenTwoPoints) { throw new CannotGenerateSpawnPointsException("Cannot generate spawn points on a grid: not enough space."); } int countGeneratedPoints = 0; final HashSet<Location> generatedPoints = new HashSet<>(); final int halfDiameter = (int) Math.floor(usedRegionDiameter / 2); Integer currentSquareSize = usedRegionDiameter; Location currentSquareStartPoint = new Location(world, xCenter + halfDiameter, 0, zCenter - halfDiameter); Location currentPoint; // Represents the location to add on each side of the squares final Location[] addOnSide = new Location[4]; addOnSide[0] = new Location(world, -distanceBetweenTwoPoints, 0, 0); // North side, direction east addOnSide[1] = new Location(world, 0, 0, distanceBetweenTwoPoints); // East side, direction south addOnSide[2] = new Location(world, distanceBetweenTwoPoints, 0, 0); // South side, direction west addOnSide[3] = new Location(world, 0, 0, -distanceBetweenTwoPoints); // West side, direction north // We generates the points until there isn't any point left to place. The loop will be broken. // On each step of this loop, a square is generated. generationLoop: while (true) { currentPoint = currentSquareStartPoint.clone(); // First point if (p.getBorderManager().isInsideBorder(currentPoint, regionDiameter) && UHUtils.searchSafeSpot(currentPoint) != null) { generatedPoints.add(currentPoint.clone()); countGeneratedPoints++; if (countGeneratedPoints >= spawnCount) { break; } } // A step for each side, j is the side (see addOnSide). for (int j = 0; j < 4; j++) { int plottedSize = 0; while (plottedSize < currentSquareSize) { currentPoint.add(addOnSide[j]); plottedSize += distanceBetweenTwoPoints; // Inside the border? if (!p.getBorderManager().isInsideBorder(currentPoint, regionDiameter)) { continue; } final Block surfaceAirBlock = world.getHighestBlockAt(currentPoint); 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(currentPoint.clone()); countGeneratedPoints++; if (countGeneratedPoints >= spawnCount) { break generationLoop; } } } // This square is complete; preparing the next one... currentSquareSize -= 2 * distanceBetweenTwoPoints; currentSquareStartPoint.add(new Location(world, -distanceBetweenTwoPoints, 0, distanceBetweenTwoPoints)); if (currentSquareSize < distanceBetweenTwoPoints) { // This may happens if we generates the points for a circular world break; } } // If the generation was broken (circular world, not enough positions), // the generation was incomplete. if (countGeneratedPoints >= spawnCount) { // Generation OK return generatedPoints; } else { throw new CannotGenerateSpawnPointsException("Cannot generate the spawn points: not enough space."); } } }