content: package thebetweenlands.common.entity.mobs;
  
  import net.minecraft.entity.EntityLivingBase;
  import net.minecraft.entity.SharedMonsterAttributes;
  import net.minecraft.entity.ai.EntityAIAttackMelee;
  import net.minecraft.entity.ai.EntityAIBase;
  import net.minecraft.entity.ai.EntityAIBreakDoor;
  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.monster.EntityMob;
  import net.minecraft.entity.passive.EntityVillager;
  import net.minecraft.entity.player.EntityPlayer;
  import net.minecraft.network.datasync.DataParameter;
  import net.minecraft.network.datasync.DataSerializers;
  import net.minecraft.network.datasync.EntityDataManager;
  import net.minecraft.pathfinding.PathNavigateGround;
  import net.minecraft.util.DamageSource;
  import net.minecraft.util.ResourceLocation;
  import net.minecraft.util.SoundCategory;
  import net.minecraft.util.SoundEvent;
  import net.minecraft.util.math.BlockPos;
  import net.minecraft.util.math.MathHelper;
  import net.minecraft.world.World;
  import thebetweenlands.api.entity.IEntityBL;
  import thebetweenlands.common.entity.ai.EntityAIHurtByTargetImproved;
  import thebetweenlands.common.registries.LootTableRegistry;
  import thebetweenlands.common.registries.SoundRegistry;
  import thebetweenlands.util.AnimationMathHelper;
  
  public class EntitySwampHag extends EntityMob implements IEntityBL {
  	private static final DataParameter<Byte> TALK_SOUND = EntityDataManager.createKey(EntitySwampHag.class, DataSerializers.BYTE);
  	private static final DataParameter<Boolean> SHOULD_JAW_MOVE = EntityDataManager.createKey(EntitySwampHag.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Integer> LIVING_SOUND_TIMER = EntityDataManager.createKey(EntitySwampHag.class, DataSerializers.VARINT);
  	private static final DataParameter<Boolean> IS_THROWING = EntityDataManager.createKey(EntitySwampHag.class, DataSerializers.BOOLEAN);
  	private static final DataParameter<Integer> THROW_TIMER = EntityDataManager.createKey(EntitySwampHag.class, DataSerializers.VARINT);
  	
  	public float jawFloat;
  	public float breatheFloat;
  	AnimationMathHelper animationTalk = new AnimationMathHelper();
  	AnimationMathHelper animationBreathe = new AnimationMathHelper();
  	private int animationTick;
  	public boolean playPullSound;
  	private int pathingCooldown = 0;
  	
  	public EntitySwampHag(World world) {
  		super(world);
  		this.setSize(0.6F, 1.8F);
  	}
  
  	@Override
  	protected void initEntityAI() {
  		((PathNavigateGround) this.getNavigator()).setBreakDoors(true);
  		this.tasks.addTask(0, new EntityAISwimming(this));
  		//Works as long as the material is wood
  		this.tasks.addTask(1, new EntityAIBreakDoor(this));
  		tasks.addTask(2, new EntitySwampHag.EntityAIThrowWorm(this));
  		// this.tasks.addTask(2, new EntityAIBLBreakDoor(this, Blocks.iron_door, 20));
  		this.tasks.addTask(3, new EntityAIAttackMelee(this, 1D, true));
  		this.tasks.addTask(4, new EntityAIMoveTowardsRestriction(this, 1.0D));
  		this.tasks.addTask(5, new EntityAIWander(this, 0.75D));
  		this.tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
  		this.tasks.addTask(7, new EntityAILookIdle(this));
  
  		this.targetTasks.addTask(0, new EntityAIHurtByTargetImproved(this, true));
  		this.targetTasks.addTask(1, new EntityAINearestAttackableTarget<EntityPlayer>(this, EntityPlayer.class, true));
  		this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<EntityVillager>(this, EntityVillager.class, false));
  	}
  
  	@Override
  	protected void entityInit() {
  		super.entityInit();
  		dataManager.register(TALK_SOUND, (byte) 0);
  		dataManager.register(SHOULD_JAW_MOVE, false);
  		dataManager.register(LIVING_SOUND_TIMER, 0);
  		dataManager.register(IS_THROWING, false);
  		dataManager.register(THROW_TIMER, 0);
  	}
  
  	@Override
  	protected void applyEntityAttributes() {
  		super.applyEntityAttributes();
  		getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.2D);
  		getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(40.0D);
  		getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(4.0D);
  		getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(35.0D);
  	}
  
  	@Override
  	public boolean getCanSpawnHere() {
  		return super.getCanSpawnHere();
  	}
  
  	@Override
  	public int getMaxSpawnedInChunk() {
  		return 3;
  	}
  
  	@Override
  	protected SoundEvent getAmbientSound() {
  		int randomSound = rand.nextInt(4) + 1;
  		setTalkSound((byte) randomSound);
  		switch (getTalkSound()) {
  		case 1:
  			return SoundRegistry.SWAMP_HAG_LIVING_1;
  		case 2:
  			return SoundRegistry.SWAMP_HAG_LIVING_2;
  		case 3:
  			return SoundRegistry.SWAMP_HAG_LIVING_3;
  		default:
  			return SoundRegistry.SWAMP_HAG_LIVING_4;
  		}
  	}
  
  	@Override
  	protected SoundEvent getHurtSound(DamageSource damageSource) {
  		setTalkSound(4);
  		setShouldJawMove(true);
  		return SoundRegistry.SWAMP_HAG_HURT;
  	}
  
  	@Override
  	protected SoundEvent getDeathSound() {
  		setTalkSound(4);
  		setShouldJawMove(true);
  		return SoundRegistry.SWAMP_HAG_DEATH;
  	}
  	
  	@Override
  	public void onUpdate() {
  		super.onUpdate();
  		// WIP Temp
  		if (!getEntityWorld().isRemote && isRidingMummy()) {
  			if (getAttackTarget() != null) {
  				if (getThrowTimer() < 90 && !getIsThrowing()) {
  					setThrowTimer(Math.min(90, getThrowTimer() + 1));
  				}
  				if (getThrowTimer() >= 90 && getIsThrowing() && getThrowTimer() < 101) {
  					setThrowTimer(Math.min(102, getThrowTimer() + 2));
  				}
  			} else {
  				if (getThrowTimer() > 0 && !getIsThrowing()) {
  					setThrowTimer(Math.max(0, getThrowTimer() - 1));
  				}
  			}
  
  			if (getThrowTimer() <= 0)
  				playPullSound = true;
  
  			if (isRaisingArm())
  				if (playPullSound) {
  					getEntityWorld().playSound(null, getPosition(), SoundRegistry.SLINGSHOT_CHARGE, SoundCategory.HOSTILE, 1F, 1F);
  					playPullSound = false;
  				}
  		}
  	}
  
  	public boolean isRidingMummy() {
  		return isRiding() && getRidingEntity() instanceof EntityPeatMummy;
  	}
  
  	public EntityPeatMummy getMummyMount() {
  		return isRidingMummy() ? (EntityPeatMummy)getRidingEntity() : null;
  	}
  
  	@Override
      public double getYOffset() {
      	if(isRidingMummy() && !getMummyMount().isSpawningFinished())
      		return -0.4D;
          return 0.0D;
      }
  
  	@Override
  	public boolean attackEntityFrom(DamageSource source, float damage) {
  		if (source.equals(DamageSource.IN_WALL) && isRidingMummy())
  			return false;
  		return super.attackEntityFrom(source, damage);
  	}
  
  	@Override
  	public void onLivingUpdate() {
  		breatheFloat = animationBreathe.swing(0.2F, 0.5F, false);
  		
  		if(this.getAttackTarget() != null && !this.getRecursivePassengersByType(EntityWight.class).isEmpty()) {
  			if(this.pathingCooldown <= 0) {
  				//No idea why the swamp hag doesn't want to move when possessed, this seems to work as hackish solution
  				if(!this.navigator.tryMoveToEntityLiving(this.getAttackTarget(), 1)) {
  					this.pathingCooldown = 20;
  				}
  			} else {
  				this.pathingCooldown--;
  			}
  		}
  		
  		if (!world.isRemote) {
  			updateLivingSoundTime();
  		}
  
  		if (animationTick > 0) {
  			animationTick--;
  		}
  
  		if (animationTick == 0) {
  			setShouldJawMove(false);
  			jawFloat = animationTalk.swing(0F, 0F, true);
  		}
  
  		if (getLivingSoundTime() == -getTalkInterval())
  			setShouldJawMove(true);
  
  		if (!shouldJawMove())
  			jawFloat = animationTalk.swing(0F, 0F, true);
  		else if (shouldJawMove() && getTalkSound() != 3 && getTalkSound() != 4)
  			jawFloat = animationTalk.swing(2.0F, 0.1F, false);
  		else if (shouldJawMove() && getTalkSound() == 3 || shouldJawMove() && getTalkSound() == 4)
  			jawFloat = animationTalk.swing(0.4F, 1.2F, false);
  		super.onLivingUpdate();
  	}
  
  	private byte getTalkSound() {
  		return dataManager.get(TALK_SOUND);
  	}
  
  	private void setTalkSound(int soundIndex) {
  		dataManager.set(TALK_SOUND, (byte) soundIndex);
  	}
  
  	public void setShouldJawMove(boolean jawState) {
  		dataManager.set(SHOULD_JAW_MOVE, jawState);
  		if (jawState)
  			animationTick = 20;
  	}
  
  	public boolean shouldJawMove() {
  		return dataManager.get(SHOULD_JAW_MOVE);
  	}
  
  	private void updateLivingSoundTime() {
  		dataManager.set(LIVING_SOUND_TIMER, livingSoundTime);
  	}
  
  	private int getLivingSoundTime() {
  		return dataManager.get(LIVING_SOUND_TIMER);
  	}
  
  	public boolean getIsThrowing() {
  		return dataManager.get(IS_THROWING);
  	}
  
  	public void setIsThrowing(boolean shooting) {
  		dataManager.set(IS_THROWING, shooting);
  	}
  	
  	public int getThrowTimer() {
  		return dataManager.get(THROW_TIMER);
  	}
  
  	public void setThrowTimer(int timer) {
  		dataManager.set(THROW_TIMER, timer);
  	}
  
  	public boolean isRaisingArm() {
  		return dataManager.get(THROW_TIMER) > 0 && dataManager.get(THROW_TIMER) < 90 ;
  	}
  
  	@Override
  	protected ResourceLocation getLootTable() {
  		return LootTableRegistry.SWAMP_HAG;
  	}
  	
  	@Override
      public float getBlockPathWeight(BlockPos pos) {
          return 0.5F;
      }
  
      @Override
      protected boolean isValidLightLevel() {
      	return true;
      }
      
      private boolean cancelRidingPathOverride;
      
      @Override
      public boolean isRiding() {
      	if(this.cancelRidingPathOverride && this.getRidingEntity() instanceof EntityPeatMummy) {
      		//This cancels the pathing and movement overrides from the rider in updateEntityActionState
      		this.cancelRidingPathOverride = false;
      		return false;
      	}
      	return super.isRiding();
      }
      
      @Override
      protected void updateAITasks() {
      	super.updateAITasks();
      	this.cancelRidingPathOverride = true;
      }
      
      static class EntityAIThrowWorm extends EntityAIBase {
      	EntitySwampHag hag;
  		EntityLivingBase target;
  		
  		public EntityAIThrowWorm(EntitySwampHag hag) {
  			this.hag = hag;
  			this.setMutexBits(5);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			target = hag.getAttackTarget();
  			
  			if (!hag.isRidingMummy())
  				return false;
  			if (hag.isRidingMummy() && !hag.getMummyMount().isSpawningFinished())
  				return false;
  			if (target == null)
  				return false;
  			else {
  				double distance = hag.getDistanceSq(target);
  				if (distance >= 16.0D && distance <= 1024.0D && hag.getThrowTimer() >= 90 && hag.canEntityBeSeen(target)) {
  						return true;
  				} else
  					return false;
  			}
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return target != null && hag.recentlyHit <= 40 && hag.getThrowTimer() >= 90 && hag.isRidingMummy() && hag.canEntityBeSeen(target);
  		}
  
  		@Override
  		public void startExecuting() {
  		}
  
  		@Override
  		public void updateTask() {
  			if(!hag.getIsThrowing())
  				hag.setIsThrowing(true);
  			if(target != null) {
  				hag.faceEntity(target, 30F, 30F);
  				hag.getLookHelper().setLookPositionWithEntity(target, 30.0F, 30.0F);
  				if (hag.getThrowTimer() == 90) {
  					double targetX = target.posX - hag.posX;
  					double targetY = target.getEntityBoundingBox().minY + (double) (target.height / 2.0F) - (hag.posY + (double) (hag.height / 2.0F));
  					double targetZ = target.posZ - hag.posZ;
  					double targetDistance = (double) MathHelper.sqrt(targetX * targetX + targetZ * targetZ);
  					EntityTinySludgeWorm worm = new EntityTinySludgeWorm(hag.getEntityWorld());
  					worm.setPosition(hag.posX, hag.posY + (double) hag.getEyeHeight() - 0.10000000149011612D, hag.posZ);
  					throwWorm(worm, targetX, targetY + targetDistance * 0.2D, targetZ, 1.6F, 0F);
  					hag.getEntityWorld().spawnEntity(worm);
  					hag.getEntityWorld().playSound(null, hag.getPosition(), SoundRegistry.WORM_THROW, SoundCategory.HOSTILE, 1F, 1F + (hag.getEntityWorld().rand.nextFloat() - hag.getEntityWorld().rand.nextFloat()) * 0.8F);
  					hag.playPullSound = true;
  				}
  			}
  			if (hag.getThrowTimer() == 102) {
  				if (hag.getIsThrowing()) {
  					hag.setIsThrowing(false);
  					hag.setThrowTimer(0);
  					hag.playPullSound = true;
  				}
  				resetTask();
  			}
  		}
  
  		@Override
  	    public void resetTask() {
  	        target = null;
  	    }
  
  		public void throwWorm(EntityTinySludgeWorm entity, double x, double y, double z, float velocity, float inaccuracy) {
  			float f = MathHelper.sqrt(x * x + y * y + z * z);
  			x = x / (double) f;
  			y = y / (double) f;
  			z = z / (double) f;
  			x = x + entity.rand.nextGaussian() * 0.007499999832361937D * (double) inaccuracy;
  			y = y + entity.rand.nextGaussian() * 0.007499999832361937D * (double) inaccuracy;
  			z = z + entity.rand.nextGaussian() * 0.007499999832361937D * (double) inaccuracy;
  			x = x * (double) velocity;
  			y = y * (double) velocity;
  			z = z * (double) velocity;
  			entity.motionX = x;
  			entity.motionY = y;
  			entity.motionZ = z;
  			float f1 = MathHelper.sqrt(x * x + z * z);
  			entity.rotationYaw = (float) (MathHelper.atan2(x, z) * (180D / Math.PI));
  			entity.rotationPitch = (float) (MathHelper.atan2(y, (double) f1) * (180D / Math.PI));
  			entity.prevRotationYaw = entity.rotationYaw;
  			entity.prevRotationPitch = entity.rotationPitch;
  		}
  	}
  }
  