package common.world;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

import common.biome.BaseBiome;
import common.block.Block;
import common.block.BlockHopper;
import common.block.BlockLiquid;
import common.block.BlockSlab;
import common.block.BlockSnow;
import common.block.BlockStairs;
import common.block.LeavesType;
import common.collect.Lists;
import common.collect.Sets;
import common.dimension.Dimension;
import common.entity.Entity;
import common.entity.item.EntityExplosion;
import common.entity.npc.EntityNPC;
import common.init.BlockRegistry;
import common.init.Blocks;
import common.init.SoundEvent;
import common.item.ItemStack;
import common.material.Material;
import common.model.ParticleType;
import common.rng.Random;
import common.tileentity.ITickable;
import common.tileentity.TileEntity;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.ChunkPos;
import common.util.ExtMath;
import common.util.Facing;
import common.util.HitPosition;
import common.util.IntHashMap;
import common.util.Vec3;

public abstract class World implements IWorldAccess {
	public static final float[][] BRIGHTNESS = new float[16][16];
	static {
		for(int l = 0; l < 16; l++) {
			float f = (float)l / 15.0f;
			for(int i = 0; i <= 15; ++i) {
				float f1 = 1.0F - (float)i / 15.0F;
				BRIGHTNESS[l][i] = (1.0F - f1) / (f1 * 3.0F + 1.0F) * (1.0F - f) + f;
			}
		}
	}
	public static final float[] MOON_PHASES = new float[] {1.0F, 0.75F, 0.5F, 0.25F, 0.0F, 0.25F, 0.5F, 0.75F};
	public static final int MAX_SIZE = 67108864;
	public static final int HEIGHT = 512;
	public static final long START_TIME = 4385238000L;
	public static final float ABSOLUTE_ZERO = -273.15f;
//	private static final Calendar calendar = Calendar.getInstance();
//	private static long lastUpdate;

	public final boolean client;
	public final boolean debug;
	public double gravity = 1.0;
	protected long timeFactor = 1L;
	public final Random rand = new Random();
	public final List<Entity> entities = Lists.<Entity>newArrayList();
	protected final List<Entity> unloaded = Lists.<Entity>newArrayList();
	protected final List<TileEntity> tiles = Lists.<TileEntity>newArrayList();
	protected final List<TileEntity> tickable = Lists.<TileEntity>newArrayList();
	protected final List<TileEntity> addedTiles = Lists.<TileEntity>newArrayList();
	protected final List<TileEntity> removeTiles = Lists.<TileEntity>newArrayList();
	public final List<EntityNPC> players = Lists.<EntityNPC>newArrayList();
	public final List<Entity> effects = Lists.<Entity>newArrayList();
	protected final IntHashMap<Entity> entityIds = new IntHashMap();
	protected final Set<ChunkPos> active = Sets.<ChunkPos>newHashSet();
	protected final int[] lightUpdate = new int[32768];
	public final Dimension dimension;
    
	protected boolean loadTiles;
	protected int subtract;
	protected float rain;
	protected float darkness;
	protected float fog;
	protected float temp;
	protected Weather weather;
	protected long daytime;

//	public static Calendar getCurrentDate() {
//		long curTime = System.currentTimeMillis();
//		if(curTime - lastUpdate >= 30000L) {
//			lastUpdate = curTime;
//			calendar.setTimeInMillis(curTime);
//		}
//		return calendar;
//	}

	public boolean isBlockSolid(BlockPos pos) {
		return isSolidSurface(this.getState(pos));
	}
	
	public static boolean isSolidSurface(State state) {
		Block block = state.getBlock();
		return block.getMaterial().isOpaque() && block.isFullCube() ? true
				: (block instanceof BlockStairs ? state.getValue(BlockStairs.HALF) == BlockStairs.EnumHalf.TOP
				: (block instanceof BlockSlab ? (state.getValue(BlockSlab.DOUBLE) || state.getValue(BlockSlab.FACING) == Facing.UP)
				: (block instanceof BlockHopper ? true
				: (block instanceof BlockSnow ? ((Integer)state.getValue(BlockSnow.LAYERS)).intValue() == 7 
				: false))));
	}

	public static boolean isValid(BlockPos pos) {
		return pos.getX() >= -MAX_SIZE && pos.getZ() >= -MAX_SIZE && pos.getX() < MAX_SIZE && pos.getZ() < MAX_SIZE && 
				pos.getY() >= 0 && pos.getY() < 512;
	}

	public static boolean isValidXZ(BlockPos pos) {
		return pos.getX() >= -MAX_SIZE && pos.getZ() >= -MAX_SIZE && pos.getX() < MAX_SIZE && pos.getZ() < MAX_SIZE;
	}

	protected World(Dimension dim, boolean client, boolean debug) {
//		this.profiler = profiler;
//		this.info = info;
//		this.dimInfo = info.getDimension(dim.getDimensionId());
		this.dimension = dim;
//		this.storage = storage;
		this.client = client;
		this.debug = debug;
		this.weather = dim.getWeather();
	}
	
	public void setGravity(float gravity) {
		this.gravity = (double)this.dimension.getGravity() * 0.1 * gravity;
		if(Math.abs(this.gravity) < 0.075)
			this.gravity = Math.signum(this.gravity) * 0.075;
	}

	public BaseBiome getBiomeGenForCoords(final BlockPos pos) {
		if(this.isBlockLoaded(pos))
			return this.getChunk(pos).getBiome(pos, null);
		else
			return BaseBiome.DEF_BIOME;
	}

	public boolean isAirBlock(BlockPos pos) {
		return this.getState(pos).getBlock().getMaterial() == Material.air;
	}

	public boolean isBlockLoaded(BlockPos pos) {
		return this.isBlockLoaded(pos, true);
	}

	public boolean isBlockLoaded(BlockPos pos, boolean allowEmpty) {
		return !isValid(pos) ? false : this.isLoaded(pos.getX() >> 4, pos.getZ() >> 4, allowEmpty);
	}

	public boolean isAreaLoaded(BlockPos center, int radius) {
		return this.isAreaLoaded(center, radius, true);
	}

	public boolean isAreaLoaded(BlockPos center, int radius, boolean allowEmpty) {
		return this.isAreaLoaded(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius,
				center.getY() + radius, center.getZ() + radius, allowEmpty);
	}

	public boolean isAreaLoaded(BlockPos from, BlockPos to) {
		return this.isAreaLoaded(from, to, true);
	}

	public boolean isAreaLoaded(BlockPos from, BlockPos to, boolean allowEmpty) {
		return this.isAreaLoaded(from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ(), allowEmpty);
	}

	private boolean isAreaLoaded(int xStart, int yStart, int zStart, int xEnd, int yEnd, int zEnd, boolean allowEmpty) {
		if(yEnd >= 0 && yStart < 512) {
			xStart = xStart >> 4;
			zStart = zStart >> 4;
			xEnd = xEnd >> 4;
			zEnd = zEnd >> 4;

			for(int i = xStart; i <= xEnd; ++i) {
				for(int j = zStart; j <= zEnd; ++j) {
					if(!this.isLoaded(i, j, allowEmpty)) {
						return false;
					}
				}
			}

			return true;
		}
		else {
			return false;
		}
	}

	protected boolean isLoaded(int x, int z, boolean allowEmpty) {
		return allowEmpty || !this.getChunk(x, z).isEmpty();
	}

