/* * Copyright (C) 2014 eccentric_nz * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package me.eccentric_nz.TARDIS.travel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.Set; import me.eccentric_nz.TARDIS.TARDIS; import me.eccentric_nz.TARDIS.TARDISConstants; import me.eccentric_nz.TARDIS.api.Parameters; import me.eccentric_nz.TARDIS.database.ResultSetPlayerPrefs; import me.eccentric_nz.TARDIS.database.ResultSetTravellers; import me.eccentric_nz.TARDIS.enumeration.COMPASS; import me.eccentric_nz.TARDIS.enumeration.FLAG; import me.eccentric_nz.TARDIS.utility.TARDISBlockSetters; import me.eccentric_nz.TARDIS.utility.TARDISMessage; import me.eccentric_nz.TARDIS.utility.TARDISStaticUtils; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; /** * All things related to time travel. * * All TARDISes built after a certain point, including the Type 40 the Doctor * uses, have a mathematically modelled duplicate of the Eye of harmony with all * its attendant features. * * @author eccentric_nz */ public class TARDISTimeTravel { private static final int[] startLoc = new int[6]; private Location dest; private final TARDIS plugin; private final int attempts; public TARDISTimeTravel(TARDIS plugin) { this.plugin = plugin; // add good materials this.attempts = plugin.getConfig().getInt("travel.random_attempts"); } /** * Retrieves a random location determined from the TARDIS repeater or * terminal settings. * * @param p a player object used to check permissions against. * @param rx the data bit setting of the x-repeater, this determines the * distance in the x direction. * @param rz the data bit setting of the z-repeater, this determines the * distance in the z direction. * @param ry the data bit setting of the y-repeater, this determines the * multiplier for both the x and z directions. * @param d the direction the TARDIS Police Box faces. * @param e the environment(s) the player has chosen (or is allowed) to * travel to. * @param this_world the world the Police Box is currently in * @param malfunction whether there should be a malfunction * @param current * @return a random Location */ @SuppressWarnings("deprecation") public Location randomDestination(Player p, byte rx, byte rz, byte ry, COMPASS d, String e, World this_world, boolean malfunction, Location current) { int startx, starty, startz, resetx, resetz, listlen; World randworld; int count; Random rand = new Random(); // get max_radius from config int max = plugin.getConfig().getInt("travel.tp_radius"); int quarter = (max + 4 - 1) / 4; int range = quarter + 1; int wherex = 0, highest = 252, wherez = 0; // get worlds Set<String> worldlist = plugin.getConfig().getConfigurationSection("worlds").getKeys(false); List<World> allowedWorlds = new ArrayList<World>(); if (e.equals("THIS")) { allowedWorlds.add(this_world); } else { for (String o : worldlist) { World ww = plugin.getServer().getWorld(o); if (ww != null) { String env = ww.getEnvironment().toString(); // Catch all non-nether and non-end ENVIRONMENT types and assume they're normal if (!env.equals("NETHER") && !env.equals("THE_END")) { env = "NORMAL"; } if (e.equalsIgnoreCase(env)) { if (plugin.getConfig().getBoolean("travel.include_default_world") || !plugin.getConfig().getBoolean("creation.default_world")) { if (plugin.getConfig().getBoolean("worlds." + o) || malfunction) { allowedWorlds.add(ww); } } else { if (!o.equals(plugin.getConfig().getString("creation.default_world_name"))) { if (plugin.getConfig().getBoolean("worlds." + o) || malfunction) { allowedWorlds.add(ww); } } } } // remove the world the Police Box is in if (allowedWorlds.size() > 1 && allowedWorlds.contains(this_world)) { allowedWorlds.remove(this_world); } // remove the world if the player doesn't have permission if (allowedWorlds.size() > 1 && plugin.getConfig().getBoolean("travel.per_world_perms") && !p.hasPermission("tardis.travel." + o)) { allowedWorlds.remove(ww); } } } } listlen = allowedWorlds.size(); // random world randworld = allowedWorlds.get(rand.nextInt(listlen)); if (randworld.getEnvironment().equals(Environment.NETHER)) { for (int n = 0; n < attempts; n++) { wherex = randomX(rand, range, quarter, rx, ry, e, current); wherez = randomZ(rand, range, quarter, rz, ry, e, current); if (safeNether(randworld, wherex, wherez, d, p)) { break; } } } if (randworld.getEnvironment().equals(Environment.THE_END)) { if (plugin.getPlanetsConfig().getBoolean("planets." + randworld.getName() + ".void")) { // any location will do! int voidx = randomX(rand, range, quarter, rx, ry, e, current); int voidy = rand.nextInt(240) + 5; int voidz = randomZ(rand, range, quarter, rz, ry, e, current); return new Location(randworld, voidx, voidy, voidz); } for (int n = 0; n < attempts; n++) { wherex = rand.nextInt(240); wherez = rand.nextInt(240); wherex -= 120; wherez -= 120; // get the spawn point Location endSpawn = randworld.getSpawnLocation(); highest = randworld.getHighestBlockYAt(endSpawn.getBlockX() + wherex, endSpawn.getBlockZ() + wherez); if (highest > 40) { Block currentBlock = randworld.getBlockAt(wherex, highest, wherez); Location chunk_loc = currentBlock.getLocation(); if (plugin.getPluginRespect().getRespect(chunk_loc, new Parameters(p, FLAG.getNoMessageFlags()))) { while (!randworld.getChunkAt(chunk_loc).isLoaded()) { randworld.getChunkAt(chunk_loc).load(); } // get start location for checking there is enough space int gsl[] = getStartLocation(chunk_loc, d); startx = gsl[0]; resetx = gsl[1]; starty = chunk_loc.getBlockY() + 1; startz = gsl[2]; resetz = gsl[3]; count = safeLocation(startx, starty, startz, resetx, resetz, randworld, d); } else { count = 1; } } else { count = 1; } if (count == 0) { break; } } dest = (highest > 0) ? new Location(randworld, wherex, highest, wherez) : null; } // Assume every non-nether/non-END world qualifies as NORMAL. if (!randworld.getEnvironment().equals(Environment.NETHER) && !randworld.getEnvironment().equals(Environment.THE_END)) { if (plugin.getPlanetsConfig().getBoolean("planets." + randworld.getName() + ".void")) { // any location will do! int voidx = randomX(rand, range, quarter, rx, ry, e, current); int voidy = rand.nextInt(240) + 5; int voidz = randomZ(rand, range, quarter, rz, ry, e, current); return new Location(randworld, voidx, voidy, voidz); } long timeout = System.currentTimeMillis() + (plugin.getConfig().getLong("travel.timeout") * 1000); while (true) { if (System.currentTimeMillis() < timeout) { // reset count count = 0; // randomX(Random rand, int range, int quarter, byte rx, byte ry, int max) wherex = randomX(rand, range, quarter, rx, ry, e, current); wherez = randomZ(rand, range, quarter, rz, ry, e, current); highest = randworld.getHighestBlockYAt(wherex, wherez); if (highest > 3) { Block currentBlock = randworld.getBlockAt(wherex, highest, wherez); if ((currentBlock.getRelative(BlockFace.DOWN).getType().equals(Material.WATER) || currentBlock.getRelative(BlockFace.DOWN).getType().equals(Material.STATIONARY_WATER)) && plugin.getConfig().getBoolean("travel.land_on_water") == false) { // check if submarine is on HashMap<String, Object> wheres = new HashMap<String, Object>(); wheres.put("uuid", p.getUniqueId().toString()); ResultSetPlayerPrefs rsp = new ResultSetPlayerPrefs(plugin, wheres); if (rsp.resultSet()) { if (rsp.isSubmarineOn() && TARDISStaticUtils.isOceanBiome(currentBlock.getBiome())) { // get submarine location TARDISMessage.send(p, "SUB_SEARCH"); Location underwater = submarine(currentBlock, d); if (underwater != null) { // get TARDIS id HashMap<String, Object> wherep = new HashMap<String, Object>(); wherep.put("uuid", p.getUniqueId().toString()); ResultSetTravellers rst = new ResultSetTravellers(plugin, wherep, false); if (rst.resultSet()) { plugin.getTrackerKeeper().getSubmarine().add(rst.getTardis_id()); } return underwater; } else { count = 1; } } else if (!rsp.isSubmarineOn()) { count = 1; } } else { count = 1; } } else { if (TARDISConstants.GOOD_MATERIALS.contains(currentBlock.getType())) { currentBlock = currentBlock.getRelative(BlockFace.DOWN); } Location chunk_loc = currentBlock.getLocation(); if (plugin.getPluginRespect().getRespect(chunk_loc, new Parameters(p, FLAG.getNoMessageFlags()))) { while (!randworld.getChunkAt(chunk_loc).isLoaded()) { randworld.getChunkAt(chunk_loc).load(); } // get start location for checking there is enough space int gsl[] = getStartLocation(chunk_loc, d); startx = gsl[0]; resetx = gsl[1]; starty = chunk_loc.getBlockY() + 1; startz = gsl[2]; resetz = gsl[3]; count = safeLocation(startx, starty, startz, resetx, resetz, randworld, d); } else { count = 1; } } } else { count = 1; } if (count == 0) { break; } } else { if (!plugin.getPluginRespect().getRespect(new Location(randworld, wherex, highest, wherez), new Parameters(p, FLAG.getNoMessageFlags()))) { return null; } else { highest = plugin.getConfig().getInt("travel.timeout_height"); break; } } } dest = new Location(randworld, wherex, highest, wherez); } return dest; } /** * Checks if a random location is safe for the TARDIS Police Box to land at. * The Police Box requires a clear 4 x 3 x 4 (d x w x h) area. * * @param startx a starting position in the x direction. * @param starty a starting position in the y direction. * @param startz a starting position in the z direction. * @param resetx a copy of the starting x position to return to. * @param resetz a copy of the starting z position to return to. * @param w the world the location check will take place in. * @param d the direction the Police Box is facing. * @return the number of unsafe blocks */ @SuppressWarnings("deprecation") public static int safeLocation(int startx, int starty, int startz, int resetx, int resetz, World w, COMPASS d) { int level, row, col, rowcount, colcount, count = 0; switch (d) { case EAST: case WEST: rowcount = 3; colcount = 4; break; default: rowcount = 4; colcount = 3; break; } for (level = 0; level < 4; level++) { for (row = 0; row < rowcount; row++) { for (col = 0; col < colcount; col++) { Material mat = w.getBlockAt(startx, starty, startz).getType(); if (!TARDISConstants.GOOD_MATERIALS.contains(mat)) { // check for siege cube if (TARDIS.plugin.getConfig().getBoolean("siege.enabled") && mat.equals(Material.HUGE_MUSHROOM_1) && w.getBlockAt(startx, starty, startz).getData() == (byte) 14) { continue; } else { count++; } } startx += 1; } startx = resetx; startz += 1; } startz = resetz; starty += 1; } return count; } /** * Checks if a location is safe for the TARDIS Police Box to land at. Used * for debugging purposes only. The Police Box requires a clear 4 x 3 x 4 (d * x w x h) area. * * @param loc * @param d the direction the Police Box is facing. */ public void testSafeLocation(Location loc, COMPASS d) { final World w = loc.getWorld(); final int starty = loc.getBlockY(); int sx, sz; switch (d) { case EAST: sx = loc.getBlockX() - 2; sz = loc.getBlockZ() - 1; break; case SOUTH: sx = loc.getBlockX() - 1; sz = loc.getBlockZ() - 2; break; default: sx = loc.getBlockX() - 1; sz = loc.getBlockZ() - 1; break; } int row, col; switch (d) { case EAST: case WEST: row = 2; col = 3; break; default: row = 3; col = 2; break; } final int r = row; final int c = col; final int startx = sx; final int startz = sz; TARDISBlockSetters.setBlock(w, startx, starty, startz, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty, startz + row, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx + col, starty, startz, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx + col, starty, startz + row, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty + 3, startz, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx + col, starty + 3, startz, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty + 3, startz + row, 80, (byte) 0); TARDISBlockSetters.setBlock(w, startx + col, starty + 3, startz + row, 80, (byte) 0); plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { @Override public void run() { TARDISBlockSetters.setBlock(w, startx, starty, startz, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty, startz + r, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx + c, starty, startz, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx + c, starty, startz + r, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty + 3, startz, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx + c, starty + 3, startz, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx, starty + 3, startz + r, 0, (byte) 0); TARDISBlockSetters.setBlock(w, startx + c, starty + 3, startz + r, 0, (byte) 0); } }, 300L); } /** * Gets the starting location for safe location checking. * * @param loc a location object to check. * @param d the direction the Police Box is facing. * @return an array containing x and z coordinates */ public static int[] getStartLocation(Location loc, COMPASS d) { switch (d) { case EAST: startLoc[0] = loc.getBlockX() - 2; startLoc[1] = startLoc[0]; startLoc[2] = loc.getBlockZ() - 1; startLoc[3] = startLoc[2]; break; case SOUTH: startLoc[0] = loc.getBlockX() - 1; startLoc[1] = startLoc[0]; startLoc[2] = loc.getBlockZ() - 2; startLoc[3] = startLoc[2]; break; default: startLoc[0] = loc.getBlockX() - 1; startLoc[1] = startLoc[0]; startLoc[2] = loc.getBlockZ() - 1; startLoc[3] = startLoc[2]; break; } return startLoc; } /** * Checks whether a NETHER location is safe to land at. * * @param nether a Nether world to search in. * @param wherex an x co-ordinate. * @param wherez a z co-ordinate. * @param d the direction the Police Box is facing. * @param p the player to check permissions for * @return true or false */ @SuppressWarnings("deprecation") public boolean safeNether(World nether, int wherex, int wherez, COMPASS d, Player p) { boolean safe = false; int startx, starty, startz, resetx, resetz, count; int wherey = 100; Block startBlock = nether.getBlockAt(wherex, wherey, wherez); while (!startBlock.getType().equals(Material.AIR)) { startBlock = startBlock.getRelative(BlockFace.DOWN); } int air = 0; while (startBlock.getType().equals(Material.AIR) && startBlock.getLocation().getBlockY() > 30) { startBlock = startBlock.getRelative(BlockFace.DOWN); air++; } Material mat = startBlock.getType(); if (plugin.getGeneralKeeper().getGoodNether().contains(mat) && air >= 4) { Location netherLocation = startBlock.getLocation(); int netherLocY = netherLocation.getBlockY(); netherLocation.setY(netherLocY + 1); if (plugin.getPluginRespect().getRespect(netherLocation, new Parameters(p, FLAG.getNoMessageFlags()))) { // get start location for checking there is enough space int gsl[] = getStartLocation(netherLocation, d); startx = gsl[0]; resetx = gsl[1]; starty = netherLocation.getBlockY(); startz = gsl[2]; resetz = gsl[3]; count = safeLocation(startx, starty, startz, resetx, resetz, nether, d); } else { count = 1; } if (count == 0) { safe = true; dest = netherLocation; } } return safe; } /** * Returns a random positive or negative x integer. * * @param rand an object of type Random. * @param range the maximum the random number can be. * @param quarter one fourth of the max_distance config option. * @param rx the data bit of the x-repeater setting. * @param ry the data bit of the y-repeater setting. * @param max the max_distance config option. */ private int randomX(Random rand, int range, int quarter, byte rx, byte ry, String e, Location l) { int currentx = (e.equals("THIS")) ? l.getBlockX() : 0; int wherex; wherex = rand.nextInt(range); // add the distance from the x and z repeaters if (rx <= 3) { wherex += quarter; } if (rx >= 4 && rx <= 7) { wherex += (quarter * 2); } if (rx >= 8 && rx <= 11) { wherex += (quarter * 3); } if (rx >= 12 && rx <= 15) { wherex += (quarter * 4); } // add chance of negative values if (rand.nextInt(2) == 1) { wherex = 0 - wherex; } // use multiplier based on position of third (y) repeater if (ry >= 4 && ry <= 7) { wherex *= 2; } if (ry >= 8 && ry <= 11) { wherex *= 3; } if (ry >= 12 && ry <= 15) { wherex *= 4; } return wherex + currentx; } /** * Returns a random positive or negative z integer. * * @param rand an object of type Random. * @param range the maximum the random number can be. * @param quarter one fourth of the max_distance config option. * @param rz the data bit of the x-repeater setting. * @param ry the data bit of the y-repeater setting. * @param max the max_distance config option. */ private int randomZ(Random rand, int range, int quarter, byte rz, byte ry, String e, Location l) { int currentz = (e.equals("THIS")) ? l.getBlockZ() : 0; int wherez; wherez = rand.nextInt(range); // add the distance from the x and z repeaters if (rz <= 3) { wherez += quarter; } if (rz >= 4 && rz <= 7) { wherez += (quarter * 2); } if (rz >= 8 && rz <= 11) { wherez += (quarter * 3); } if (rz >= 12 && rz <= 15) { wherez += (quarter * 4); } // add chance of negative values if (rand.nextInt(2) == 1) { wherez = 0 - wherez; } // use multiplier based on position of third (y) repeater if (ry >= 4 && ry <= 7) { wherez *= 2; } if (ry >= 8 && ry <= 11) { wherez *= 3; } if (ry >= 12 && ry <= 15) { wherez *= 4; } return wherez + currentz; } @SuppressWarnings("deprecation") public Location submarine(Block b, COMPASS d) { Block block = b; while (true) { block = block.getRelative(BlockFace.DOWN); if (!block.getType().equals(Material.STATIONARY_WATER) && !block.getType().equals(Material.WATER) && !block.getType().equals(Material.ICE)) { break; } } Location loc = block.getRelative(BlockFace.UP).getLocation(); for (int n = 0; n < attempts; n++) { if (isSafeSubmarine(loc, d)) { return loc; } else { loc.setY(loc.getY() + 1); } } return (isSafeSubmarine(loc, d)) ? loc : null; } @SuppressWarnings("deprecation") public boolean isSafeSubmarine(Location l, COMPASS d) { int[] s = getStartLocation(l, d); int level, row, col, rowcount, colcount, count = 0; int starty = l.getBlockY(); switch (d) { case EAST: case WEST: rowcount = 3; colcount = 4; break; default: rowcount = 4; colcount = 3; break; } for (level = 0; level < 4; level++) { for (row = 0; row < rowcount; row++) { for (col = 0; col < colcount; col++) { Material mat = l.getWorld().getBlockAt(s[0], starty, s[2]).getType(); if (!TARDISConstants.GOOD_WATER.contains(mat)) { count++; } s[0] += 1; } s[0] = s[1]; s[2] += 1; } s[2] = s[3]; starty += 1; } return (count == 0); } }