/*******************************************************************************
* This file is part of ASkyBlock.
*
* ASkyBlock 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.
*
* ASkyBlock 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 ASkyBlock. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package com.wasteofplastic.askyblock;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import com.wasteofplastic.askyblock.util.Util;
/**
* A class that calculates finds a safe spot asynchronously and then teleports the player there.
* @author tastybento
*
*/
public class SafeSpotTeleport {
//private NMSAbstraction nms;
//private ASkyBlock plugin;
/**
* Teleport to a safe place and if it fails, show a failure message
* @param plugin
* @param player
* @param l
* @param failureMessage
*/
public SafeSpotTeleport(final ASkyBlock plugin, final Entity player, final Location l, final String failureMessage) {
new SafeSpotTeleport(plugin, player, l, 1, failureMessage, false);
}
/**
* Teleport to a safe place and set home
* @param plugin
* @param player
* @param l
* @param number
*/
public SafeSpotTeleport(final ASkyBlock plugin, final Entity player, final Location l, final int number) {
new SafeSpotTeleport(plugin, player, l, number, "", true);
}
/**
* Teleport to a safe spot on an island
* @param plugin
* @param player
* @param l
*/
public SafeSpotTeleport(final ASkyBlock plugin, final Entity player, final Location l) {
new SafeSpotTeleport(plugin, player, l, 1, "", false);
}
/**
* Teleport to a safe spot on an island
* @param plugin
* @param entity
* @param islandLoc
*/
public SafeSpotTeleport(final ASkyBlock plugin, final Entity entity, final Location islandLoc, final int homeNumber, final String failureMessage, final boolean setHome) {
//this.plugin = plugin;
//plugin.getLogger().info("DEBUG: running safe spot");
// Get island
Island island = plugin.getGrid().getIslandAt(islandLoc);
if (island != null) {
final World world = islandLoc.getWorld();
// Get the chunks
List<ChunkSnapshot> chunkSnapshot = new ArrayList<ChunkSnapshot>();
// Add the center chunk
chunkSnapshot.add(island.getCenter().toVector().toLocation(world).getChunk().getChunkSnapshot());
// Add immediately adjacent chunks
for (int x = islandLoc.getChunk().getX()-1; x <= islandLoc.getChunk().getX()+1; x++) {
for (int z = islandLoc.getChunk().getZ()-1; z <= islandLoc.getChunk().getZ()+1; z++) {
if (x != islandLoc.getChunk().getX() || z != islandLoc.getChunk().getZ()) {
chunkSnapshot.add(world.getChunkAt(x, z).getChunkSnapshot());
}
}
}
// Add the rest of the island protected area
for (int x = island.getMinProtectedX() /16; x <= (island.getMinProtectedX() + island.getProtectionSize() - 1)/16; x++) {
for (int z = island.getMinProtectedZ() /16; z <= (island.getMinProtectedZ() + island.getProtectionSize() - 1)/16; z++) {
// This includes the center spots again, so is not as efficient...
chunkSnapshot.add(world.getChunkAt(x, z).getChunkSnapshot());
}
}
//plugin.getLogger().info("DEBUG: size of chunk ss = " + chunkSnapshot.size());
final List<ChunkSnapshot> finalChunk = chunkSnapshot;
int maxHeight = world.getMaxHeight() - 2;
if (world.getEnvironment().equals(Environment.NETHER)) {
// We need to ignore the roof
maxHeight -= 20;
}
final int worldHeight = maxHeight;
//plugin.getLogger().info("DEBUG:world height = " + worldHeight);
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() {
@SuppressWarnings("deprecation")
@Override
public void run() {
// Find a safe spot, defined as a solid block, with 2 air spaces above it
//long time = System.nanoTime();
int x = 0;
int y = 0;
int z = 0;
ChunkSnapshot safeChunk = null;
ChunkSnapshot portalChunk = null;
boolean safeSpotFound = false;
/*
try {
nms = checkVersion();
} catch (Exception e) {
e.printStackTrace();
}*/
Vector safeSpotInChunk = null;
Vector portalPart = null;
double distance = 0D;
double safeDistance = 0D;
for (ChunkSnapshot chunk: finalChunk) {
for (x = 0; x< 16; x++) {
for (z = 0; z < 16; z++) {
// Work down from the entry point up
for (y = Math.min(chunk.getHighestBlockYAt(x, z), worldHeight); y >= 0; y--) {
//System.out.println("Trying " + (16 * chunk.getX() + x) + " " + y + " " + (16 * chunk.getZ() + z));
// Check for portal - only if this is not a safe home search
if (!setHome && chunk.getBlockTypeId(x, y, z) == Material.PORTAL.getId()) {
if (portalPart == null || (distance > islandLoc.toVector().distanceSquared(new Vector(x,y,z)))) {
// First one found or a closer one, save the chunk the position and the distance
portalChunk = chunk;
portalPart = new Vector(x,y,z);
distance = portalPart.distanceSquared(islandLoc.toVector());
}
}
// Check for safe spot, but only if it is closer than one we have found already
if (!safeSpotFound || (safeDistance > islandLoc.toVector().distanceSquared(new Vector(x,y,z)))) {
// No safe spot yet, or closer distance
if (checkBlock(chunk,x,y,z, worldHeight)) {
safeChunk = chunk;
safeSpotFound = true;
safeSpotInChunk = new Vector(x,y,z);
safeDistance = islandLoc.toVector().distanceSquared(safeSpotInChunk);
}
}
}
} //end z
} // end x
//if (safeSpotFound) {
//System.out.print("DEBUG: safe spot found " + safeSpotInChunk.toString());
//break search;
//}
}
// End search
// Check if the portal is safe (it should be)
if (portalPart != null) {
//System.out.print("DEBUG: Portal found");
// There is a portal available, but is it safe?
// Get the lowest portal spot
x = portalPart.getBlockX();
y = portalPart.getBlockY();
z = portalPart.getBlockZ();
while (portalChunk.getBlockTypeId(x,y,z) == Material.PORTAL.getId()) {
y--;
}
//System.out.print("DEBUG: Portal teleport loc = " + (16 * portalChunk.getX() + x) + "," + (y) + "," + (16 * portalChunk.getZ() + z));
// Now check if this is a safe location
if (checkBlock(portalChunk,x,y,z, worldHeight)) {
// Yes, so use this instead of the highest location
//System.out.print("DEBUG: Portal is safe");
safeSpotFound = true;
safeSpotInChunk = new Vector(x,y,z);
safeChunk = portalChunk;
// TODO: Add safe portal spot to island
}
}
//System.out.print("Seconds = " + ((System.nanoTime() - time) * 0.000000001));
if (safeChunk != null && safeSpotFound) {
//final Vector spot = new Vector((16 *currentChunk.getX()) + x + 0.5D, y +1, (16 * currentChunk.getZ()) + z + 0.5D)
final Vector spot = new Vector((16 *safeChunk.getX()) + 0.5D, 1, (16 * safeChunk.getZ()) + 0.5D).add(safeSpotInChunk);
// Return to main thread and teleport the player
plugin.getServer().getScheduler().runTask(plugin, new Runnable() {
@Override
public void run() {
Location destination = spot.toLocation(islandLoc.getWorld());
//plugin.getLogger().info("DEBUG: safe spot found = " + destination);
// Create a portal
// TODO Add if statement here
//Block b = player.getLocation().getBlock();
//if (b.getType() != Material.PORTAL) {
/*
if (world.equals(ASkyBlock.getNetherWorld())) {
for (int x = -1; x < 3; x++) {
for (int y = -1; y< 4; y++) {
Location l = new Location(islandLoc.getWorld(), destination.getBlockX() + x, destination.getBlockY() + y, destination.getBlockZ() -1);
if (x == -1 || x == 2 || y == -1 || y == 3) {
//nms.setBlockSuperFast(l.getBlock(), Material.OBSIDIAN.getId(), (byte)0, false);
//l.getBlock().setType(Material.OBSIDIAN);
//plugin.getLogger().info("DEBUG: obsidian at "+ l);
} else {
//plugin.getLogger().info("DEBUG: Portal at "+ l);
nms.setBlockSuperFast(l.getBlock(), Material.PORTAL.getId(), (byte)0, false);
//l.getBlock().setType(Material.PORTAL);
}
}
}
}*/
if (setHome && entity instanceof Player) {
plugin.getPlayers().setHomeLocation(entity.getUniqueId(), destination, homeNumber);
}
Vector velocity = entity.getVelocity();
entity.teleport(destination);
entity.setVelocity(velocity);
}});
} else {
// We did not find a spot
plugin.getServer().getScheduler().runTask(plugin, new Runnable() {
@Override
public void run() {
//plugin.getLogger().info("DEBUG: safe spot not found");
if (entity instanceof Player) {
if (!failureMessage.isEmpty()) {
Util.sendMessage(((Player)entity), failureMessage);
} else {
Util.sendMessage(((Player)entity), ChatColor.RED + plugin.myLocale(((Player)entity).getUniqueId()).warpserrorNotSafe);
}
}
}});
}
}
/**
* Returns true if the location is a safe one.
* @param chunk
* @param x
* @param y
* @param z
* @param worldHeight
* @return
*/
@SuppressWarnings("deprecation")
private boolean checkBlock(ChunkSnapshot chunk, int x, int y, int z, int worldHeight) {
int type = chunk.getBlockTypeId(x, y, z);
if (type != 0) { // AIR
int space1 = chunk.getBlockTypeId(x, Math.min(y + 1, worldHeight), z);
int space2 = chunk.getBlockTypeId(x, Math.min(y + 2, worldHeight), z);
if ((space1 == 0 && space2 == 0) || (space1 == Material.PORTAL.getId() || space2 == Material.PORTAL.getId())) {
// Now there is a chance that this is a safe spot
// Check for safe ground
Material mat = Material.getMaterial(type);
if (!mat.toString().contains("FENCE")
&& !mat.toString().contains("DOOR")
&& !mat.toString().contains("GATE")
&& !mat.toString().contains("PLATE")) {
switch (mat) {
// Unsafe
case ANVIL:
case BARRIER:
case BOAT:
case CACTUS:
case DOUBLE_PLANT:
case ENDER_PORTAL:
case FIRE:
case FLOWER_POT:
case LADDER:
case LAVA:
case LEVER:
case LONG_GRASS:
case PISTON_EXTENSION:
case PISTON_MOVING_PIECE:
case PORTAL:
case SIGN_POST:
case SKULL:
case STANDING_BANNER:
case STATIONARY_LAVA:
case STATIONARY_WATER:
case STONE_BUTTON:
case TORCH:
case TRIPWIRE:
case WATER:
case WEB:
case WOOD_BUTTON:
//System.out.println("Block is dangerous " + mat.toString());
break;
default:
// Safe
// System.out.println("Block is safe " + mat.toString());
return true;
}
}
}
}
return false;
}});
}
}
/**
* Checks what version the server is running and picks the appropriate NMS handler, or fallback
* @return NMSAbstraction class
* @throws ClassNotFoundException
* @throws IllegalArgumentException
* @throws SecurityException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodException
*/
/*
private NMSAbstraction checkVersion() throws ClassNotFoundException, IllegalArgumentException,
SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
String serverPackageName = plugin.getServer().getClass().getPackage().getName();
String pluginPackageName = plugin.getClass().getPackage().getName();
String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1);
Class<?> clazz;
try {
//plugin.getLogger().info("DEBUG: Trying " + pluginPackageName + ".nms." + version + ".NMSHandler");
clazz = Class.forName(pluginPackageName + ".nms." + version + ".NMSHandler");
} catch (Exception e) {
plugin.getLogger().info("No NMS Handler found, falling back to Bukkit API.");
clazz = Class.forName(pluginPackageName + ".nms.fallback.NMSHandler");
}
//plugin.getLogger().info("DEBUG: " + serverPackageName);
//plugin.getLogger().info("DEBUG: " + pluginPackageName);
// Check if we have a NMSAbstraction implementing class at that location.
if (NMSAbstraction.class.isAssignableFrom(clazz)) {
return (NMSAbstraction) clazz.getConstructor().newInstance();
} else {
throw new IllegalStateException("Class " + clazz.getName() + " does not implement NMSAbstraction");
}
}*/
}