package thebetweenlands.common.entity.mobs;

import java.util.List;
import java.util.UUID;

import javax.annotation.Nullable;

import com.google.common.base.Optional;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIAttackMelee;
import net.minecraft.entity.ai.EntityAIHurtByTarget;
import net.minecraft.entity.ai.EntityAILookIdle;
import net.minecraft.entity.ai.EntityAIMoveTowardsRestriction;
import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
import net.minecraft.entity.ai.EntityAISwimming;
import net.minecraft.entity.ai.EntityAIWander;
import net.minecraft.entity.ai.EntityAIWatchClosest;
import net.minecraft.entity.ai.attributes.IAttribute;
import net.minecraft.entity.ai.attributes.RangedAttribute;
import net.minecraft.entity.item.EntityXPOrb;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.pathfinding.Path;
import net.minecraft.pathfinding.PathNavigate;
import net.minecraft.pathfinding.PathNodeType;
import net.minecraft.pathfinding.PathPoint;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EntityDamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockPos.MutableBlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BossInfo;
import net.minecraft.world.BossInfoServer;
import net.minecraft.world.World;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.fml.common.network.NetworkRegistry.TargetPoint;
import thebetweenlands.api.entity.IBLBoss;
import thebetweenlands.api.entity.IEntityBL;
import thebetweenlands.api.entity.IEntityCameraOffset;
import thebetweenlands.api.entity.IEntityMusic;
import thebetweenlands.api.entity.IEntityScreenShake;
import thebetweenlands.client.audio.EntityMusicLayers;
import thebetweenlands.common.TheBetweenlands;
import thebetweenlands.common.entity.movement.IPathObstructionAwareEntity;
import thebetweenlands.common.entity.movement.ObstructionAwarePathNavigateGround;
import thebetweenlands.common.entity.projectiles.EntitySludgeBall;
import thebetweenlands.common.network.clientbound.MessageSummonPeatMummyParticles;
import thebetweenlands.common.registries.BlockRegistry;
import thebetweenlands.common.registries.LootTableRegistry;
import thebetweenlands.common.registries.SoundRegistry;
import thebetweenlands.common.sound.BLSoundEvent;

public class EntityDreadfulMummy extends EntityMob implements IEntityBL, IBLBoss, IEntityScreenShake, IEntityCameraOffset, IEntityMusic, IPathObstructionAwareEntity {

	private final BossInfoServer bossInfo = (BossInfoServer)(new BossInfoServer(this.getDisplayName(), BossInfo.Color.RED, BossInfo.Overlay.PROGRESS)).setDarkenSky(false);
	public static final IAttribute SPAWN_LENGTH_ATTRIB = (new RangedAttribute(null, "bl.spawnLength", 180.0D, 0.0D, Integer.MAX_VALUE)).setDescription("Spawning Length");
	public static final IAttribute SPAWN_OFFSET_ATTRIB = (new RangedAttribute(null, "bl.spawnOffset", 3.0D, -Integer.MAX_VALUE, Integer.MAX_VALUE)).setDescription("Spawning Y Offset");

	private static final DataParameter<Integer> SPAWNING_STATE_DW = EntityDataManager.createKey(EntityDreadfulMummy.class, DataSerializers.VARINT);
	private static final DataParameter<Boolean> SPEW = EntityDataManager.createKey(EntityDreadfulMummy.class, DataSerializers.BOOLEAN);
	private static final DataParameter<Integer> PREY = EntityDataManager.createKey(EntityDreadfulMummy.class, DataSerializers.VARINT);
	private static final DataParameter<Float> Y_OFFSET = EntityDataManager.createKey(EntityDreadfulMummy.class, DataSerializers.FLOAT);
	private static final DataParameter<Optional<UUID>> BOSSINFO_ID = EntityDataManager.createKey(EntityDreadfulMummy.class, DataSerializers.OPTIONAL_UNIQUE_ID);

	private int prevSpawningState;

	private int spewTicks = 0;

	private static final int BREAK_COUNT = 20;

	private static final int SPAWN_MUMMY_COOLDOWN = 350;
	private int untilSpawnMummy = 0;
	private static final int SPAWN_SLUDGE_COOLDOWN = 150;
	private int untilSpawnSludge = 0;
	private float prevYOffset;

	private int eatPreyTimer = 60;
	public EntityLivingBase currentEatPrey;

	public int deathTicks = 0;

	private int outOfRangeCounter = 0;
	private int outOfRangeCycleCounter = 0;
	private int obstructedCounter = 0;
	private boolean breakBlocksBelow = false;
	private int blockBreakCounter = 0;

