content: package thebetweenlands.common.handler;
  
  import java.util.Collections;
  
  import javax.annotation.Nullable;
  
  import net.minecraft.block.state.IBlockState;
  import net.minecraft.entity.Entity;
  import net.minecraft.entity.player.EntityPlayer;
  import net.minecraft.entity.player.EntityPlayerMP;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.network.play.server.SPacketSpawnPosition;
  import net.minecraft.util.math.BlockPos;
  import net.minecraft.util.math.BlockPos.MutableBlockPos;
  import net.minecraft.world.World;
  import net.minecraft.world.WorldServer;
  import net.minecraft.world.chunk.Chunk;
  import net.minecraftforge.common.util.Constants;
  import net.minecraftforge.event.entity.living.LivingDeathEvent;
  import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
  import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerRespawnEvent;
  import thebetweenlands.api.entity.spawning.IWeightProvider;
  import thebetweenlands.common.config.BetweenlandsConfig;
  import thebetweenlands.common.world.teleporter.TeleporterBetweenlands;
  import thebetweenlands.common.world.teleporter.TeleporterHandler;
  import thebetweenlands.util.WeightedList;
  
  public class PlayerRespawnHandler {
  	public static final String RESPAWN_IN_BL_NBT = "thebetweenlands.respawn_in_bl";
  
  	@SubscribeEvent
  	public static void onPlayerDeath(LivingDeathEvent event) {
  		if(event.getEntityLiving() instanceof EntityPlayer) {
  			EntityPlayer player = (EntityPlayer) event.getEntityLiving();
  
  			NBTTagCompound dataNbt = player.getEntityData();
  			NBTTagCompound persistentNbt = dataNbt.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG);
  
  			BlockPos spawnPos = player.getBedLocation(player.dimension);
  
  			BlockPos adjustedSpawnPos = spawnPos == null ? null : EntityPlayer.getBedSpawnLocation(player.world, spawnPos, player.isSpawnForced(player.dimension));
  
  			boolean respawnInBL = BetweenlandsConfig.WORLD_AND_DIMENSION.startInBetweenlands && (!player.world.provider.canRespawnHere() || adjustedSpawnPos == null);
  
  			persistentNbt.setBoolean(RESPAWN_IN_BL_NBT, respawnInBL);
  
  			dataNbt.setTag(EntityPlayer.PERSISTED_NBT_TAG, persistentNbt);
  		}
  	}
  
  
  	@SubscribeEvent
  	public static void onRespawn(PlayerRespawnEvent event) {
  		if(!event.player.world.isRemote) {
  			BlockPos spawnPos = event.player.getBedLocation(event.player.dimension);
  
  			BlockPos adjustedSpawnPos = spawnPos == null ? null : EntityPlayer.getBedSpawnLocation(event.player.world, spawnPos, event.player.isSpawnForced(event.player.dimension));
  
  			NBTTagCompound dataNbt = event.player.getEntityData();
  			NBTTagCompound persistentNbt = dataNbt.getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG);
  
  			boolean shouldTeleportToBL = (BetweenlandsConfig.WORLD_AND_DIMENSION.startInBetweenlands && event.isEndConquered()) || persistentNbt.getBoolean(RESPAWN_IN_BL_NBT);
  
  			if(shouldTeleportToBL && event.player.world instanceof WorldServer) {
  				WorldServer blWorld = ((WorldServer) event.player.world).getMinecraftServer().getWorld(BetweenlandsConfig.WORLD_AND_DIMENSION.dimensionId);
  
  				TeleporterHandler.transferToDim(event.player, blWorld, false, false);
  
  				spawnPos = event.player.getBedLocation(event.player.dimension);
  
  				adjustedSpawnPos = spawnPos == null ? null : EntityPlayer.getBedSpawnLocation(event.player.world, spawnPos, event.player.isSpawnForced(event.player.dimension));
  
  				if(adjustedSpawnPos != null) {
  					event.player.setPosition(adjustedSpawnPos.getX() + 0.5D, adjustedSpawnPos.getY(), adjustedSpawnPos.getZ() + 0.5D);
  
  					if(event.player instanceof EntityPlayerMP) {
  						EntityPlayerMP playerMP = (EntityPlayerMP) event.player;
  
  						playerMP.connection.setPlayerLocation(playerMP.posX, playerMP.posY, playerMP.posZ, playerMP.rotationYaw, playerMP.rotationPitch);
  						playerMP.connection.sendPacket(new SPacketSpawnPosition(adjustedSpawnPos));
  					}
  				}
  			}
  
  			if(shouldTeleportToBL || event.player.dimension == BetweenlandsConfig.WORLD_AND_DIMENSION.dimensionId) {
  				boolean newRespawn = false;
  				if(adjustedSpawnPos == null) {
  					newRespawn = true;
  				} else {
  					IBlockState stateDown = event.player.world.getBlockState(adjustedSpawnPos.down());
  					boolean isValidSpawnBlock = stateDown.getMaterial().blocksMovement() && !stateDown.getBlock().isLeaves(stateDown, event.player.world, adjustedSpawnPos.down()) && !stateDown.getBlock().isFoliage(event.player.world, adjustedSpawnPos.down());
  					if(!isValidSpawnBlock) {
  						newRespawn = true;
  					}
  				}
  
  				if(newRespawn) {
  					if(persistentNbt.hasKey(TeleporterBetweenlands.LAST_PORTAL_POS_NBT, Constants.NBT.TAG_LONG)) {
  						BlockPos lastPortal = BlockPos.fromLong(persistentNbt.getLong(TeleporterBetweenlands.LAST_PORTAL_POS_NBT));
  
  						respawnNearPos(event.player, lastPortal);
  					}
  				}
  			}
  		}
  	}
  
  	public static void respawnNearPos(Entity entity, BlockPos pos) {
  		BlockPos newSpawn = getRespawnPointNearPos(entity.world, pos, 64);
  
  		entity.setLocationAndAngles(newSpawn.getX() + 0.5D, newSpawn.getY(), newSpawn.getZ() + 0.5D, entity.rotationYaw, entity.rotationPitch);
  
  		while (!entity.world.getCollisionBoxes(entity, entity.getEntityBoundingBox()).isEmpty() && entity.posY < 256.0D) {
  			entity.setPosition(entity.posX, entity.posY + 1.0D, entity.posZ);
  		}
  
  		newSpawn = new BlockPos(entity);
  
  		if(entity instanceof EntityPlayer) {
  			((EntityPlayer)entity).setSpawnPoint(newSpawn, true);
  		}
  
  		if(entity instanceof EntityPlayerMP) {
  			EntityPlayerMP playerMP = (EntityPlayerMP) entity;
  
  			playerMP.connection.setPlayerLocation(playerMP.posX, playerMP.posY, playerMP.posZ, playerMP.rotationYaw, playerMP.rotationPitch);
  			playerMP.connection.sendPacket(new SPacketSpawnPosition(newSpawn));
  		}
  	}
  
  	public static BlockPos getRespawnPointNearPos(World world, BlockPos pos, int fuzz) {
  		BlockPos result = getSpawnPointNearPos(world, pos, fuzz, true, 16, 3);
  		if(result == null) {
  			int spawnFuzz = fuzz;
  			int spawnFuzzHalf = spawnFuzz / 2;
  			result = world.getTopSolidOrLiquidBlock(pos.add(world.rand.nextInt(spawnFuzz) - spawnFuzzHalf, 0, world.rand.nextInt(spawnFuzz) - spawnFuzzHalf));
  		}
  		return result;
  	}
  
  	@Nullable
  	public static BlockPos getSpawnPointNearPos(World world, BlockPos pos, int fuzz, boolean surface, int yRange, int xzSkip) {
  		xzSkip = Math.max(xzSkip, 1);
  		
  		int spawnFuzz = fuzz;
  		int spawnFuzzHalf = spawnFuzz / 2;
  
  		class WeightedPos implements IWeightProvider {
  			final BlockPos pos;
  			short weight;
  
  			WeightedPos(BlockPos pos) {
  				this.pos = pos;
  			}
  
  			@Override
  			public short getWeight() {
  				return this.weight;
  			}
  		}
  
  		short maxWeight = 0;
  
  		WeightedList<WeightedPos> spawnCandidates = new WeightedList<>();
  
  		MutableBlockPos checkPos = new MutableBlockPos();
  
  		for(int xo = -spawnFuzzHalf; xo <= spawnFuzzHalf; xo += 1 + world.rand.nextInt(xzSkip)) {
  			for(int zo = -spawnFuzzHalf; zo <= spawnFuzzHalf; zo += 1 + world.rand.nextInt(xzSkip)) {
  				checkPos.setPos(pos.getX() + xo, 0, pos.getZ() + zo);
  
  				Chunk chunk = world.getChunk(checkPos);
  
  				for(int yo = yRange; surface || yo >= -yRange; yo--) {
  					if(surface) {
  						checkPos.setPos(checkPos.getX(), chunk.getHeight(checkPos), checkPos.getZ());
  					} else {
  						checkPos.setPos(checkPos.getX(), pos.getY() + yo, checkPos.getZ());
  					}
  
  					if(Math.abs(checkPos.getY() - pos.getY()) <= yRange && EntityPlayer.getBedSpawnLocation(world, checkPos, true) != null) {
  						checkPos.setY(checkPos.getY() - 1);
  
  						IBlockState stateDown = chunk.getBlockState(checkPos);
  						if(stateDown.getMaterial().blocksMovement() && !stateDown.getBlock().isLeaves(stateDown, world, checkPos) && !stateDown.getBlock().isFoliage(world, checkPos)) {
  							BlockPos newPos = checkPos.up();
  							WeightedPos p = new WeightedPos(newPos);
  							p.weight = (short)Math.abs(newPos.getY() - pos.getY());
  							maxWeight = (short)Math.max(maxWeight, p.weight);
  							spawnCandidates.add(p);
  						}
  					}
  
  					if(surface) {
  						break;
  					}
  				}
  			}
  		}
  
  		if(!spawnCandidates.isEmpty()) {
  			Collections.shuffle(spawnCandidates, world.rand);
  
  			for(WeightedPos p : spawnCandidates) {
  				p.weight = (short) (maxWeight - p.weight);
  				p.weight = (short) Math.pow(p.weight, 1.5D);
  			}
  
  			spawnCandidates.recalculateWeight();
  
  			return spawnCandidates.getRandomItem(world.rand).pos;
  		}
  
  		return null;
  	}
  }
  