	public Chunk getChunk(BlockPos pos) {
		return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4);
	}

	/**
	 * Flag 1 will cause a block update Flag 2 will send the change to clients Flag
	 * 4 prevents the block from being re-rendered on client worlds
	 */
	public boolean setState(BlockPos pos, State newState, int flags) {
		if(!isValid(pos)) {
			return false;
		}
		else if(!this.client && this.debug) {
			return false;
		}
		else {
			Chunk chunk = this.getChunk(pos);
			Block block = newState.getBlock();
			State iblockstate = chunk.setState(pos, newState);

			if(iblockstate == null) {
				return false;
			}
			else {
				Block block1 = iblockstate.getBlock();

				if(block.getLightOpacity() != block1.getLightOpacity() || block.getLightValue() != block1.getLightValue()) {
//					this.profiler.start("checkLight");
					this.checkLight(pos);
//					this.profiler.end();
				}

				if((flags & 2) != 0 && (!this.client || (flags & 4) == 0) && chunk.isPopulated()) {
					this.markBlockForUpdate(pos);
				}

				if(!this.client && (flags & 1) != 0) {
					this.notifyNeighborsRespectDebug(pos, iblockstate.getBlock());

					if(block.hasComparatorInputOverride()) {
						this.updateComparatorOutputLevel(pos, block);
					}
				}

				return true;
			}
		}
	}

	public boolean setState(BlockPos pos, State state) {
		return this.setState(pos, state, 3);
	}

	public boolean setBlockToAir(BlockPos pos) {
		return this.setState(pos, Blocks.air.getState(), 3);
	}

	public boolean destroyBlock(BlockPos pos, boolean dropBlock) {
		State iblockstate = this.getState(pos);
		Block block = iblockstate.getBlock();

		if(block.getMaterial() == Material.air) {
			return false;
		}
		else {
			this.playAuxSFX(2001, pos, BlockRegistry.getStateId(iblockstate));

			if(dropBlock) {
				block.dropBlockAsItem(this, pos, iblockstate, 0);
			}

			return this.setState(pos, Blocks.air.getState(), 3);
		}
	}

	public void notifyNeighborsRespectDebug(BlockPos pos, Block blockType) {
		if(!this.debug) {
			this.notifyNeighborsOfStateChange(pos, blockType);
		}
	}

	public void markBlocksDirtyVertical(int x1, int z1, int x2, int z2) {
		if(x2 > z2) {
			int i = z2;
			z2 = x2;
			x2 = i;
		}

		if(!this.dimension.hasNoLight()) {
			for(int j = x2; j <= z2; ++j) {
				this.checkLightFor(LightType.SKY, new BlockPos(x1, j, z1));
			}
		}

		this.markBlockRangeForRenderUpdate(x1, x2, z1, x1, z2, z1);
	}

	public void markBlockRangeForRenderUpdate(BlockPos rangeMin, BlockPos rangeMax) {
		this.markBlockRangeForRenderUpdate(rangeMin.getX(), rangeMin.getY(), rangeMin.getZ(), rangeMax.getX(), rangeMax.getY(), rangeMax.getZ());
	}

	public void notifyNeighborsOfStateChange(BlockPos pos, Block blockType) {
		this.notifyBlockOfStateChange(pos.west(), blockType);
		this.notifyBlockOfStateChange(pos.east(), blockType);
		this.notifyBlockOfStateChange(pos.down(), blockType);
		this.notifyBlockOfStateChange(pos.up(), blockType);
		this.notifyBlockOfStateChange(pos.north(), blockType);
		this.notifyBlockOfStateChange(pos.south(), blockType);
	}

	public void notifyNeighborsOfStateExcept(BlockPos pos, Block blockType, Facing skipSide) {
		if(skipSide != Facing.WEST) {
			this.notifyBlockOfStateChange(pos.west(), blockType);
		}

		if(skipSide != Facing.EAST) {
			this.notifyBlockOfStateChange(pos.east(), blockType);
		}

		if(skipSide != Facing.DOWN) {
			this.notifyBlockOfStateChange(pos.down(), blockType);
		}

		if(skipSide != Facing.UP) {
			this.notifyBlockOfStateChange(pos.up(), blockType);
		}

		if(skipSide != Facing.NORTH) {
			this.notifyBlockOfStateChange(pos.north(), blockType);
		}

		if(skipSide != Facing.SOUTH) {
			this.notifyBlockOfStateChange(pos.south(), blockType);
		}
	}

	public void notifyBlockOfStateChange(BlockPos pos, final Block blockIn) {
		if(!this.client) {
			State iblockstate = this.getState(pos);

			iblockstate.getBlock().onNeighborBlockChange(this, pos, iblockstate, blockIn);
		}
	}

	public boolean canSeeSky(BlockPos pos) {
		return this.getChunk(pos).canSeeSky(pos);
	}

	public int getLight(BlockPos pos) {
		if(pos.getY() < 0) {
			return 0;
		}
		else {
			if(pos.getY() >= 512) {
				pos = new BlockPos(pos.getX(), 511, pos.getZ());
			}

			return this.getChunk(pos).getLightSub(pos, 0);
		}
	}

	public int getLightFromNeighbors(BlockPos pos) {
		return this.getLight(pos, true);
	}

	private int getLight(BlockPos pos, boolean checkNeighbors) {
		if(pos.getX() >= -MAX_SIZE && pos.getZ() >= -MAX_SIZE && pos.getX() < MAX_SIZE && pos.getZ() < MAX_SIZE) {
			if(checkNeighbors && this.getState(pos).getBlock().getSumBrightness()) {
				int i1 = this.getLight(pos.up(), false);
				int i = this.getLight(pos.east(), false);
				int j = this.getLight(pos.west(), false);
				int k = this.getLight(pos.south(), false);
				int l = this.getLight(pos.north(), false);

				if(i > i1) {
					i1 = i;
				}

				if(j > i1) {
					i1 = j;
				}

				if(k > i1) {
					i1 = k;
				}

				if(l > i1) {
					i1 = l;
				}

				return i1;
			}
			else if(pos.getY() < 0) {
				return 0;
			}
			else {
				if(pos.getY() >= 512) {
					pos = new BlockPos(pos.getX(), 511, pos.getZ());
				}

				Chunk chunk = this.getChunk(pos);
				return chunk.getLightSub(pos, this.subtract);
			}
		}
		else {
			return 15;
		}
	}

	public BlockPos getHeight(BlockPos pos) {
		int i;

		if(pos.getX() >= -MAX_SIZE && pos.getZ() >= -MAX_SIZE && pos.getX() < MAX_SIZE && pos.getZ() < MAX_SIZE) {
			if(this.isLoaded(pos.getX() >> 4, pos.getZ() >> 4, true)) {
				i = this.getChunk(pos.getX() >> 4, pos.getZ() >> 4).getHeight(pos.getX() & 15, pos.getZ() & 15);
			}
			else {
				i = 0;
			}
		}
		else {
			i = this.getSeaLevel() + 1;
		}

		return new BlockPos(pos.getX(), i, pos.getZ());
	}

	public int getChunksLowestHorizon(int x, int z) {
		if(x >= -MAX_SIZE && z >= -MAX_SIZE && x < MAX_SIZE && z < MAX_SIZE) {
			if(!this.isLoaded(x >> 4, z >> 4, true)) {
				return 0;
			}
			else {
				Chunk chunk = this.getChunk(x >> 4, z >> 4);
				return chunk.getLowest();
			}
		}
		else {
			return this.getSeaLevel() + 1;
		}
	}

	public int getLightFromNeighborsFor(LightType type, BlockPos pos) {
		if(this.dimension.hasNoLight() && type == LightType.SKY) {
			return 0;
		}
		else {
			if(pos.getY() < 0) {
				pos = new BlockPos(pos.getX(), 0, pos.getZ());
			}

			if(!isValid(pos)) {
				return type.defValue;
			}
			else if(!this.isBlockLoaded(pos)) {
				return type.defValue;
			}
			else if(this.getState(pos).getBlock().getSumBrightness()) {
				int i1 = this.getLightFor(type, pos.up());
				int i = this.getLightFor(type, pos.east());
				int j = this.getLightFor(type, pos.west());
				int k = this.getLightFor(type, pos.south());
				int l = this.getLightFor(type, pos.north());

				if(i > i1) {
					i1 = i;
				}

				if(j > i1) {
					i1 = j;
				}

				if(k > i1) {
					i1 = k;
				}

				if(l > i1) {
					i1 = l;
				}

				return i1;
			}
			else {
				Chunk chunk = this.getChunk(pos);
				return chunk.getLight(type, pos);
			}
		}
	}

	public int getLightFor(LightType type, BlockPos pos) {
		if(pos.getY() < 0) {
			pos = new BlockPos(pos.getX(), 0, pos.getZ());
		}

		if(!isValid(pos)) {
			return type.defValue;
		}
		else if(!this.isBlockLoaded(pos)) {
			return type.defValue;
		}
		else {
			Chunk chunk = this.getChunk(pos);
			return chunk.getLight(type, pos);
		}
	}

	public void setLightFor(LightType type, BlockPos pos, int lightValue) {
		if(isValid(pos)) {
			if(this.isBlockLoaded(pos)) {
				Chunk chunk = this.getChunk(pos);
				chunk.setLight(type, pos, lightValue);
				this.notifyLightSet(pos);
			}
		}
	}

	public int getCombinedLight(BlockPos pos, int lightValue) {
		int i = this.getLightFromNeighborsFor(LightType.SKY, pos);
		int j = this.getLightFromNeighborsFor(LightType.BLOCK, pos);

		if(j < lightValue) {
			j = lightValue;
		}

		return i << 20 | j << 4;
	}

	public float getLightBrightness(BlockPos pos) {
		return BRIGHTNESS[this.dimension.getBrightness()][this.getLightFromNeighbors(pos)];
	}

	public State getState(BlockPos pos) {
		if(!isValid(pos)) {
			return Blocks.air.getState();
		}
		else {
			Chunk chunk = this.getChunk(pos);
			return chunk.getState(pos);
		}
	}
    
    public BlockPos getBlockTrace(Entity entity, int distance) {
    	double range = (double)distance;
    	double theta = 0.2;
		double yaw = (entity.rotYaw + 90) % 360;
		double pitch = entity.rotPitch * -1;
		double dist = 0.0;
		double h = theta * Math.cos(Math.toRadians(pitch));
		Vec3 offset = new Vec3(h * Math.cos(Math.toRadians(yaw)), theta * Math.sin(Math.toRadians(pitch)), 
				h * Math.sin(Math.toRadians(yaw)));
		Vec3 shift = new Vec3(entity.posX, entity.posY + entity.getEyeHeight(), entity.posZ);
		BlockPos target = new BlockPos(ExtMath.floord(shift.xCoord), ExtMath.floord(shift.yCoord), ExtMath.floord(shift.zCoord));
		BlockPos prev = target;
        while(true) {
            prev = target;
            do {
                dist += theta;
                shift = offset.addVector(shift.xCoord, shift.yCoord, shift.zCoord);
                target = new BlockPos(ExtMath.floord(shift.xCoord), ExtMath.floord(shift.yCoord), ExtMath.floord(shift.zCoord));
            }
            while(dist <= range && target.getX() == prev.getX() && target.getY() == prev.getY() && target.getZ() == prev.getZ());
            if(dist > range)
                return null;
            if(this.getState(new BlockPos(target.getX(), target.getY(), target.getZ()))
            		.getBlock().getMaterial().blocksMovement())
                break;
            List<Entity> ents = this.getEntitiesInAABBexcluding(entity, new BoundingBox(target, target.add(1, 1, 1)), 
            		new Predicate<Entity>() {
				public boolean test(Entity entity) {
					return entity.canBeCollidedWith();
				}
			});
            if(!ents.isEmpty())
            	break;
        }
        return target;
    }

	public HitPosition rayTraceBlocks(Vec3 p_72933_1_, Vec3 p_72933_2_) {
		return this.rayTraceBlocks(p_72933_1_, p_72933_2_, false, false, false);
	}

	public HitPosition rayTraceBlocks(Vec3 start, Vec3 end, boolean stopOnLiquid) {
		return this.rayTraceBlocks(start, end, stopOnLiquid, false, false);
	}

	public HitPosition rayTraceBlocks(Vec3 vec31, Vec3 vec32, boolean stopOnLiquid, boolean ignoreBlockWithoutBoundingBox,
			boolean returnLastUncollidableBlock) {
		if(!Double.isNaN(vec31.xCoord) && !Double.isNaN(vec31.yCoord) && !Double.isNaN(vec31.zCoord)) {
			if(!Double.isNaN(vec32.xCoord) && !Double.isNaN(vec32.yCoord) && !Double.isNaN(vec32.zCoord)) {
				int i = ExtMath.floord(vec32.xCoord);
				int j = ExtMath.floord(vec32.yCoord);
				int k = ExtMath.floord(vec32.zCoord);
				int l = ExtMath.floord(vec31.xCoord);
				int i1 = ExtMath.floord(vec31.yCoord);
				int j1 = ExtMath.floord(vec31.zCoord);
				BlockPos blockpos = new BlockPos(l, i1, j1);
				State iblockstate = this.getState(blockpos);
				Block block = iblockstate.getBlock();

				if((!ignoreBlockWithoutBoundingBox || block.getCollisionBoundingBox(this, blockpos, iblockstate) != null)
						&& block.canCollideCheck(iblockstate, stopOnLiquid)) {
					HitPosition movingobjectposition = block.collisionRayTrace(this, blockpos, vec31, vec32);

					if(movingobjectposition != null) {
						return movingobjectposition;
					}
				}

				HitPosition movingobjectposition2 = null;
				int k1 = 200;

				while(k1-- >= 0) {
					if(Double.isNaN(vec31.xCoord) || Double.isNaN(vec31.yCoord) || Double.isNaN(vec31.zCoord)) {
						return null;
					}

					if(l == i && i1 == j && j1 == k) {
						return returnLastUncollidableBlock ? movingobjectposition2 : null;
					}

					boolean flag2 = true;
					boolean flag = true;
					boolean flag1 = true;
					double d0 = 999.0D;
					double d1 = 999.0D;
					double d2 = 999.0D;

					if(i > l) {
						d0 = (double)l + 1.0D;
					}
					else if(i < l) {
						d0 = (double)l + 0.0D;
					}
					else {
						flag2 = false;
					}

					if(j > i1) {
						d1 = (double)i1 + 1.0D;
					}
					else if(j < i1) {
						d1 = (double)i1 + 0.0D;
					}
					else {
						flag = false;
					}

					if(k > j1) {
						d2 = (double)j1 + 1.0D;
					}
					else if(k < j1) {
						d2 = (double)j1 + 0.0D;
					}
					else {
						flag1 = false;
					}

					double d3 = 999.0D;
					double d4 = 999.0D;
					double d5 = 999.0D;
					double d6 = vec32.xCoord - vec31.xCoord;
					double d7 = vec32.yCoord - vec31.yCoord;
					double d8 = vec32.zCoord - vec31.zCoord;

					if(flag2) {
						d3 = (d0 - vec31.xCoord) / d6;
					}

					if(flag) {
						d4 = (d1 - vec31.yCoord) / d7;
					}

					if(flag1) {
						d5 = (d2 - vec31.zCoord) / d8;
					}

					if(d3 == -0.0D) {
						d3 = -1.0E-4D;
					}

					if(d4 == -0.0D) {
						d4 = -1.0E-4D;
					}

					if(d5 == -0.0D) {
						d5 = -1.0E-4D;
					}

					Facing enumfacing;

					if(d3 < d4 && d3 < d5) {
						enumfacing = i > l ? Facing.WEST : Facing.EAST;
						vec31 = new Vec3(d0, vec31.yCoord + d7 * d3, vec31.zCoord + d8 * d3);
					}
					else if(d4 < d5) {
						enumfacing = j > i1 ? Facing.DOWN : Facing.UP;
						vec31 = new Vec3(vec31.xCoord + d6 * d4, d1, vec31.zCoord + d8 * d4);
					}
					else {
						enumfacing = k > j1 ? Facing.NORTH : Facing.SOUTH;
						vec31 = new Vec3(vec31.xCoord + d6 * d5, vec31.yCoord + d7 * d5, d2);
					}

					l = ExtMath.floord(vec31.xCoord) - (enumfacing == Facing.EAST ? 1 : 0);
					i1 = ExtMath.floord(vec31.yCoord) - (enumfacing == Facing.UP ? 1 : 0);
					j1 = ExtMath.floord(vec31.zCoord) - (enumfacing == Facing.SOUTH ? 1 : 0);
					blockpos = new BlockPos(l, i1, j1);
					State iblockstate1 = this.getState(blockpos);
					Block block1 = iblockstate1.getBlock();

					if(!ignoreBlockWithoutBoundingBox || block1.getCollisionBoundingBox(this, blockpos, iblockstate1) != null) {
						if(block1.canCollideCheck(iblockstate1, stopOnLiquid)) {
							HitPosition movingobjectposition1 = block1.collisionRayTrace(this, blockpos, vec31, vec32);

							if(movingobjectposition1 != null) {
								return movingobjectposition1;
							}
						}
						else {
							movingobjectposition2 = new HitPosition(HitPosition.ObjectType.MISS, vec31, enumfacing, blockpos);
						}
					}
				}

				return returnLastUncollidableBlock ? movingobjectposition2 : null;
			}
			else {
				return null;
			}
		}
		else {
			return null;
		}
	}

	public void playSoundAtEntity(Entity entityIn, SoundEvent name, float volume) {
		this.playSound(name, entityIn.posX, entityIn.posY, entityIn.posZ, volume);
	}

	public boolean spawnEntityInWorld(Entity entityIn) {
		int i = ExtMath.floord(entityIn.posX / 16.0D);
		int j = ExtMath.floord(entityIn.posZ / 16.0D);
		boolean flag = entityIn.forceSpawn;

		if(entityIn.isPlayer()) {
			flag = true;
		}

		if(!flag && !this.isLoaded(i, j, true)) {
			return false;
		}
		else {
			if(entityIn.isPlayer()) {
				EntityNPC entityplayer = (EntityNPC)entityIn;
				this.players.add(entityplayer);
//				this.updateAllPlayersSleepingFlag();
			}

			this.getChunk(i, j).addEntity(entityIn);
			this.entities.add(entityIn);
			this.onEntityAdded(entityIn);
			return true;
		}
	}

	public void removeEntity(Entity entityIn) {
		if(entityIn.passenger != null) {
			entityIn.passenger.mountEntity((Entity)null);
		}

		if(entityIn.vehicle != null) {
			entityIn.mountEntity((Entity)null);
		}

		entityIn.setDead();

		if(entityIn.isPlayer()) {
			this.players.remove(entityIn);
//			this.updateAllPlayersSleepingFlag();
			this.onEntityRemoved(entityIn);
		}
	}

	public List<BoundingBox> getCollidingBoundingBoxes(Entity entityIn, BoundingBox bb) {
		List<BoundingBox> list = Lists.<BoundingBox>newArrayList();
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);
//		Border worldborder = this.getWorldBorder();
		boolean flag = entityIn.isOutsideBorder();
		boolean flag1 = this.isInsideBorder(entityIn);
		State iblockstate = Blocks.stone.getState();
		BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

		for(int k1 = i; k1 < j; ++k1) {
			for(int l1 = i1; l1 < j1; ++l1) {
				if(this.isBlockLoaded(blockpos$mutableblockpos.set(k1, 64, l1))) {
					for(int i2 = k - 1; i2 < l; ++i2) {
						blockpos$mutableblockpos.set(k1, i2, l1);

						if(flag && flag1) {
							entityIn.setOutsideBorder(false);
						}
						else if(!flag && !flag1) {
							entityIn.setOutsideBorder(true);
						}

						State iblockstate1 = iblockstate;

						if(isValidXZ(blockpos$mutableblockpos) || !flag1) {
							iblockstate1 = this.getState(blockpos$mutableblockpos);
						}

						iblockstate1.getBlock().addCollisionBoxesToList(this, blockpos$mutableblockpos, iblockstate1, bb, list, entityIn);
					}
				}
			}
		}

		double d0 = 0.25D;
		List<Entity> list1 = this.getEntitiesWithinAABBExcludingEntity(entityIn, bb.expand(d0, d0, d0));

		for(int j2 = 0; j2 < list1.size(); ++j2) {
			if(entityIn.passenger != list1 && entityIn.vehicle != list1) {
				BoundingBox axisalignedbb = ((Entity)list1.get(j2)).getCollisionBoundingBox();

				if(axisalignedbb != null && axisalignedbb.intersectsWith(bb)) {
					list.add(axisalignedbb);
				}

				axisalignedbb = entityIn.getCollisionBox((Entity)list1.get(j2));

				if(axisalignedbb != null && axisalignedbb.intersectsWith(bb)) {
					list.add(axisalignedbb);
				}
			}
		}

		return list;
	}

	private boolean isInsideBorder(Entity entityIn) {
		double d0 = (double)-MAX_SIZE; // worldBorderIn.minX();
		double d2 = (double)MAX_SIZE; // worldBorderIn.maxX();

		if(entityIn.isOutsideBorder()) {
			++d0;
			--d2;
		}
		else {
			--d0;
			++d2;
		}

		return entityIn.posX > d0 && entityIn.posX < d2 && entityIn.posZ > d0 && entityIn.posZ < d2;
	}

	public List<BoundingBox> getCollisionBoxes(BoundingBox bb) {
		List<BoundingBox> list = Lists.<BoundingBox>newArrayList();
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);
		BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

		for(int k1 = i; k1 < j; ++k1) {
			for(int l1 = i1; l1 < j1; ++l1) {
				if(this.isBlockLoaded(blockpos$mutableblockpos.set(k1, 64, l1))) {
					for(int i2 = k - 1; i2 < l; ++i2) {
						blockpos$mutableblockpos.set(k1, i2, l1);
						State iblockstate;

						if(k1 >= -MAX_SIZE && k1 < MAX_SIZE && l1 >= -MAX_SIZE && l1 < MAX_SIZE) {
							iblockstate = this.getState(blockpos$mutableblockpos);
						}
						else {
							iblockstate = Blocks.bedrock.getState();
						}

						iblockstate.getBlock().addCollisionBoxesToList(this, blockpos$mutableblockpos, iblockstate, bb, list, (Entity)null);
					}
				}
			}
		}

		return list;
	}

	private float calculateCelestialAngle(long worldTime, float partialTicks) {
		int i = (int)(worldTime % this.dimension.getRotationalPeriod());
		float f = ((float)i + partialTicks) / (float)this.dimension.getRotationalPeriod() - 0.5F;

		if(f < 0.0F) {
			++f;
		}

		if(f > 1.0F) {
			--f;
		}

		f = 1.0F - (float)((Math.cos((double)f * Math.PI) + 1.0D) / 2.0D);
		f = f + (f - f) / 3.0F;
		return f;
	}

	public int calculateSkylightSubtracted(boolean current) {
		float f = !this.dimension.getType().days ? 0.5f : this.calculateCelestialAngle(current ? this.daytime : 
				(this.dimension.getRotationalPeriod() / 4L), 1.0f);
		float f1 = 1.0F - (ExtMath.cos(f * (float)Math.PI * 2.0F) * 2.0F + 0.5F);
		f1 = ExtMath.clampf(f1, 0.0F, 1.0F);
		f1 = 1.0F - f1;
		f1 = (float)((double)f1 * (1.0D - (double)(this.getRainStrength() * 5.0F) / 16.0D));
		f1 = (float)((double)f1 * (1.0D - (double)(this.getDarkness() * 5.0F) / 16.0D));
		f1 = 1.0F - f1;
		return (int)(f1 * 11.0F);
	}

	public float getCelestialAngle(float partialTicks) {
		return !this.dimension.getType().days ? 0.5f : this.calculateCelestialAngle(this.daytime, partialTicks);
	}

	public int getMoonPhase() {
		return (int)(this.daytime / this.dimension.getRotationalPeriod() % 8L + 8L) % 8;
	}

	public float getCurrentMoonPhaseFactor() {
		return MOON_PHASES[this.getMoonPhase()];
	}

	public float getCelestialAngleRadians(float partialTicks) {
		float f = this.getCelestialAngle(partialTicks);
		return f * (float)Math.PI * 2.0F;
	}

	public BlockPos getPrecipitationHeight(BlockPos pos) {
		return this.getChunk(pos).getPrecipitation(pos);
	}

	public void updateEntities() {
//		this.profiler.start("entities");
//		this.profiler.start("global");

		for(int i = 0; i < this.effects.size(); ++i) {
			Entity entity = (Entity)this.effects.get(i);

			++entity.ticksExisted;
			entity.onUpdate();

			if(entity.dead) {
				this.effects.remove(i--);
			}
		}

//		this.profiler.next("remove");
		this.entities.removeAll(this.unloaded);

		for(int k = 0; k < this.unloaded.size(); ++k) {
			Entity entity1 = (Entity)this.unloaded.get(k);
			int j = entity1.chunkCoordX;
			int l1 = entity1.chunkCoordZ;

			if(entity1.addedToChunk && this.isLoaded(j, l1, true)) {
				this.getChunk(j, l1).removeEntity(entity1);
			}
		}

		for(int l = 0; l < this.unloaded.size(); ++l) {
			this.onEntityRemoved((Entity)this.unloaded.get(l));
		}

		this.unloaded.clear();
//		this.profiler.next("regular");

		for(int i1 = 0; i1 < this.entities.size(); ++i1) {
			Entity entity2 = this.entities.get(i1);

			if(entity2.vehicle != null) {
				if(!entity2.vehicle.dead && entity2.vehicle.passenger == entity2) {
					continue;
				}

				entity2.vehicle.passenger = null;
				entity2.vehicle = null;
			}

//			this.profiler.start("tick");

			if(!entity2.dead) {
				this.updateEntity(entity2, true);
			}

//			this.profiler.end();
		}

//		this.profiler.next("dead");
		Iterator<Entity> ents = this.entities.iterator();
		while(ents.hasNext()) {
			Entity entity2 = ents.next();

			if(entity2.dead) {
				int k1 = entity2.chunkCoordX;
				int i2 = entity2.chunkCoordZ;

				if(entity2.addedToChunk && this.isLoaded(k1, i2, true)) {
					this.getChunk(k1, i2).removeEntity(entity2);
				}

				ents.remove();
				this.onEntityRemoved(entity2);
			}
		}

//		this.profiler.next("blockEntities");
		this.loadTiles = true;
		Iterator<TileEntity> iterator = this.tickable.iterator();

		while(iterator.hasNext()) {
			TileEntity tileentity = (TileEntity)iterator.next();

			if(!tileentity.isInvalid() && tileentity.hasWorldObj()) {
				BlockPos blockpos = tileentity.getPos();

				if(this.isBlockLoaded(blockpos)) { // && this.border.contains(blockpos)) {
					((ITickable)tileentity).update();
				}
			}

			if(tileentity.isInvalid()) {
				iterator.remove();
				this.tiles.remove(tileentity);

				if(this.isBlockLoaded(tileentity.getPos())) {
					this.getChunk(tileentity.getPos()).removeTileEntity(tileentity.getPos());
				}
			}
		}

		this.loadTiles = false;

		if(!this.removeTiles.isEmpty()) {
			this.tickable.removeAll(this.removeTiles);
			this.tiles.removeAll(this.removeTiles);
			this.removeTiles.clear();
		}

//		this.profiler.next("pendingBlockEntities");

		if(!this.addedTiles.isEmpty()) {
			for(int j1 = 0; j1 < this.addedTiles.size(); ++j1) {
				TileEntity tileentity1 = (TileEntity)this.addedTiles.get(j1);

				if(!tileentity1.isInvalid()) {
					if(!this.tiles.contains(tileentity1)) {
						this.addTileEntity(tileentity1);
					}

					if(this.isBlockLoaded(tileentity1.getPos())) {
						this.getChunk(tileentity1.getPos()).addTileEntity(tileentity1.getPos(), tileentity1);
					}

					this.markBlockForUpdate(tileentity1.getPos());
				}
			}

			this.addedTiles.clear();
		}

//		this.profiler.end();
//		this.profiler.end();
	}

	public boolean addTileEntity(TileEntity tile) {
		boolean flag = this.tiles.add(tile);

		if(flag && tile instanceof ITickable) {
			this.tickable.add(tile);
		}

		return flag;
	}

	public void addTileEntities(Collection<TileEntity> tileEntityCollection) {
		if(this.loadTiles) {
			this.addedTiles.addAll(tileEntityCollection);
		}
		else {
			for(TileEntity tileentity : tileEntityCollection) {
				this.tiles.add(tileentity);

				if(tileentity instanceof ITickable) {
					this.tickable.add(tileentity);
				}
			}
		}
	}

	public void updateEntity(Entity entityIn, boolean forceUpdate) {
		int i = ExtMath.floord(entityIn.posX);
		int j = ExtMath.floord(entityIn.posZ);
		int k = 32;

		if(!forceUpdate || this.isAreaLoaded(i - k, 0, j - k, i + k, 0, j + k, true)) {
			entityIn.lastTickPosX = entityIn.posX;
			entityIn.lastTickPosY = entityIn.posY;
			entityIn.lastTickPosZ = entityIn.posZ;
			entityIn.prevYaw = entityIn.rotYaw;
			entityIn.prevPitch = entityIn.rotPitch;

			if(forceUpdate && entityIn.addedToChunk) {
				++entityIn.ticksExisted;

				if(entityIn.vehicle != null) {
					entityIn.updateRidden();
				}
				else {
					entityIn.onUpdate();
				}
			}

//			this.profiler.start("chunkCheck");

			if(Double.isNaN(entityIn.posX) || Double.isInfinite(entityIn.posX)) {
				entityIn.posX = entityIn.lastTickPosX;
			}

			if(Double.isNaN(entityIn.posY) || Double.isInfinite(entityIn.posY)) {
				entityIn.posY = entityIn.lastTickPosY;
			}

			if(Double.isNaN(entityIn.posZ) || Double.isInfinite(entityIn.posZ)) {
				entityIn.posZ = entityIn.lastTickPosZ;
			}

			if(Double.isNaN((double)entityIn.rotPitch) || Double.isInfinite((double)entityIn.rotPitch)) {
				entityIn.rotPitch = entityIn.prevPitch;
			}

			if(Double.isNaN((double)entityIn.rotYaw) || Double.isInfinite((double)entityIn.rotYaw)) {
				entityIn.rotYaw = entityIn.prevYaw;
			}

			int l = ExtMath.floord(entityIn.posX / 16.0D);
			int i1 = ExtMath.floord(entityIn.posY / 16.0D);
			int j1 = ExtMath.floord(entityIn.posZ / 16.0D);

			if(!entityIn.addedToChunk || entityIn.chunkCoordX != l || entityIn.chunkCoordY != i1 || entityIn.chunkCoordZ != j1) {
				if(entityIn.addedToChunk && this.isLoaded(entityIn.chunkCoordX, entityIn.chunkCoordZ, true)) {
					this.getChunk(entityIn.chunkCoordX, entityIn.chunkCoordZ).removeEntity(entityIn); // fix red. method
				}

				if(this.isLoaded(l, j1, true)) {
					entityIn.addedToChunk = true;
					this.getChunk(l, j1).addEntity(entityIn);
				}
				else {
					entityIn.addedToChunk = false;
				}
			}

//			this.profiler.end();

			if(forceUpdate && entityIn.addedToChunk && entityIn.passenger != null) {
				if(!entityIn.passenger.dead && entityIn.passenger.vehicle == entityIn) {
					this.updateEntity(entityIn.passenger, true);
				}
				else {
					entityIn.passenger.vehicle = null;
					entityIn.passenger = null;
				}
			}
		}
	}

	public boolean checkNoEntityCollision(BoundingBox bb) {
		return this.checkNoEntityCollision(bb, (Entity)null);
	}

	public boolean checkNoEntityCollision(BoundingBox bb, Entity entityIn) {
		List<Entity> list = this.getEntitiesWithinAABBExcludingEntity((Entity)null, bb);

		for(int i = 0; i < list.size(); ++i) {
			Entity entity = (Entity)list.get(i);

			if(!entity.dead && entity.preventSpawning && entity != entityIn
					&& (entityIn == null || entityIn.vehicle != entity && entityIn.passenger != entity)) {
				return false;
			}
		}

		return true;
	}

	public boolean isAnyLiquid(BoundingBox bb) {
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ);
		BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

		for(int k1 = i; k1 <= j; ++k1) {
			for(int l1 = k; l1 <= l; ++l1) {
				for(int i2 = i1; i2 <= j1; ++i2) {
					Block block = this.getState(blockpos$mutableblockpos.set(k1, l1, i2)).getBlock();

					if(block.getMaterial().isLiquid()) {
						return true;
					}
				}
			}
		}

		return false;
	}

	public boolean isFlammableWithin(BoundingBox bb) {
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);

		if(this.isAreaLoaded(i, k, i1, j, l, j1, true)) {
			BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

			for(int k1 = i; k1 < j; ++k1) {
				for(int l1 = k; l1 < l; ++l1) {
					for(int i2 = i1; i2 < j1; ++i2) {
						Block block = this.getState(blockpos$mutableblockpos.set(k1, l1, i2)).getBlock();

						if(block == Blocks.fire || block == Blocks.flowing_lava || block == Blocks.lava) {
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	public boolean handleLiquidAcceleration(BoundingBox bb, Entity entityIn) {
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);

		if(!this.isAreaLoaded(i, k, i1, j, l, j1, true)) {
			return false;
		}
		else {
			boolean flag = false;
			Vec3 vec3 = new Vec3(0.0D, 0.0D, 0.0D);
			BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

			for(int k1 = i; k1 < j; ++k1) {
				for(int l1 = k; l1 < l; ++l1) {
					for(int i2 = i1; i2 < j1; ++i2) {
						blockpos$mutableblockpos.set(k1, l1, i2);
						State iblockstate = this.getState(blockpos$mutableblockpos);
						Block block = iblockstate.getBlock();

						if(block.getMaterial().isColdLiquid()) {
							double d0 = (double)((float)(l1 + 1)
									- BlockLiquid.getLiquidHeightPercent(((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue()));

							if((double)l >= d0) {
								flag = true;
								vec3 = block.modifyAcceleration(this, blockpos$mutableblockpos, entityIn, vec3);
							}
						}
					}
				}
			}

			if(vec3.lengthVector() > 0.0D && entityIn.isPushedByWater()) {
				vec3 = vec3.normalize();
				double d1 = 0.014D;
				entityIn.motionX += vec3.xCoord * d1;
				entityIn.motionY += vec3.yCoord * d1;
				entityIn.motionZ += vec3.zCoord * d1;
			}

			return flag;
		}
	}

	public boolean isMaterialInMolten(BoundingBox bb) {
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);
		BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

		for(int k1 = i; k1 < j; ++k1) {
			for(int l1 = k; l1 < l; ++l1) {
				for(int i2 = i1; i2 < j1; ++i2) {
					if(this.getState(blockpos$mutableblockpos.set(k1, l1, i2)).getBlock().getMaterial().isHotLiquid()) {
						return true;
					}
				}
			}
		}

		return false;
	}

	public boolean isAABBInLiquid(BoundingBox bb) {
		int i = ExtMath.floord(bb.minX);
		int j = ExtMath.floord(bb.maxX + 1.0D);
		int k = ExtMath.floord(bb.minY);
		int l = ExtMath.floord(bb.maxY + 1.0D);
		int i1 = ExtMath.floord(bb.minZ);
		int j1 = ExtMath.floord(bb.maxZ + 1.0D);
		BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

		for(int k1 = i; k1 < j; ++k1) {
			for(int l1 = k; l1 < l; ++l1) {
				for(int i2 = i1; i2 < j1; ++i2) {
					State iblockstate = this.getState(blockpos$mutableblockpos.set(k1, l1, i2));
					Block block = iblockstate.getBlock();

					if(block.getMaterial().isColdLiquid()) {
						int j2 = ((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue();
						double d0 = (double)(l1 + 1);

						if(j2 < 8) {
							d0 = (double)(l1 + 1) - (double)j2 / 8.0D;
						}

						if(d0 >= bb.minY) {
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	public Explosion createExplosion(Entity entityIn, double x, double y, double z, float strength, boolean isSmoking) {
		return this.newExplosion(entityIn, x, y, z, strength, false, isSmoking, false);
	}

	public Explosion createAltExplosion(Entity entityIn, double x, double y, double z, float strength, boolean isSmoking) {
		return this.newExplosion(entityIn, x, y, z, strength, false, isSmoking, true);
	}

	public Explosion newExplosion(Entity entityIn, double x, double y, double z, float strength, boolean isFlaming, boolean isSmoking, boolean altSound) {
		Explosion explosion = new Explosion(this, entityIn, x, y, z, strength, isFlaming, isSmoking);
		explosion.doExplosionA();
		explosion.doExplosionB(true, altSound);
		return explosion;
	}

	public EntityExplosion newExplosion(double x, double y, double z, int strength) {
		EntityExplosion explosion = new EntityExplosion(this, x, y, z, strength);
		this.spawnEntityInWorld(explosion);
		return explosion;
	}

	public float getBlockDensity(Vec3 vec, BoundingBox bb) {
		double d0 = 1.0D / ((bb.maxX - bb.minX) * 2.0D + 1.0D);
		double d1 = 1.0D / ((bb.maxY - bb.minY) * 2.0D + 1.0D);
		double d2 = 1.0D / ((bb.maxZ - bb.minZ) * 2.0D + 1.0D);
		double d3 = (1.0D - Math.floor(1.0D / d0) * d0) / 2.0D;
		double d4 = (1.0D - Math.floor(1.0D / d2) * d2) / 2.0D;

		if(d0 >= 0.0D && d1 >= 0.0D && d2 >= 0.0D) {
			int i = 0;
			int j = 0;

			for(float f = 0.0F; f <= 1.0F; f = (float)((double)f + d0)) {
				for(float f1 = 0.0F; f1 <= 1.0F; f1 = (float)((double)f1 + d1)) {
					for(float f2 = 0.0F; f2 <= 1.0F; f2 = (float)((double)f2 + d2)) {
						double d5 = bb.minX + (bb.maxX - bb.minX) * (double)f;
						double d6 = bb.minY + (bb.maxY - bb.minY) * (double)f1;
						double d7 = bb.minZ + (bb.maxZ - bb.minZ) * (double)f2;

						if(this.rayTraceBlocks(new Vec3(d5 + d3, d6, d7 + d4), vec) == null) {
							++i;
						}

						++j;
					}
				}
			}

			return (float)i / (float)j;
		}
		else {
			return 0.0F;
		}
	}

	public boolean extinguishFire(EntityNPC player, BlockPos pos, Facing side) {
		pos = pos.offset(side);

		if(this.getState(pos).getBlock() == Blocks.fire) {
			this.playAuxSFX(player, 1004, pos, 0);
			this.setBlockToAir(pos);
			return true;
		}
		else {
			return false;
		}
	}

	public TileEntity getTileEntity(BlockPos pos) {
		if(!isValid(pos)) {
			return null;
		}
		else {
			TileEntity tileentity = null;

			if(this.loadTiles) {
				for(int i = 0; i < this.addedTiles.size(); ++i) {
					TileEntity tileentity1 = (TileEntity)this.addedTiles.get(i);

					if(!tileentity1.isInvalid() && tileentity1.getPos().equals(pos)) {
						tileentity = tileentity1;
						break;
					}
				}
			}

			if(tileentity == null) {
				tileentity = this.getChunk(pos).getTileEntity(pos, TileEntity.EnumCreateEntityType.IMMEDIATE);
			}

			if(tileentity == null) {
				for(int j = 0; j < this.addedTiles.size(); ++j) {
					TileEntity tileentity2 = (TileEntity)this.addedTiles.get(j);

					if(!tileentity2.isInvalid() && tileentity2.getPos().equals(pos)) {
						tileentity = tileentity2;
						break;
					}
				}
			}

			return tileentity;
		}
	}

	public void setTileEntity(BlockPos pos, TileEntity tileEntityIn) {
		if(tileEntityIn != null && !tileEntityIn.isInvalid()) {
			if(this.loadTiles) {
				tileEntityIn.setPos(pos);
				Iterator<TileEntity> iterator = this.addedTiles.iterator();

				while(iterator.hasNext()) {
					TileEntity tileentity = (TileEntity)iterator.next();

					if(tileentity.getPos().equals(pos)) {
						tileentity.invalidate();
						iterator.remove();
					}
				}

				this.addedTiles.add(tileEntityIn);
			}
			else {
				this.addTileEntity(tileEntityIn);
				this.getChunk(pos).addTileEntity(pos, tileEntityIn);
			}
		}
	}

	public void removeTileEntity(BlockPos pos) {
		TileEntity tileentity = this.getTileEntity(pos);

		if(tileentity != null && this.loadTiles) {
			tileentity.invalidate();
			this.addedTiles.remove(tileentity);
		}
		else {
			if(tileentity != null) {
				this.addedTiles.remove(tileentity);
				this.tiles.remove(tileentity);
				this.tickable.remove(tileentity);
			}

			this.getChunk(pos).removeTileEntity(pos);
		}
	}

	public void markTileEntityForRemoval(TileEntity tileEntityIn) {
		this.removeTiles.add(tileEntityIn);
	}

	protected void calculateInitialSkylight() {
		int light = this.calculateSkylightSubtracted(false);
		if(light != this.subtract)
			this.subtract = light;
	}

	protected void calculateInitialWeather() {
		this.rain = this.weather.hasDownfall() ? 1.0f : 0.0f;
		this.darkness = this.weather.isDark() ? 1.0f : 0.0f;
		this.fog = this.weather.getFogIntensity();
		this.temp = this.getBaseTemperature() + this.weather.getTemperature();
	}

	protected void setActivePlayerChunksAndCheckLight(int l) {
		this.active.clear();
//		this.profiler.start("buildList");

		for(int i = 0; i < this.players.size(); ++i) {
			EntityNPC entityplayer = (EntityNPC)this.players.get(i);
			int j = ExtMath.floord(entityplayer.posX / 16.0D);
			int k = ExtMath.floord(entityplayer.posZ / 16.0D);
//			int l = this.getRenderDistanceChunks();

			for(int i1 = -l; i1 <= l; ++i1) {
				for(int j1 = -l; j1 <= l; ++j1) {
					this.active.add(new ChunkPos(i1 + j, j1 + k));
				}
			}
		}

//		this.profiler.end();
//
//		this.profiler.start("playerCheckLight");

		if(!this.players.isEmpty()) {
			int k1 = this.rand.zrange(this.players.size());
			EntityNPC entityplayer1 = (EntityNPC)this.players.get(k1);
			int l1 = ExtMath.floord(entityplayer1.posX) + this.rand.zrange(11) - 5;
			int i2 = ExtMath.floord(entityplayer1.posY) + this.rand.zrange(11) - 5;
			int j2 = ExtMath.floord(entityplayer1.posZ) + this.rand.zrange(11) - 5;
			this.checkLight(new BlockPos(l1, i2, j2));
		}

//		this.profiler.end();
	}

	public float getTempOffset() {
		return this.temp;
	}
    
    protected float getBaseTemperature() {
    	return this.dimension.getTemperature() + this.dimension.getOrbitOffset() * 
    			ExtMath.sin((((float)(this.daytime % this.dimension.getOrbitalPeriod()) / (float)this.dimension.getOrbitalPeriod()) 
    			-0.125f) * (float)Math.PI * 2.0f);
    }
    
    public LeavesType getLeavesGen(BlockPos pos) {
		return this.canFreezeAt(pos) ? LeavesType.SNOWY : ((!this.dimension.getType().days || this.dimension.getOrbitOffset() == 0.0f) ? 
				this.dimension.getLeavesType() : LeavesType.values()[(int)((this.daytime % 
				this.dimension.getOrbitalPeriod()) * (long)LeavesType.values().length / this.dimension.getOrbitalPeriod())]);
	}

	public float getTemperatureK(BlockPos pos) {
		return this.temp + this.getBiomeGenForCoords(pos).getTemperature(pos);
	}

	public float getTemperatureC(BlockPos pos) {
		return World.ABSOLUTE_ZERO + this.getTemperatureK(pos);
	}

	public boolean canFreezeAt(BlockPos pos) {
		return this.getTemperatureC(pos) <= 0.0F;
	}

	public boolean canBurnAt(BlockPos pos) {
		return this.getTemperatureC(pos) >= 194.0f;
	}

	public boolean doesWaterVaporize(BlockPos pos) {
		return this.getTemperatureC(pos) >= 100.0f;
	}

	public boolean isLavaFaster(BlockPos pos) {
		return this.getTemperatureC(pos) >= 314.0f;
	}

	public boolean canSnowAt(BlockPos pos, boolean checkLight, boolean allowLayers) {
		if(!this.canFreezeAt(pos)) {
			return false;
		}
		else if(!checkLight) {
			return true;
		}
		else {
			if(pos.getY() >= 0 && pos.getY() < 512 && this.getLightFor(LightType.BLOCK, pos) < 10) {
				Block block = this.getState(pos).getBlock();

				if((block.getMaterial() == Material.air || (allowLayers && block == Blocks.snow_layer)) 
						&& Blocks.snow_layer.canPlaceBlockAt(this, pos)) {
					return true;
				}
			}

			return false;
		}
	}

	public boolean checkLight(BlockPos pos) {
		boolean flag = false;

		if(!this.dimension.hasNoLight()) {
			flag |= this.checkLightFor(LightType.SKY, pos);
		}

		flag = flag | this.checkLightFor(LightType.BLOCK, pos);
		return flag;
	}

	private int getRawLight(BlockPos pos, LightType lightType) {
		if(lightType == LightType.SKY && this.canSeeSky(pos)) {
			return 15;
		}
		else {
			Block block = this.getState(pos).getBlock();
			int i = lightType == LightType.SKY ? 0 : block.getLightValue();
			int j = block.getLightOpacity();

			if(j >= 15 && block.getLightValue() > 0) {
				j = 1;
			}

			if(j < 1) {
				j = 1;
			}

			if(j >= 15) {
				return 0;
			}
			else if(i >= 14) {
				return i;
			}
			else {
				for(Facing enumfacing : Facing.values()) {
					BlockPos blockpos = pos.offset(enumfacing);
					int k = this.getLightFor(lightType, blockpos) - j;

					if(k > i) {
						i = k;
					}

					if(i >= 14) {
						return i;
					}
				}

				return i;
			}
		}
	}

	public boolean checkLightFor(LightType lightType, BlockPos pos) {
		if(!this.isAreaLoaded(pos, 17, false)) {
			return false;
		}
		else {
			int i = 0;
			int j = 0;
//			this.profiler.start("getBrightness");
			int k = this.getLightFor(lightType, pos);
			int l = this.getRawLight(pos, lightType);
			int i1 = pos.getX();
			int j1 = pos.getY();
			int k1 = pos.getZ();

			if(l > k) {
				this.lightUpdate[j++] = 133152;
			}
			else if(l < k) {
				this.lightUpdate[j++] = 133152 | k << 18;

				while(i < j) {
					int l1 = this.lightUpdate[i++];
					int i2 = (l1 & 63) - 32 + i1;
					int j2 = (l1 >> 6 & 63) - 32 + j1;
					int k2 = (l1 >> 12 & 63) - 32 + k1;
					int l2 = l1 >> 18 & 15;
					BlockPos blockpos = new BlockPos(i2, j2, k2);
					int i3 = this.getLightFor(lightType, blockpos);

					if(i3 == l2) {
						this.setLightFor(lightType, blockpos, 0);

						if(l2 > 0) {
							int j3 = ExtMath.absi(i2 - i1);
							int k3 = ExtMath.absi(j2 - j1);
							int l3 = ExtMath.absi(k2 - k1);

							if(j3 + k3 + l3 < 17) {
								BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();

								for(Facing enumfacing : Facing.values()) {
									int i4 = i2 + enumfacing.getFrontOffsetX();
									int j4 = j2 + enumfacing.getFrontOffsetY();
									int k4 = k2 + enumfacing.getFrontOffsetZ();
									blockpos$mutableblockpos.set(i4, j4, k4);
									int l4 = Math.max(1, this.getState(blockpos$mutableblockpos).getBlock().getLightOpacity());
									i3 = this.getLightFor(lightType, blockpos$mutableblockpos);

									if(i3 == l2 - l4 && j < this.lightUpdate.length) {
										this.lightUpdate[j++] = i4 - i1 + 32 | j4 - j1 + 32 << 6 | k4 - k1 + 32 << 12 | l2 - l4 << 18;
									}
								}
							}
						}
					}
				}

				i = 0;
			}

//			this.profiler.end();
//			this.profiler.start("checkedPosition < toCheckCount");

			while(i < j) {
				int i5 = this.lightUpdate[i++];
				int j5 = (i5 & 63) - 32 + i1;
				int k5 = (i5 >> 6 & 63) - 32 + j1;
				int l5 = (i5 >> 12 & 63) - 32 + k1;
				BlockPos blockpos1 = new BlockPos(j5, k5, l5);
				int i6 = this.getLightFor(lightType, blockpos1);
				int j6 = this.getRawLight(blockpos1, lightType);

				if(j6 != i6) {
					this.setLightFor(lightType, blockpos1, j6);

					if(j6 > i6) {
						int k6 = Math.abs(j5 - i1);
						int l6 = Math.abs(k5 - j1);
						int i7 = Math.abs(l5 - k1);
						boolean flag = j < this.lightUpdate.length - 6;

						if(k6 + l6 + i7 < 17 && flag) {
							if(this.getLightFor(lightType, blockpos1.west()) < j6) {
								this.lightUpdate[j++] = j5 - 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
							}

							if(this.getLightFor(lightType, blockpos1.east()) < j6) {
								this.lightUpdate[j++] = j5 + 1 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
							}

							if(this.getLightFor(lightType, blockpos1.down()) < j6) {
								this.lightUpdate[j++] = j5 - i1 + 32 + (k5 - 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
							}

							if(this.getLightFor(lightType, blockpos1.up()) < j6) {
								this.lightUpdate[j++] = j5 - i1 + 32 + (k5 + 1 - j1 + 32 << 6) + (l5 - k1 + 32 << 12);
							}

							if(this.getLightFor(lightType, blockpos1.north()) < j6) {
								this.lightUpdate[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 - 1 - k1 + 32 << 12);
							}

							if(this.getLightFor(lightType, blockpos1.south()) < j6) {
								this.lightUpdate[j++] = j5 - i1 + 32 + (k5 - j1 + 32 << 6) + (l5 + 1 - k1 + 32 << 12);
							}
						}
					}
				}
			}

//			this.profiler.end();
			return true;
		}
	}

	public List<Entity> getEntitiesWithinAABBExcludingEntity(Entity entityIn, BoundingBox bb) {
		return this.getEntitiesInAABBexcluding(entityIn, bb, null);
	}

	public List<Entity> getEntitiesInAABBexcluding(Entity entityIn, BoundingBox boundingBox, Predicate<? super Entity> predicate) {
		List<Entity> list = Lists.<Entity>newArrayList();
		int i = ExtMath.floord((boundingBox.minX - 2.0D) / 16.0D);
		int j = ExtMath.floord((boundingBox.maxX + 2.0D) / 16.0D);
		int k = ExtMath.floord((boundingBox.minZ - 2.0D) / 16.0D);
		int l = ExtMath.floord((boundingBox.maxZ + 2.0D) / 16.0D);

		for(int i1 = i; i1 <= j; ++i1) {
			for(int j1 = k; j1 <= l; ++j1) {
				if(this.isLoaded(i1, j1, true)) {
					this.getChunk(i1, j1).getEntities(entityIn, boundingBox, list, predicate);
				}
			}
		}

		return list;
	}

	public <T extends Entity> List<T> getEntitiesWithinAABB(Class<? extends T> classEntity, BoundingBox bb) {
		return this.<T>getEntitiesWithinAABB(classEntity, bb, null);
	}

	public <T extends Entity> List<T> getEntitiesWithinAABB(Class<? extends T> clazz, BoundingBox aabb, Predicate<? super T> filter) {
		int i = ExtMath.floord((aabb.minX - 2.0D) / 16.0D);
		int j = ExtMath.floord((aabb.maxX + 2.0D) / 16.0D);
		int k = ExtMath.floord((aabb.minZ - 2.0D) / 16.0D);
		int l = ExtMath.floord((aabb.maxZ + 2.0D) / 16.0D);
		List<T> list = Lists.<T>newArrayList();

		for(int i1 = i; i1 <= j; ++i1) {
			for(int j1 = k; j1 <= l; ++j1) {
				if(this.isLoaded(i1, j1, true)) {
					this.getChunk(i1, j1).getEntities(clazz, aabb, list, filter);
				}
			}
		}

		return list;
	}

	public Entity getEntityByID(int id) {
		return (Entity)this.entityIds.lookup(id);
	}

	public void markChunkDirty(BlockPos pos, TileEntity unusedTileEntity) {
		if(this.isBlockLoaded(pos)) {
			this.getChunk(pos).setModified();
		}
	}

	public void loadEntities(Collection<Entity> entityCollection) {
		this.entities.addAll(entityCollection);

		for(Entity entity : entityCollection) {
			this.onEntityAdded(entity);
		}
	}

	public void unloadEntities(Collection<Entity> entityCollection) {
		this.unloaded.addAll(entityCollection);
	}

	public boolean canBlockBePlaced(Block blockIn, BlockPos pos, boolean p_175716_3_, Facing side, Entity entityIn, ItemStack itemStackIn) {
		Block block = this.getState(pos).getBlock();
		BoundingBox axisalignedbb = p_175716_3_ ? null : blockIn.getCollisionBoundingBox(this, pos, blockIn.getState());
		return axisalignedbb != null && !this.checkNoEntityCollision(axisalignedbb, entityIn) ? false
				: (block.getMaterial() == Material.circuits && blockIn == Blocks.anvil ? true
						: block.getMaterial().isReplaceable() && blockIn.canReplace(this, pos, side, itemStackIn));
	}

	public int getSeaLevel() {
		return 63;
	}

	private int getStrongPower(BlockPos pos, Facing direction) {
		State iblockstate = this.getState(pos);
		return iblockstate.getBlock().getStrongPower(this, pos, iblockstate, direction);
	}

	public int getStrongPower(BlockPos pos) {
		int i = 0;
		i = Math.max(i, this.getStrongPower(pos.down(), Facing.DOWN));

		if(i >= 15) {
			return i;
		}
		else {
			i = Math.max(i, this.getStrongPower(pos.up(), Facing.UP));

			if(i >= 15) {
				return i;
			}
			else {
				i = Math.max(i, this.getStrongPower(pos.north(), Facing.NORTH));

				if(i >= 15) {
					return i;
				}
				else {
					i = Math.max(i, this.getStrongPower(pos.south(), Facing.SOUTH));

					if(i >= 15) {
						return i;
					}
					else {
						i = Math.max(i, this.getStrongPower(pos.west(), Facing.WEST));

						if(i >= 15) {
							return i;
						}
						else {
							i = Math.max(i, this.getStrongPower(pos.east(), Facing.EAST));
							return i >= 15 ? i : i;
						}
					}
				}
			}
		}
	}

	public boolean isSidePowered(BlockPos pos, Facing side) {
		return this.getRedstonePower(pos, side) > 0;
	}

	public int getRedstonePower(BlockPos pos, Facing facing) {
		State iblockstate = this.getState(pos);
		Block block = iblockstate.getBlock();
		return block.isNormalCube() ? this.getStrongPower(pos) : block.getWeakPower(this, pos, iblockstate, facing);
	}

	public boolean isBlockPowered(BlockPos pos) {
		return this.getRedstonePower(pos.down(), Facing.DOWN) > 0 ? true
				: (this.getRedstonePower(pos.up(), Facing.UP) > 0 ? true
						: (this.getRedstonePower(pos.north(), Facing.NORTH) > 0 ? true
								: (this.getRedstonePower(pos.south(), Facing.SOUTH) > 0 ? true
										: (this.getRedstonePower(pos.west(), Facing.WEST) > 0 ? true
												: this.getRedstonePower(pos.east(), Facing.EAST) > 0))));
	}

	public int isBlockIndirectlyGettingPowered(BlockPos pos) {
		int i = 0;

		for(Facing enumfacing : Facing.values()) {
			int j = this.getRedstonePower(pos.offset(enumfacing), enumfacing);

			if(j >= 15) {
				return 15;
			}

			if(j > i) {
				i = j;
			}
		}

		return i;
	}

	public EntityNPC getClosestPlayerToEntity(Entity entityIn, double distance) {
		return this.getClosestPlayer(entityIn.posX, entityIn.posY, entityIn.posZ, distance);
	}

	public EntityNPC getClosestPlayer(double x, double y, double z, double distance) {
		double d0 = -1.0D;
		EntityNPC entityplayer = null;

		for(int i = 0; i < this.players.size(); ++i) {
			EntityNPC entityplayer1 = (EntityNPC)this.players.get(i);

//			if(EntitySelectors.NOT_SPECTATING.apply(entityplayer1)) {
			double d1 = entityplayer1.getDistanceSq(x, y, z);

			if((distance < 0.0D || d1 < distance * distance) && (d0 == -1.0D || d1 < d0)) {
				d0 = d1;
				entityplayer = entityplayer1;
			}
//			}
		}

		return entityplayer;
	}

	public boolean isAnyPlayerWithinRangeAt(double x, double y, double z, double range) {
		for(int i = 0; i < this.players.size(); ++i) {
			EntityNPC entityplayer = (EntityNPC)this.players.get(i);

//			if(EntitySelectors.NOT_SPECTATING.apply(entityplayer)) {
			double d0 = entityplayer.getDistanceSq(x, y, z);

			if(range < 0.0D || d0 < range * range) {
				return true;
			}
//			}
		}

		return false;
	}

//	public EntityNPC getPlayer(String name) {
//		for(int i = 0; i < this.players.size(); ++i) {
//			EntityNPC entityplayer = this.players.get(i);
//
//			if(name.equalsIgnoreCase(entityplayer.getUser())) {
//				return entityplayer;
//			}
//		}
//
//		return null;
//	}

	public void setTimeFactor(int factor) {
		this.timeFactor = Math.max((long)factor, 1L);
//		if(this.timeFactor > Constants.EARTH_DAY)
//			this.timeFactor = (this.timeFactor - (this.timeFactor % Constants.EARTH_DAY)) + 1L;
	}

	public void setEntityState(Entity entityIn, byte state) {
	}

	public void addBlockEvent(BlockPos pos, Block blockIn, int eventID, int eventParam) {
		blockIn.onBlockEventReceived(this, pos, this.getState(pos), eventID, eventParam);
	}

	public Weather getWeather() {
		return this.weather;
	}

	public long getDayTime() {
		return this.daytime;
	}

	public void setWeather(Weather weather) {
		this.weather = weather;
	}

	public void setDayTime(long time) {
		this.daytime = time;
	}

	public float getDarkness() {
		return this.darkness;
	}

	public void setDarkness(float dark) {
		this.darkness = dark;
	}

	public float getRainStrength() {
		return this.rain;
	}

	public void setRainStrength(float strength) {
		this.rain = strength;
	}

	public float getFogStrength() {
		return this.fog;
	}

	public void setFogStrength(float strength) {
		this.fog = strength;
	}

	public void setTemperature(float temp) {
		this.temp = temp;
	}

	public boolean isDark() {
		return this.darkness > 0.9f;
	}

	public boolean isThundering() {
		return this.isDark() && this.weather.hasThunder() && (!this.weather.hasDownfall() || this.hasDownfall());
	}

	public boolean hasDownfall() {
		return this.rain > 0.2f;
	}

	public boolean isRaining() {
		return this.hasDownfall() && this.weather.canRain();
	}

	public boolean isRainingAt(BlockPos strikePosition, boolean wet) {
		if(wet ? !this.isRaining() : !this.hasDownfall()) {
			return false;
		}
		else if(!this.canSeeSky(strikePosition)) {
			return false;
		}
		else if(this.getPrecipitationHeight(strikePosition).getY() > strikePosition.getY()) {
			return false;
		}
		return !this.canSnowAt(strikePosition, false, false);
//		else {
//			Biome biomegenbase = this.getBiomeGenForCoords(strikePosition);
//			return biomegenbase.isSnowyBiome() ? false : ( ? false : biomegenbase.canRain());
//		}
	}

//	public void setItemData(String dataID, WorldSavedData worldSavedDataIn) {
//	}
//
//	public WorldSavedData loadItemData(Class<? extends WorldSavedData> clazz, String dataID) {
//		return null;
//	}
//
//	public int getNextMapId() {
//		return 0;
//	}

	public final void playAuxSFX(int type, BlockPos pos, int data) {
		this.playAuxSFX(null, type, pos, data);
	}

	public void updateComparatorOutputLevel(BlockPos pos, Block blockIn) {
		for(Facing enumfacing : Facing.Plane.HORIZONTAL) {
			BlockPos blockpos = pos.offset(enumfacing);

			if(this.isBlockLoaded(blockpos)) {
				State iblockstate = this.getState(blockpos);

				if(Blocks.comparator.isAssociated(iblockstate.getBlock())) {
					iblockstate.getBlock().onNeighborBlockChange(this, blockpos, iblockstate, blockIn);
				}
				else if(iblockstate.getBlock().isNormalCube()) {
					blockpos = blockpos.offset(enumfacing);
					iblockstate = this.getState(blockpos);

					if(Blocks.comparator.isAssociated(iblockstate.getBlock())) {
						iblockstate.getBlock().onNeighborBlockChange(this, blockpos, iblockstate, blockIn);
					}
				}
			}
		}
	}

//	public DifficultyInstance getDifficultyForLocation(BlockPos pos) {
//		return null;
//	}

	public void scheduleUpdate(BlockPos pos, Block blockIn, int delay) {
	}

	public void spawnParticle(ParticleType particleType, double xCoord, double yCoord, double zCoord, double xOffset, double yOffset,
			double zOffset, int... data) {
	}
	
//	public Difficulty getDifficulty() {
//		return this.difficulty;
//	}
	
//	public void setDifficulty(Difficulty diff) {
//		this.difficulty = diff;
//	}
	
	public abstract Chunk getChunk(int x, int z);
	public abstract void markBlockForUpdate(BlockPos pos);
	protected abstract void notifyLightSet(BlockPos pos);
	public abstract void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2);
	public abstract void playSound(SoundEvent sound, double x, double y, double z, float volume);
	protected abstract void onEntityAdded(Entity entityIn);
	protected abstract void onEntityRemoved(Entity entityIn);
//	public abstract void broadcastSound(int soundID, BlockPos pos, int data);
	public abstract void playAuxSFX(EntityNPC player, int sfxType, BlockPos blockPosIn, int data);
	public abstract void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress);
}
