content: package thebetweenlands.common.entity.mobs;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import net.minecraft.entity.EntityLivingBase;
  import net.minecraft.entity.MultiPartEntityPart;
  import net.minecraft.entity.SharedMonsterAttributes;
  import net.minecraft.entity.ai.EntityAIAttackMelee;
  import net.minecraft.entity.ai.EntityAIBase;
  import net.minecraft.entity.ai.EntityAIHurtByTarget;
  import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
  import net.minecraft.entity.ai.EntityAIWander;
  import net.minecraft.entity.monster.IMob;
  import net.minecraft.entity.player.EntityPlayer;
  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.util.DamageSource;
  import net.minecraft.util.EntityDamageSource;
  import net.minecraft.util.ResourceLocation;
  import net.minecraft.util.math.MathHelper;
  import net.minecraft.util.math.Vec3d;
  import net.minecraft.world.World;
  import net.minecraftforge.common.util.Constants;
  import thebetweenlands.common.entity.EntityTinyWormEggSac;
  import thebetweenlands.common.registries.LootTableRegistry;
  import thebetweenlands.util.CatmullRomSpline;
  import thebetweenlands.util.ReparameterizedSpline;
  
  public class EntityLargeSludgeWorm extends EntitySludgeWorm {
  	private static final DataParameter<Float> EGG_SAC_PERCENTAGE = EntityDataManager.<Float>createKey(EntityLargeSludgeWorm.class, DataSerializers.FLOAT);
  
  	public boolean segmentsAvailable = false;
  
  	public ReparameterizedSpline spineySpliney;
  
  	public final HullSegment[] segments;
  	public final List<SpineBone> bones = new ArrayList<>();
  
  	public Vec3d prevEggSacPosition = null;
  	public Vec3d eggSacPosition = null;
  
  	protected int eggSacMovementCooldown = 0;
  
  	protected static final float HULL_OUTER_WIDTH = 0.58F;
  	protected static final float HULL_INNER_WIDTH = 0.44F;
  	protected static final float[][] HULL_CROSS_SECTION = new float[][] {
  		{-HULL_OUTER_WIDTH, HULL_INNER_WIDTH},
  		{-HULL_OUTER_WIDTH, -HULL_INNER_WIDTH},
  		{-HULL_INNER_WIDTH, -HULL_INNER_WIDTH},
  		{-HULL_INNER_WIDTH, -HULL_OUTER_WIDTH},
  		{HULL_INNER_WIDTH, -HULL_OUTER_WIDTH},
  		{HULL_INNER_WIDTH, -HULL_INNER_WIDTH},
  		{HULL_OUTER_WIDTH, -HULL_INNER_WIDTH},
  		{HULL_OUTER_WIDTH, HULL_INNER_WIDTH},
  		{HULL_INNER_WIDTH, HULL_INNER_WIDTH},
  		{HULL_INNER_WIDTH, HULL_OUTER_WIDTH},
  		{-HULL_INNER_WIDTH, HULL_OUTER_WIDTH},
  		{-HULL_INNER_WIDTH, HULL_INNER_WIDTH},
  	};
  
  	public static class HullSegment {
  		private static final Vec3d WORLD_UP = new Vec3d(0, 1, 0);
  
  		public Vec3d prevPos, pos;
  		public float prevYaw, yaw;
  		public final float[] offsetX, offsetY, offsetZ;
  
  		public HullSegment() {
  			this.offsetX = new float[HULL_CROSS_SECTION.length];
  			this.offsetY = new float[HULL_CROSS_SECTION.length];
  			this.offsetZ = new float[HULL_CROSS_SECTION.length];
  		}
  
  		public void update(Vec3d newPos, Vec3d splineDir) {
  			this.prevPos = this.pos;
  			this.pos = newPos;
  
  			this.prevYaw = this.yaw;
  			this.yaw = (float)Math.toDegrees(Math.atan2(splineDir.z, splineDir.x)) - 90;
  
  			if(this.prevPos == null) {
  				this.prevPos = this.pos;
  				this.prevYaw = this.yaw;
  			}
  
  			while(this.yaw - this.prevYaw < -180.0F) {
  				this.prevYaw -= 360.0F;
  			}
  
  			while(this.yaw - this.prevYaw >= 180.0F) {
  				this.prevYaw += 360.0F;
  			}
  
  			Vec3d right = splineDir.crossProduct(WORLD_UP).normalize();
  			Vec3d up = right.crossProduct(splineDir).normalize();
  
  			int i = 0;
  			for(float[] hullCrossSection : HULL_CROSS_SECTION) {
  				float hullX = hullCrossSection[0];
  				float hullY = hullCrossSection[1];
  
  				this.offsetX[i] = (float) (right.x * hullX + up.x * hullY);
  				this.offsetY[i] = (float) (right.y * hullX + up.y * hullY);
  				this.offsetZ[i] = (float) (right.z * hullX + up.z * hullY);
  
  				i++;
  			}
  		}
  	}
  
  	public static class SpineBone {
  		public Vec3d prevPos, pos;
  		public float prevYaw, yaw;
  
  		public void update(Vec3d newPos, Vec3d splineDir) {
  			this.prevPos = this.pos;
  			this.pos = newPos;
  
  			this.prevYaw = this.yaw;
  			this.yaw = -(float)Math.toDegrees(Math.atan2(splineDir.z, splineDir.x)) + 90;
  
  			if(this.prevPos == null) {
  				this.prevPos = this.pos;
  				this.prevYaw = this.yaw;
  			}
  
  			while(this.yaw - this.prevYaw < -180.0F) {
  				this.prevYaw -= 360.0F;
  			}
  
  			while(this.yaw - this.prevYaw >= 180.0F) {
  				this.prevYaw += 360.0F;
  			}
  		}
  	}
  
  	public EntityLargeSludgeWorm(World world) {
  		super(world, false);
  		this.experienceValue = 10;
  		setSize(0.8F, 0.8F);
  		isImmuneToFire = true;
  
  		this.parts = new MultiPartEntityPart[] {
  				new MultiPartEntityPart(this, "part1", 0.8F, 0.8F),
  				new MultiPartEntityPart(this, "part2", 0.8F, 0.8F),
  				new MultiPartEntityPart(this, "part3", 0.8F, 0.8F),
  				new MultiPartEntityPart(this, "part4", 0.8F, 0.8F),
  				new MultiPartEntityPart(this, "part5", 0.8F, 0.8F)
  		};
  
  		final int numSegments = 3 * this.parts.length;
  
  		this.segments = new HullSegment[numSegments];
  	}
  
  	@Override
  	protected void entityInit() {
  		super.entityInit();
  		this.getDataManager().register(EGG_SAC_PERCENTAGE, -1.0F);
  	}
  
  	@Override
  	protected void initEntityAI() {
  		tasks.addTask(1, new EntityAIAttackMelee(this, 1, false));
  		tasks.addTask(3, new EntityAIWander(this, 0.8D, 1));
  		tasks.addTask(4, new AILayEggSac(this));
  		targetTasks.addTask(0, new EntityAIHurtByTarget(this, false));
  		targetTasks.addTask(1, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, true));
  		targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityLivingBase.class, 10, true, false, entity -> entity instanceof IMob == false));
  	}
  
  	@Override
  	protected void applyEntityAttributes() {
  		super.applyEntityAttributes();
  		getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(60.0D);
  		getEntityAttribute(SharedMonsterAttributes.FOLLOW_RANGE).setBaseValue(20.0D);
  		getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.19D);
  		getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D);
  	}
  
  	@Override
  	protected ResourceLocation getLootTable() {
  		return LootTableRegistry.LARGE_SLUDGE_WORM;
  	}
  
  	@Override
  	protected float getSoundPitch() {
  		return super.getSoundPitch() * 0.5F;
  	}
  
  	@Override
  	protected double getMaxPieceDistance() {
  		return 0.95D;
  	}
  
  	@Override
  	public void writeEntityToNBT(NBTTagCompound nbt) {
  		super.writeEntityToNBT(nbt);
  
  		nbt.setFloat("eggSacPercentage", this.getEggSacPercentage());
  		nbt.setInteger("eggSacCooldown", this.eggSacMovementCooldown);
  	}	
  
  	@Override
  	public void readEntityFromNBT(NBTTagCompound nbt) {
  		super.readEntityFromNBT(nbt);
  
  		if(nbt.hasKey("eggSacPercentage", Constants.NBT.TAG_FLOAT)) {
  			this.setEggSacPercentage(nbt.getFloat("eggSacPercentage"));
  		}
  		if(nbt.hasKey("eggSacCooldown", Constants.NBT.TAG_INT)) {
  			this.eggSacMovementCooldown = nbt.getInteger("eggSacCooldown");
  		}
  	}
  
  
  	@Override
  	public void onUpdate() {
  		super.onUpdate();
  
  		if(!this.world.isRemote) {
  			if(this.isEntityAlive()) {
  				if(this.eggSacMovementCooldown > 0) {
  					this.eggSacMovementCooldown--;
  				} else if(this.getEggSacPercentage() >= 0) {
  					float percentage = Math.max(this.getEggSacPercentage(), 0) + 0.005F;
  
  					if(percentage >= 1.0F) {
  						MultiPartEntityPart tailPart = this.parts[this.parts.length - 1];
  
  						EntityTinyWormEggSac eggSac = new EntityTinyWormEggSac(this.world);
  						eggSac.setLocationAndAngles(tailPart.posX, tailPart.posY, tailPart.posZ, 0, 0);
  
  						this.world.spawnEntity(eggSac);
  
  						this.setEggSacPercentage(-1);
  					} else {
  						this.setEggSacPercentage(percentage);
  					}
  				}
  			}
  		} else {
  			this.updateSegmentPositions();
  
  			float eggSackPercentage = this.getEggSacPercentage();
  
  			if(eggSackPercentage >= 0) {
  				this.prevEggSacPosition = this.eggSacPosition;
  				this.eggSacPosition = this.spineySpliney.interpolate(eggSackPercentage);
  			} else {
  				this.prevEggSacPosition = this.eggSacPosition = null;
  			}
  		}
  	}
  
  	@Override
  	protected boolean damageWorm(DamageSource source, float amount) {
  		this.eggSacMovementCooldown = 50;
  
  		if(!this.world.isRemote && source instanceof EntityDamageSource && this.world.rand.nextInt(6) == 0 && amount > 0.5F) {
  			MultiPartEntityPart spawnPart = this.parts[this.rand.nextInt(this.parts.length)];
  
  			EntitySmollSludge entity = new EntitySmollSludge(this.world);
  			entity.setLocationAndAngles(spawnPart.posX, spawnPart.posY, spawnPart.posZ, this.rand.nextFloat() * 360.0F, 0);
  
  			this.world.spawnEntity(entity);
  		}
  
  		return super.damageWorm(source, amount);
  	}
  
  	protected void updateSegmentPositions() {
  		this.segmentsAvailable = true;
  
  		Vec3d origin = this.getPositionVector();
  
  		Vec3d[] points = new Vec3d[this.parts.length + 2];
  
  		Vec3d partDir = null;
  		MultiPartEntityPart prevPart = null;
  
  		Vec3d look = this.getLookVec();
  
  		points[0] = this.parts[0].getPositionVector().add(-origin.x + look.x, -origin.y + look.y, -origin.z + look.z);
  		for(int i = 0; i < this.parts.length; i++) {
  			MultiPartEntityPart part = this.parts[i];
  
  			boolean isSamePos = false;
  
  			if(prevPart != null) {
  				Vec3d currPos = part.getPositionVector();
  				Vec3d prevPos = prevPart.getPositionVector();
  				Vec3d diff = currPos.subtract(prevPos);
  				if(diff.lengthSquared() > 0.01D) {
  					partDir = diff.normalize();
  				} else {
  					isSamePos = true;
  				}
  			}
  
  			prevPart = part;
  
  			Vec3d splineNode = part.getPositionVector().subtract(origin);
  
  			if(isSamePos && partDir != null) {
  				//Adds a slight offset in part dir such that the two positions
  				//aren't the same
  				splineNode = splineNode.add(partDir.scale(0.1D / this.parts.length * i));
  			}
  
  			points[i + 1] = splineNode;
  		}
  
  		Vec3d endPoint;
  		if(partDir != null) {
  			endPoint = this.parts[this.parts.length - 1].getPositionVector().add(-origin.x + partDir.x, -origin.y + partDir.y, -origin.z + partDir.z);
  		} else {
  			endPoint = this.parts[this.parts.length - 1].getPositionVector().add(-origin.x, -origin.y- 0.0001D, -origin.z);
  		}
  		points[this.parts.length + 1] = endPoint;
  
  		this.spineySpliney = new ReparameterizedSpline(new CatmullRomSpline(points));
  		this.spineySpliney.init(this.segments.length * 2, 3);
  
  		for(int i = 0; i < this.segments.length; i++) {
  			HullSegment segment = this.segments[i];
  			if(segment == null) {
  				this.segments[i] = segment = new HullSegment();
  			}
  
  			Vec3d pos = this.spineySpliney.interpolate(i / (float)(this.segments.length - 1));
  			Vec3d dir = this.spineySpliney.derivative(i / (float)(this.segments.length - 1));
  
  			segment.update(pos, dir);
  		}
  
  		int spineBones = MathHelper.ceil(this.spineySpliney.getArcLength() * 12);
  
  		for(int i = spineBones - this.bones.size(); i > 0; i--) {
  			this.bones.add(new SpineBone());
  		}
  		for(int i = this.bones.size() - spineBones; i > 0; i--) {
  			this.bones.remove(this.bones.size() - 1);
  		}
  
  		for(int i = 0; i < this.bones.size(); i++) {
  			SpineBone bone = this.bones.get(i);
  
  			Vec3d pos = this.spineySpliney.interpolate(i / (float)(this.bones.size() - 1));
  			Vec3d dir = this.spineySpliney.derivative(i / (float)(this.bones.size() - 1));
  
  			bone.update(pos, dir);
  		}
  	}
  
  	/**
  	 * Sets the egg sac laying progress percentage.
  	 * Use value < 0 if no egg sac.
  	 * @param percentage
  	 */
  	public void setEggSacPercentage(float percentage) {
  		this.getDataManager().set(EGG_SAC_PERCENTAGE, percentage);
  	}
  
  	/**
  	 * Returns the egg sac laying progress percentage.
  	 * Value < 0 means no egg sac.
  	 * @return
  	 */
  	public float getEggSacPercentage() {
  		return this.getDataManager().get(EGG_SAC_PERCENTAGE);
  	}
  
  	public void startLayingEggSac() {
  		if(this.getEggSacPercentage() < 0) {
  			this.setEggSacPercentage(0.00001F);
  		}
  	}
  
  	public static class AILayEggSac extends EntityAIBase {
  		protected final EntityLargeSludgeWorm entity;
  
  		protected int cooldown = 30;
  
  		public AILayEggSac(EntityLargeSludgeWorm entity) {
  			this.entity = entity;
  			this.setMutexBits(0);
  		}
  
  		@Override
  		public boolean shouldExecute() {
  			boolean canLay = this.entity.isEntityAlive() && this.entity.getAttackTarget() != null && this.entity.getEggSacPercentage() < 0;
  			if(canLay) {
  				if(this.cooldown-- <= 0) {
  					List<EntityTinyWormEggSac> nearbyEggSacs = this.entity.world.getEntitiesWithinAABB(EntityTinyWormEggSac.class, this.entity.getEntityBoundingBox().grow(16.0D));
  					List<EntityTinySludgeWorm> nearbyTinyWorms = this.entity.world.getEntitiesWithinAABB(EntityTinySludgeWorm.class, this.entity.getEntityBoundingBox().grow(16.0D));
  
  					if(nearbyEggSacs.size() < 5 && nearbyTinyWorms.size() < 8) {
  						this.cooldown = 30 + this.entity.rand.nextInt(30);
  						return true;
  					} else {
  						this.cooldown = 10 + this.entity.rand.nextInt(20);
  					}
  				}
  			}
  			return false;
  		}
  
  		@Override
  		public void startExecuting() {
  			this.entity.startLayingEggSac();
  		}
  
  		@Override
  		public boolean shouldContinueExecuting() {
  			return false;
  		}
  	}
  }
  