	public EntityDreadfulMummy(World world) {
		super(world);
		this.isImmuneToFire = true;
		setSize(1.1F, 2.0F);

		tasks.addTask(0, new EntityAISwimming(this));
		tasks.addTask(1, new EntityAIAttackMelee(this, 1.0D, false));
		tasks.addTask(2, new EntityAIMoveTowardsRestriction(this, 1.0D));
		tasks.addTask(3, new EntityAIWatchClosest(this, EntityPlayer.class, 16.0F));
		tasks.addTask(4, new EntityAIWander(this, 1.0D));
		tasks.addTask(5, new EntityAILookIdle(this));
		targetTasks.addTask(0, new EntityAIHurtByTarget(this, true));
		targetTasks.addTask(1, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, false));
	}

	@Override
	protected void entityInit() {
		super.entityInit();
		dataManager.register(SPAWNING_STATE_DW, 0);
		dataManager.register(SPEW, false);
		dataManager.register(PREY, -1);
		dataManager.register(Y_OFFSET, 0F);
		this.getDataManager().register(BOSSINFO_ID, Optional.absent());
	}

	@Override
	protected void applyEntityAttributes() {
		super.applyEntityAttributes();
		getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.3D);
		getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(550.0D);
		getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(8);
		getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(40.0D);
		getEntityAttribute(SharedMonsterAttributes.KNOCKBACK_RESISTANCE).setBaseValue(1.0D);

		getAttributeMap().registerAttribute(SPAWN_LENGTH_ATTRIB);
		getAttributeMap().registerAttribute(SPAWN_OFFSET_ATTRIB);
	}

	@Override
	protected PathNavigate createNavigator(World worldIn) {
		ObstructionAwarePathNavigateGround<EntityDreadfulMummy> navigate = new ObstructionAwarePathNavigateGround<>(this, worldIn);
		navigate.setCanSwim(true);
		return navigate;
	}

	@Override
	protected SoundEvent getAmbientSound() {
		return SoundRegistry.DREADFUL_PEAT_MUMMY_LIVING;
	}

	@Override
	protected SoundEvent getHurtSound(DamageSource source) {
		return SoundRegistry.DREADFUL_PEAT_MUMMY_HURT;
	}

	@Override
	protected SoundEvent getDeathSound() {
		return SoundRegistry.DREADFUL_PEAT_MUMMY_DEATH;
	}

	@Override
	public double getYOffset() {
		return dataManager.get(Y_OFFSET);
	}

	public void setYOffset(float yOffset) {
		dataManager.set(Y_OFFSET, yOffset);
	}

	public float getCurrentOffset() {
		return (float) ((-getSpawnOffset() + getSpawningProgress() * getSpawnOffset()));
	}

	public double getSpawnOffset() {
		return getEntityAttribute(SPAWN_OFFSET_ATTRIB).getAttributeValue();
	}

	public int getSpawningState() {
		return dataManager.get(SPAWNING_STATE_DW);
	}

	public int getSpawningLength() {
		return (int) getEntityAttribute(SPAWN_LENGTH_ATTRIB).getAttributeValue();
	}

	public float getSpawningProgress() {
		if(getSpawningLength() == 0) {
			return 1.0F;
		}
		return 1.0F / getSpawningLength() * getSpawningState();
	}

	public float getSpawningProgress(float delta) {
		if(getSpawningLength() == 0) {
			return 1.0F;
		}
		return 1.0F / getSpawningLength() * (this.prevSpawningState + (this.getSpawningState() - this.prevSpawningState) * delta);
	}

	/**
	 * Returns the interpolated relative spawning progress
	 * @param partialTicks
	 * @return
	 */
	public float getInterpolatedYOffsetProgress(float partialTicks) {
		return this.prevYOffset + (((float)this.getYOffset()) - this.prevYOffset) * partialTicks;
	}

	public void updateSpawningState() {
		int spawningState = getSpawningState();

		this.prevSpawningState = spawningState;

		if(this.dataManager.get(SPEW)) {
			int targetState = MathHelper.ceil(this.getSpawningLength() * 0.6f);

			if(spawningState > targetState) {
				dataManager.set(SPAWNING_STATE_DW, spawningState - 1);
			}
		} else if(spawningState < getSpawningLength()) {
			dataManager.set(SPAWNING_STATE_DW, spawningState + 1);
		}
	}

	@Override
	protected boolean canDespawn() {
		return false;
	}

	@Override
	public boolean isPushedByWater() {
		return false;
	}

	@Override
	public void moveRelative(float strafe, float up, float forward, float friction) {
		//Can't use the SWIM_SPEED attribute for this because it also causes the mob to jump
		//way too high out of the water
		final float swimSpeedBoost = 10.0f;

		float f = strafe * strafe + up * up + forward * forward;
		if (f >= 1.0E-4F) {
			f = MathHelper.sqrt(f);
			if (f < 1.0F) f = 1.0F;
			f = friction / f;
			strafe = strafe * f;
			up = up * f;
			forward = forward * f;
			if(this.isInWater() || this.isInLava()) {
				strafe = strafe * (float)this.getEntityAttribute(SWIM_SPEED).getAttributeValue() * swimSpeedBoost;
				up = up * (float)this.getEntityAttribute(SWIM_SPEED).getAttributeValue();
				forward = forward * (float)this.getEntityAttribute(SWIM_SPEED).getAttributeValue() * swimSpeedBoost;
			}
			float f1 = MathHelper.sin(this.rotationYaw * 0.017453292F);
			float f2 = MathHelper.cos(this.rotationYaw * 0.017453292F);
			this.motionX += (double)(strafe * f2 - forward * f1);
			this.motionY += (double)up;
			this.motionZ += (double)(forward * f2 + strafe * f1);
		}
	}

	@Override
	public void onLivingUpdate() {
		super.onLivingUpdate();
		if (!world.isRemote)
			dataManager.set(BOSSINFO_ID, Optional.of(bossInfo.getUniqueId()));
	}

	protected boolean isTargetOutOfAttackRange(EntityLivingBase target) {
		if(target.posY > this.getEntityBoundingBox().maxY - 0.25D) {
			Path path = this.getNavigator().getPath();

			//Check if there is a path to the target
			if(path != null) {
				PathPoint finalPoint = path.getFinalPathPoint();

				if(finalPoint != null) {
					PathNodeType type = this.getNavigator().getNodeProcessor().getPathNodeType(this.world, finalPoint.x, finalPoint.y - 1, finalPoint.z);
					boolean isEndpointInAir = type != PathNodeType.BLOCKED;
					return isEndpointInAir || finalPoint.y + this.height - 0.25D < target.posY;
				}
			}

			return true;
		}

		return this.getDistance(target) > 24;
	}

	protected boolean isTargetInCloseRange(EntityLivingBase target) {
		return !this.isTargetOutOfAttackRange(target) && target.getDistance(this) < 4;
	}

	protected boolean isTargetObstructed(EntityLivingBase target) {
		Path path = this.getNavigator().getPath();

		//Check if there is a path to the target
		if(path != null && path.getFinalPathPoint() != null) {
			PathPoint finalPoint = path.getFinalPathPoint();

			if(finalPoint != null && target.getDistance(finalPoint.x, finalPoint.y, finalPoint.z) < 0.9f) {
				PathNodeType type = this.getNavigator().getNodeProcessor().getPathNodeType(this.world, finalPoint.x, finalPoint.y, finalPoint.z);

				if(type == PathNodeType.OPEN || type == PathNodeType.WALKABLE) {
					return false;
				}
			}
		}

		MutableBlockPos checkPos = new MutableBlockPos();

		BlockPos center = new BlockPos(target);

		for(int yo = MathHelper.floor(target.height); yo < 3; yo++) {
			for(int xo = -1; xo <= 1; xo++) {
				for(int zo = -1; zo <= 1; zo++) {
					checkPos.setPos(center.getX() + xo, center.getY() + yo, center.getZ() + zo);

					if(!this.world.getCollisionBoxes(null, new AxisAlignedBB(checkPos)).isEmpty()) {
						return true;
					}
				}
			}
		}

		return false;
	}

	protected boolean isBridgableSpot(BlockPos pos) {
		if(this.world.isAirBlock(pos)) {
			return true;
		}
		PathNodeType nodeType = this.getNavigator().getNodeProcessor().getPathNodeType(this.world, pos.getX(), pos.getY(), pos.getZ());
		return nodeType != PathNodeType.BLOCKED && nodeType != PathNodeType.WATER;
	}

	protected void placeBridge() {
		Path path = this.getNavigator().getPath();

		if(path != null && path.getCurrentPathIndex() < path.getCurrentPathLength()) {
			PathPoint nextPoint = path.getPathPointFromIndex(path.getCurrentPathIndex());

			if(nextPoint.y == MathHelper.floor(this.posY + 0.5f)) {
				BlockPos pos = new BlockPos(this);
				pos = pos.add(0, nextPoint.y - pos.getY() - 1, 0);

				if(this.getDistanceSq(pos) <= 2 && this.isBridgableSpot(pos)) {
					if(ForgeEventFactory.getMobGriefingEvent(this.world, this)) {

						int nx = -2;
						int nz = -2;

						if(path.getCurrentPathIndex() < path.getCurrentPathLength() - 1) {
							PathPoint secondNextPoint = path.getPathPointFromIndex(path.getCurrentPathIndex() + 1);
							nx = MathHelper.clamp(secondNextPoint.x - nextPoint.x, -1, 1);
							nz = MathHelper.clamp(secondNextPoint.z - nextPoint.z, -1, 1);
							if(nx == 0) nx = -2;
							if(nz == 0) nz = -2;
						}

						for(int xo = -1; xo <= 1; xo++) {
							for(int zo = -1; zo <= 1; zo++) {
								if(xo != nx && zo != nz) {
									BlockPos offsetPos = pos.add(xo, 0, zo);

									if(this.world.getEntitiesWithinAABB(EntityLivingBase.class, new AxisAlignedBB(offsetPos), e -> e != this).isEmpty() && this.isBridgableSpot(offsetPos)) {

										boolean canReplace = false;

										if(this.world.getBlockState(offsetPos).getBlock().isReplaceable(this.world, offsetPos)) {
											canReplace = true;
										} else {
											IBlockState hitState = this.world.getBlockState(offsetPos);
											float hardness = hitState.getBlockHardness(this.world, offsetPos);

											if(hardness >= 0
													&& hitState.getBlock().canEntityDestroy(hitState, this.world, offsetPos, this)
													&& ForgeEventFactory.onEntityDestroyBlock(this, offsetPos, hitState)) {

												canReplace = true;
												if(!hitState.getMaterial().isLiquid() && hitState.getCollisionBoundingBox(this.world, pos) != null) {
													this.world.playEvent(2001, offsetPos, Block.getStateId(hitState));
												}
												this.world.setBlockToAir(offsetPos);
											}
										}

										if(canReplace) {
											this.world.setBlockState(offsetPos, BlockRegistry.PEAT.getDefaultState());
										}

									}
								}
							}
						}

					}
				}
			}
		}
	}

	@Override
	public float getBridgePathingMalus(EntityLiving entity, BlockPos pos, PathPoint fallPathPoint) {
		return fallPathPoint == null || fallPathPoint.y < pos.getY() ? 2.0f : -1.0f;
	}

	@Override
	public float getPathingMalus(EntityLiving entity, PathNodeType nodeType, BlockPos pos) {
		if(nodeType == PathNodeType.BLOCKED) {
			return 10.0f;
		}
		return super.getPathPriority(nodeType);
	}

	@Override
	public void onPathingObstructed(EnumFacing facing) {
		if(this.getAttackTarget() != null) {
			this.breakBlocksBelow = facing == EnumFacing.DOWN;
			this.blockBreakCounter = 40;
		}
	}

	@Override
	public int getMaxFallHeight() {
		//Doesn't take fall damage
		return 128;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();
		this.prevYOffset = (float) getYOffset();

		if((getPrey() != null && this.dataManager.get(SPEW)) || !this.isEntityAlive()) {
			setPrey(null);
		}

		Entity prey = getPrey();
		if(prey instanceof EntityLivingBase) {
			currentEatPrey = (EntityLivingBase)prey;
			if(currentEatPrey != null) {
				updateEatPrey();
			}
		} else {
			currentEatPrey = null;
		}

		updateSpawningState();

		if(getEntityWorld().isRemote) {
			if(getSpawningProgress() < 1.0F) {
				setYOffset(getCurrentOffset());
				motionX = 0;
				motionY = 0;
				motionZ = 0;
				if(getSpawningState() == getSpawningLength() - 1) {
					setPosition(posX, posY, posZ);
				}
				int breakPoint = getSpawningLength() / BREAK_COUNT;
				if ((getSpawningState() - breakPoint / 2 - 1) % breakPoint == 0) {
					BlockPos pos = new BlockPos(this.posX, this.posY - 1, this.posZ);
					IBlockState state = getEntityWorld().getBlockState(pos);
					double px = posX + rand.nextDouble() - 0.5F;
					double py = posY + rand.nextDouble() * 0.2 + 0.075;
					double pz = posZ + rand.nextDouble() - 0.5F;
					for (int i = 0, amount = rand.nextInt(20) + 15; i < amount; i++) {
						double ox = rand.nextDouble() * 0.1F - 0.05F;
						double oz = rand.nextDouble() * 0.1F - 0.05F;
						double motionX = rand.nextDouble() * 0.2 - 0.1;
						double motionY = rand.nextDouble() * 0.25 + 0.1;
						double motionZ = rand.nextDouble() * 0.2 - 0.1;
						getEntityWorld().spawnParticle(EnumParticleTypes.BLOCK_DUST, px + ox, py, pz + oz, motionX, motionY, motionZ, Block.getStateId(state));
					}
				}
			} else if(this.deathTicks == 0) {
				setYOffset(0F);
			} else if(this.deathTicks > 60) {
				motionX = 0;
				motionY = 0;
				motionZ = 0;
				if (this.deathTicks % 5 == 0) {
					BlockPos pos = new BlockPos(this.posX, this.posY - 1, this.posZ);
					IBlockState state = getEntityWorld().getBlockState(pos);
					double px = posX + rand.nextDouble() - 0.5F;
					double py = posY + rand.nextDouble() * 0.2 + 0.075;
					double pz = posZ + rand.nextDouble() - 0.5F;
					for (int i = 0, amount = rand.nextInt(20) + 15; i < amount; i++) {
						double ox = rand.nextDouble() * 0.1F - 0.05F;
						double oz = rand.nextDouble() * 0.1F - 0.05F;
						double motionX = rand.nextDouble() * 0.2 - 0.1;
						double motionY = rand.nextDouble() * 0.25 + 0.1;
						double motionZ = rand.nextDouble() * 0.2 - 0.1;
						getEntityWorld().spawnParticle(EnumParticleTypes.BLOCK_DUST, px + ox, py, pz + oz, motionX, motionY, motionZ, Block.getStateId(state));
					}
				}
			}
		} else {
			EntityLivingBase target = this.getAttackTarget();

			if(target != null && (this.blockBreakCounter == 0 || !this.breakBlocksBelow) && !this.isJumping) {
				this.placeBridge();
			}

			float targetMotionX = 0.0f;
			float targetMotionY = 0.0f;
			float targetMotionZ = 0.0f;

			if(target instanceof EntityPlayer) {
				NBTTagCompound nbt = target.getEntityData();

				targetMotionX = (float)target.posX - nbt.getFloat("thebetweenlands.dpm.lastX");
				targetMotionY = (float)target.posY - nbt.getFloat("thebetweenlands.dpm.lastY");
				targetMotionZ = (float)target.posZ - nbt.getFloat("thebetweenlands.dpm.lastZ");

				nbt.setFloat("thebetweenlands.dpm.lastX", (float)target.posX);
				nbt.setFloat("thebetweenlands.dpm.lastY", (float)target.posY);
				nbt.setFloat("thebetweenlands.dpm.lastZ", (float)target.posZ);
			} else if(target != null) {
				targetMotionX = (float)target.motionX;
				targetMotionY = (float)target.motionY;
				targetMotionZ = (float)target.motionZ;
			}

			if(target != null && this.isTargetOutOfAttackRange(target)) {
				if(this.obstructedCounter > 0 && this.dataManager.get(SPEW)) {
					this.outOfRangeCycleCounter++;

					//Make DPM cycle between ranged and melee if both obstructed and out of range
					if(this.outOfRangeCycleCounter > 200) {
						this.outOfRangeCycleCounter = 0;
						this.outOfRangeCounter = 0;
						this.dataManager.set(SPEW, false);
					}
				} else {
					this.outOfRangeCounter += 2;
				}
			} else {
				if(target != null && this.isTargetInCloseRange(target)) {
					this.outOfRangeCounter = Math.max(0, this.outOfRangeCounter - 2);
				} else {
					this.outOfRangeCounter = Math.max(0, this.outOfRangeCounter - 1);
				}
			}

			if(this.ticksExisted % 10 == 0) {
				if(target != null && this.isTargetObstructed(target)) {
					this.obstructedCounter += 2;
				} else {
					this.obstructedCounter = Math.max(0, this.obstructedCounter - 1);
				}
			}

			if(this.obstructedCounter <= 0) {
				this.outOfRangeCycleCounter = Math.max(0, this.outOfRangeCycleCounter - 1);
			}

			if(this.obstructedCounter > 10) {
				this.obstructedCounter = 0;

				if(target != null) {
					BlockPos pos = new BlockPos(target);

					if(!this.world.isAirBlock(pos.down()) && this.world.getEntitiesWithinAABB(EntityMummyArm.class, new AxisAlignedBB(pos)).isEmpty()) {
						EntityMummyArm arm = new EntityMummyArm(this.world);
						arm.setLocationAndAngles(pos.getX() + 0.5f, pos.getY(), pos.getZ() + 0.5f, 0, 0);

						this.world.spawnEntity(arm);
					}

					this.breakBlocksBelow = false;
					this.blockBreakCounter = 40;
				}
			}

			if(this.blockBreakCounter > 0) {
				this.blockBreakCounter--;

				if(ForgeEventFactory.getMobGriefingEvent(this.world, this)) {
					boolean broken = false;

					for(int xo = -2; xo <= 2; xo++) {
						for(int yo = (this.breakBlocksBelow ? -2 : 0); yo <= 2; yo++) {
							for(int zo = -2; zo <= 2; zo++) {
								if(this.world.rand.nextInt(6) == 0) {
									Vec3d center = new Vec3d(this.posX + xo, this.posY + yo, this.posZ + zo);

									if(center.subtract(new Vec3d(this.posX, this.posY, this.posZ)).dotProduct(this.breakBlocksBelow ? new Vec3d(0, -1, 0) : this.getLookVec()) > 0.3) {
										BlockPos pos = new BlockPos(center);

										IBlockState hitState = this.world.getBlockState(pos);
										float hardness = hitState.getBlockHardness(this.world, pos);

										if(!hitState.getBlock().isAir(hitState, this.world, pos) && hardness >= 0
												&& hitState.getBlock().canEntityDestroy(hitState, this.world, pos, this)
												&& ForgeEventFactory.onEntityDestroyBlock(this, pos, hitState)) {
											if(!hitState.getMaterial().isLiquid() && hitState.getCollisionBoundingBox(this.world, pos) != null) {
												this.world.playEvent(2001, pos, Block.getStateId(hitState));
											}
											this.world.setBlockToAir(pos);
											broken = true;
										}
									}
								}
							}
						}
					}

					if(broken) {
						this.playSound(SoundRegistry.DREADFUL_PEAT_MUMMY_SWIPE, 1, 1);
						this.playSound(SoundRegistry.DREADFUL_PEAT_MUMMY_BITE, 1, 1);
					}
				}
			}

			if(this.outOfRangeCounter > 300) {
				this.outOfRangeCounter = 300;
				this.dataManager.set(SPEW, true);
			} else if(this.outOfRangeCounter <= 0) {
				this.dataManager.set(SPEW, false);
			}

			if(this.dataManager.get(SPEW)) {
				this.spewTicks++;

				if(this.spewTicks > 60 && (this.spewTicks / 5) % 5 == 0) {
					if(target != null) {
						int numBalls = 1 + this.rand.nextInt(3);
						for(int i = 1; i < numBalls; i++) {
							float g = -0.03f;

							float vy0 = 0.5f + this.rand.nextFloat() * 1.3f;

							float tmax = -vy0 / g;

							float s = vy0 * tmax + 0.5f * g * tmax * tmax;

							float h = (float)(target.posY - this.posY);

							float fall = h - s;

							if(fall < 0) {
								float tmin = MathHelper.sqrt(fall * 2 / g);

								float t = tmax + tmin;

								float dx = (float)(target.posX - this.posX) + targetMotionX * t / 3 + (this.rand.nextFloat() - 0.5f) * 2;
								float dz = (float)(target.posZ - this.posZ) + targetMotionZ * t / 3 + (this.rand.nextFloat() - 0.5f) * 2;

								float len = MathHelper.sqrt(dx*dx + dz*dz);

								dx /= len;
								dz /= len;

								float speed = Math.min(len / t * 3.0f /*constant adjustment for MC physics*/, 1.5f);

								this.spawnRangedSludge(dx * speed, vy0, dz * speed);
							}
						}

						if(this.spewTicks % 5 == 0) {
							this.world.playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_RETCH, SoundCategory.HOSTILE, 1F, 0.7F + rand.nextFloat() * 0.6F);
							this.world.playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_LICK, SoundCategory.HOSTILE, 1F, 1F);
						}
					}
				}
			} else {
				this.spewTicks = 0;
			}

			if(deathTicks > 60) {
				setYOffset(-(deathTicks - 60) * 0.05F);
			}

			if(getSpawningProgress() < 1.0F) {
				if(getSpawningState() == 0) {
					getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_EMERGE, SoundCategory.HOSTILE, 1.2F, 1.0F);
				}

				setYOffset(getCurrentOffset());
				motionY = 0;
				motionX = 0;
				motionZ = 0;
				velocityChanged = true;

				int breakPoint = getSpawningLength() / BREAK_COUNT;
				if ((getSpawningState() - breakPoint / 2 - 1) % breakPoint == 0) {
					BlockPos pos = new BlockPos(this.posX, this.posY - 1, this.posZ);
					IBlockState blockState = this.world.getBlockState(pos);
					playSound(blockState.getBlock().getSoundType(blockState, this.world, pos, this).getBreakSound(), this.rand.nextFloat() * 0.3F + 0.3F, this.rand.nextFloat() * 0.15F + 0.7F);
				}

				if(getAttackTarget() != null) faceEntity(getAttackTarget(), 360, 360);
				if(getSpawningState() == getSpawningLength() - 1) {
					setPosition(posX, posY, posZ);
				}
			} else if(deathTicks < 60) {
				setYOffset(0);
				prevYOffset = 0;
			}
		}

		if(!getEntityWorld().isRemote && isEntityAlive() && this.getSpawningProgress() >= 1) {
			//Heal when no player is nearby
			if(this.ticksExisted % 12 == 0 && this.getHealth() < this.getMaxHealth() && this.world.getClosestPlayerToEntity(this, 32) == null) {
				this.heal(1);
			}

			if (getAttackTarget() != null) {
				AxisAlignedBB checkAABB = getEntityBoundingBox().expand(64, 64, 64);
				List<EntityPeatMummy> peatMummies = getEntityWorld().getEntitiesWithinAABB(EntityPeatMummy.class, checkAABB);
				int mummies = 0;
				for(EntityPeatMummy mummy : peatMummies) {
					if(mummy.getDistance(this) <= 64.0D && mummy.isSpawningFinished())
						mummies++;
				}
				//Max. 4 peat mummies
				if(mummies < 4 && untilSpawnMummy <= 0)
					spawnMummy();

				if(untilSpawnSludge <= 0)
					spawnSludge();
			}

			if(untilSpawnMummy > 0)
				untilSpawnMummy--;

			if(untilSpawnSludge > 0)
				untilSpawnSludge--;

			if(eatPreyTimer > 0 && currentEatPrey != null)
				eatPreyTimer--;

			if(eatPreyTimer <= 0) {
				setPrey(null);
				eatPreyTimer = 60;
			}
		}
	}

	@Override
	protected void updateAITasks() {
		super.updateAITasks();
		bossInfo.setPercent(this.getHealth() / this.getMaxHealth());
	}

	private void spawnMummy() {
		EntityPeatMummy mummy = new EntityPeatMummy(getEntityWorld());
		mummy.setPosition(posX + (rand.nextInt(6) - 3), posY, posZ + (rand.nextInt(6) - 3));
		if(mummy.getEntityWorld().checkNoEntityCollision(mummy.getEntityBoundingBox()) && mummy.getEntityWorld().getCollisionBoxes(mummy, mummy.getEntityBoundingBox()).isEmpty()) {
			untilSpawnMummy = SPAWN_MUMMY_COOLDOWN;
			mummy.setAttackTarget((EntityLivingBase) getAttackTarget());
			mummy.setHealth(30);
			getEntityWorld().spawnEntity(mummy);
			mummy.setCarryShimmerstone(false);
			mummy.setBossMummy(true);
			getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_SCREAM, SoundCategory.HOSTILE, 1F, 1.0F);
			TheBetweenlands.networkWrapper.sendToAllAround(new MessageSummonPeatMummyParticles(mummy), new TargetPoint(this.dimension, mummy.posX, mummy.posY, mummy.posZ, 64));
		} else {
			//Try again the next tick
			untilSpawnMummy = 1;
		}
	}

	private void spawnSludge() {
		untilSpawnSludge = SPAWN_SLUDGE_COOLDOWN;
		if(getAttackTarget() != null)
			faceEntity(getAttackTarget(), 360.0F, 360.0F);
		Vec3d look = getLookVec();
		double direction = Math.toRadians(renderYawOffset);
		EntitySludgeBall sludge = new EntitySludgeBall(getEntityWorld(), this, false);
		sludge.setPosition(posX - Math.sin(direction) * 3.5, posY + height, posZ + Math.cos(direction) * 3.5);
		sludge.motionX = look.x * 0.5D;
		sludge.motionY = look.y;
		sludge.motionZ = look.z * 0.5D;
		getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_RETCH, SoundCategory.HOSTILE, 1F, 0.7F + rand.nextFloat() * 0.6F);
		getEntityWorld().spawnEntity(sludge);
	}

	private void spawnRangedSludge(float dx, float dy, float dz) {
		double direction = Math.toRadians(renderYawOffset);
		EntitySludgeBall sludge = new EntitySludgeBall(getEntityWorld(), this, true);
		sludge.setPosition(posX - Math.sin(direction) * 0.75, posY + 0.3f, posZ + Math.cos(direction) * 0.75);
		sludge.motionX = dx;
		sludge.motionY = dy;
		sludge.motionZ = dz;
		getEntityWorld().spawnEntity(sludge);
	}

	@Override
	public boolean attackEntityAsMob(Entity target) {
		if((getSpawningProgress() < 0.95F && !this.dataManager.get(SPEW)) || this.getHealth() <= 0.0F) {
			return false;
		}

		boolean attacked = super.attackEntityAsMob(target);
		if (attacked && this.isEntityAlive() && rand.nextInt(6) == 0 && target != currentEatPrey && target instanceof EntityLivingBase && !(target instanceof EntityPlayer && ((EntityPlayer)target).capabilities.isCreativeMode) && !getEntityWorld().isRemote && !this.dataManager.get(SPEW)) {
			setPrey((EntityLivingBase)target);
		}

		if(attacked) {
			getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_SWIPE, SoundCategory.HOSTILE, 1F, 0.7F + rand.nextFloat() * 0.6F);
		}

		return attacked;
	}

	private void updateEatPrey() {
		double direction = Math.toRadians(renderYawOffset);
		currentEatPrey.setPositionAndRotation(posX - Math.sin(direction) * 1.7, posY + 0.25, posZ + Math.cos(direction) * 1.7, (float) (Math.toDegrees(direction) + 180), 0);
		currentEatPrey.rotationYawHead = currentEatPrey.prevRotationYawHead = currentEatPrey.rotationYaw = currentEatPrey.prevRotationYaw = ((float) (Math.toDegrees(direction) + 180));
		currentEatPrey.fallDistance = 0;
		if (ticksExisted % 10 == 0) {
			if(!getEntityWorld().isRemote) {
				currentEatPrey.attackEntityFrom(DamageSource.causeMobDamage(this), 3);
				getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_BITE, SoundCategory.HOSTILE, 1F, 0.7F + rand.nextFloat() * 0.6F);
				getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_LICK, SoundCategory.HOSTILE, 1F, 1F);
			}
		}
		if (!currentEatPrey.isEntityAlive() && !getEntityWorld().isRemote) 
			setPrey(null);
	}

	private void setPrey(EntityLivingBase prey) {
		if (prey == null) {
			dataManager.set(PREY, -1);
		} else {
			dataManager.set(PREY, prey.getEntityId());
		}
	}

	private EntityLivingBase getPrey() {
		int id = dataManager.get(PREY);
		Entity prey = id >= 0 ? getEntityWorld().getEntityByID(id) : null;
		if(prey instanceof EntityLivingBase)
			return (EntityLivingBase) prey;
		return null;
	}

	@Override
	public boolean attackEntityFrom(DamageSource source, float damage) {
		if(source == DamageSource.OUT_OF_WORLD) {
			return super.attackEntityFrom(source, damage);
		}

		if(source == DamageSource.FALL || source == DamageSource.FALLING_BLOCK || source == DamageSource.IN_WALL || source == DamageSource.CRAMMING
				|| source == DamageSource.DROWN || source == DamageSource.HOT_FLOOR || source == DamageSource.IN_FIRE || source == DamageSource.ON_FIRE) {
			return false;
		}

		if (currentEatPrey != null && source.getTrueSource() == currentEatPrey)  {
			return false;
		}

		if(getSpawningProgress() < 0.95F && !this.dataManager.get(SPEW)) {
			return false;
		}

		if(super.attackEntityFrom(source, this.dataManager.get(SPEW) ? damage * 0.25f : damage)) {
			if(!this.world.isRemote && source instanceof EntityDamageSource) {
				Entity sourceEntity = ((EntityDamageSource) source).getTrueSource();

				if(sourceEntity instanceof EntityLivingBase) {
					if(this.isTargetOutOfAttackRange((EntityLivingBase) sourceEntity)) {
						this.outOfRangeCounter += 60;
					} else if(this.isTargetInCloseRange((EntityLivingBase) sourceEntity)) {
						this.outOfRangeCounter = Math.max(0, this.outOfRangeCounter - 40);
					}
				}
			}

			return true;
		}

		return false;
	}

	@Override
	public boolean isNonBoss() {
		return false;
	}

	@Override
	protected void onDeathUpdate() {
		bossInfo.setPercent(0);
		if(deathTicks == 0) {
			if(!getEntityWorld().isRemote) {
				getEntityWorld().playSound(null, getPosition(), SoundRegistry.DREADFUL_PEAT_MUMMY_DEATH, SoundCategory.HOSTILE, 1F, 1F);
			}
		}

		++deathTicks;

		if(!getEntityWorld().isRemote) {
			posX = lastTickPosX;
			posY = lastTickPosY;
			posZ = lastTickPosZ;
			motionX = 0;
			motionY = 0;
			motionZ = 0;

			if (deathTicks > 40 && deathTicks % 5 == 0) {
				int xp = 100;
				while (xp > 0) {
					int dropXP = EntityXPOrb.getXPSplit(xp);
					xp -= dropXP;
					getEntityWorld().spawnEntity(new EntityXPOrb(getEntityWorld(), posX, posY + height / 2.0D, posZ, dropXP));
				}
			}

			if(deathTicks == 80) {
				int xp = 1200;
				while (xp > 0) {
					int dropXP = EntityXPOrb.getXPSplit(xp);
					xp -= dropXP;
					getEntityWorld().spawnEntity(new EntityXPOrb(getEntityWorld(), posX, posY + height / 2.0D, posZ, dropXP));
				}
			}

			if(deathTicks > 120) {
				setDead();
			}
		}

		if(deathTicks > 80) {
			if(getEntityWorld().isRemote && deathTicks % 5 == 0) {
				for(int xo = -1; xo <= 1; xo++) {
					for(int zo = -1; zo <= 1; zo++) {
						int x = MathHelper.floor(posX) + xo, y = MathHelper.floor(posY - getYOffset() - 0.1D), z = MathHelper.floor(posZ) + zo;
						IBlockState state = getEntityWorld().getBlockState(new BlockPos(x, y, z));
						Block block = state.getBlock();
						if(block != Blocks.AIR) {
							double px = posX + rand.nextDouble() - 0.5F;
							double py = posY - getYOffset() + rand.nextDouble() * 0.2 + 0.075;
							double pz = posZ + rand.nextDouble() - 0.5F;
							getEntityWorld().playSound(null, getPosition(), block.getSoundType(state, getEntityWorld(), getPosition().down(), null).getBreakSound(), SoundCategory.BLOCKS, rand.nextFloat() * 0.3F + 0.3F, rand.nextFloat() * 0.15F + 0.7F);
							for (int i = 0, amount = rand.nextInt(20) + 10; i < amount; i++) {
								double ox = rand.nextDouble() * 0.1F - 0.05F;
								double oz = rand.nextDouble() * 0.1F - 0.05F;
								double motionX = rand.nextDouble() * 0.2 - 0.1;
								double motionY = rand.nextDouble() * 0.25 + 0.1;
								double motionZ = rand.nextDouble() * 0.2 - 0.1;
								getEntityWorld().spawnParticle(EnumParticleTypes.BLOCK_DUST, px + ox + xo, py, pz + oz + zo, motionX, motionY, motionZ, Block.getStateId(state));
							}
						}
					}
				}
			}
		}
	}

	@Override
	public void onCollideWithPlayer(EntityPlayer player) {
		if(isEntityAlive()) {
			super.onCollideWithPlayer(player);
		}
	}

	@Override
	public boolean canBePushed() {
		return isEntityAlive() && this.getSpawningProgress() >= 1 && super.canBePushed();
	}

	@Override
	public boolean canBeCollidedWith() {
		return isEntityAlive();
	}

	@Override
	protected boolean isMovementBlocked() {
		return isEntityAlive() && super.isMovementBlocked();
	}

	@Override
	public float getShakeIntensity(Entity viewer, float partialTicks) {
		if(deathTicks > 0) {
			double dist = getDistance(viewer);
			float screamMult = (float) (1.0F - dist / 30.0F);
			if(dist >= 30.0F) {
				return 0.0F;
			}
			return (float) ((Math.sin(deathTicks / 120.0D * Math.PI) + 0.1F) * 0.15F * screamMult);
		} else {
			return 0.0F;
		}
	}

	@Override
	public boolean applyOffset(Entity view, float partialTicks) {
		if(currentEatPrey == view) {
			double direction = Math.toRadians(prevRenderYawOffset + (renderYawOffset - prevRenderYawOffset) * partialTicks);
			view.prevRotationYaw = view.rotationYaw = (float) (Math.toDegrees(direction) + 180);
			view.prevRotationPitch = view.rotationPitch = 0;
			view.setRotationYawHead((float) (Math.toDegrees(direction) + 180));
			return true;
		}
		return false;
	}

	@Override
	public void writeEntityToNBT(NBTTagCompound nbt) {
		super.writeEntityToNBT(nbt);
		nbt.setInteger("spawningState", getSpawningState());
		nbt.setInteger("deathTicks", deathTicks);
		nbt.setDouble("initialPosY", posY);
		nbt.setFloat("previousYOffset", prevYOffset);
	}

	@Override
	public void readEntityFromNBT(NBTTagCompound nbt) {
		super.readEntityFromNBT(nbt);
		deathTicks = nbt.getInteger("deathTicks");
		posY = nbt.getDouble("initialPosY");
		setYOffset(nbt.getFloat("previousYOffset"));
		dataManager.set(SPAWNING_STATE_DW, nbt.getInteger("spawningState"));
		if(hasCustomName())
			bossInfo.setName(this.getDisplayName());
	}

	@Override
	public void setCustomNameTag(String name) {
		super.setCustomNameTag(name);
		bossInfo.setName(this.getDisplayName());
	}

	@Override
	public void addTrackingPlayer(EntityPlayerMP player) {
		super.addTrackingPlayer(player);
		bossInfo.addPlayer(player);
	}

	@Override
	public void removeTrackingPlayer(EntityPlayerMP player) {
		super.removeTrackingPlayer(player);
		bossInfo.removePlayer(player);
	}

	@Nullable
	@Override
	protected ResourceLocation getLootTable() {
		return LootTableRegistry.DREADFUL_PEAT_MUMMY;
	}

	@Override
	public float getBlockPathWeight(BlockPos pos) {
		return 0.5F;
	}

	@Override
	protected boolean isValidLightLevel() {
		return true;
	}

	@Override
	public BLSoundEvent getMusicFile(EntityPlayer listener) {
		return SoundRegistry.DREADFUL_PEAT_MUMMY_LOOP;
	}

	@Override
	public double getMusicRange(EntityPlayer listener) {
		return 32.0D;
	}

	@Override
	public boolean isMusicActive(EntityPlayer listener) {
		return isEntityAlive();
	}

	@Override
	public int getMusicLayer(EntityPlayer listener) {
		return EntityMusicLayers.BOSS;
	}

	@Override
	public UUID getBossInfoUuid() {
		return dataManager.get(BOSSINFO_ID).or(new UUID(0, 0));
	}
}