content: package thebetweenlands.common.tile;
  
  import java.util.List;
  
  import net.minecraft.block.state.IBlockState;
  import net.minecraft.entity.Entity;
  import net.minecraft.entity.EntityLivingBase;
  import net.minecraft.entity.IProjectile;
  import net.minecraft.entity.MoverType;
  import net.minecraft.entity.monster.IMob;
  import net.minecraft.entity.projectile.EntityFireball;
  import net.minecraft.init.MobEffects;
  import net.minecraft.nbt.NBTTagCompound;
  import net.minecraft.network.NetworkManager;
  import net.minecraft.network.play.server.SPacketUpdateTileEntity;
  import net.minecraft.potion.PotionEffect;
  import net.minecraft.tileentity.TileEntity;
  import net.minecraft.util.ITickable;
  import net.minecraft.util.math.AxisAlignedBB;
  import net.minecraft.util.math.Vec3d;
  import net.minecraftforge.fml.relauncher.Side;
  import net.minecraftforge.fml.relauncher.SideOnly;
  import thebetweenlands.api.entity.IBLBoss;
  import thebetweenlands.common.entity.mobs.EntityWight;
  
  public class TileEntityRepeller extends TileEntity implements ITickable {
  	private static final float MAX_RADIUS = 18.0F;
  	private static final int DEPLOY_TIME = 80;
  
  	protected boolean hasShimmerstone = false;
  	protected int fuel = 0;
  	protected boolean running = false;
  	
  	private boolean prevRunning = false;
  	private float lastRadius = 0.0F;
  	private float radius = 0.0F;
  	private int deployTicks = 0;
  	private int radiusState = 0;
  	private float accumulatedCost = 0.0F;
  
  	public int renderTicks = 0;
  
  	public void setRadiusState(int state) {
  		state = state % 4;
  		if(state != this.radiusState) {
  			if(this.running) {
  				this.deployTicks = 0;
  			}
  			this.radiusState = state;
  			this.markDirty();
  			IBlockState blockState = this.world.getBlockState(this.pos);
  			this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  		}
  	}
  	
  	public int getRadiusState() {
  		return this.radiusState;
  	}
  
  	public void cycleRadiusState() {
  		this.radiusState = (this.radiusState + 1) % 4;
  		if(this.running)
  			this.deployTicks = 0;
  		this.markDirty();
  		IBlockState blockState = this.world.getBlockState(this.pos);
  		this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  	}
  
  	public void addShimmerstone() {
  		if(!this.hasShimmerstone) {
  			this.hasShimmerstone = true;
  			this.markDirty();
  			IBlockState blockState = this.world.getBlockState(this.pos);
  			this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  		}
  	}
  
  	public boolean hasShimmerstone() {
  		return this.hasShimmerstone;
  	}
  
  	public void removeShimmerstone() {
  		if(this.hasShimmerstone) {
  			this.hasShimmerstone = false;
  			this.markDirty();
  			IBlockState blockState = this.world.getBlockState(this.pos);
  			this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  		}
  	}
  
  	public int getMaxFuel() {
  		return 10000;
  	}
  
  	public int addFuel(int amount) {
  		if(amount != 0) {
  			int canAdd = this.getMaxFuel() - this.fuel;
  			if(canAdd > 0) {
  				int added = Math.min(canAdd, amount);
  				this.fuel += added;
  				IBlockState blockState = this.world.getBlockState(this.pos);
  				this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  				this.markDirty();
  				return added;
  			}
  		}
  		return 0;
  	}
  
  	public int removeFuel(int amount) {
  		int removed = Math.min(this.fuel, amount);
  		if(amount != 0) {
  			this.fuel -= amount;
  			IBlockState blockState = this.world.getBlockState(this.pos);
  			this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  			this.markDirty();
  		}
  		return removed;
  	}
  	
  	public int getFuel() {
  		return this.fuel;
  	}
  
  	public void emptyFuel() {
  		this.fuel = 0;
  	}
  
  	public boolean isRunning() {
  		return this.running || this.radius > 0.0F;
  	}
  
  	public float getRadius(float partialTicks) {
  		return this.lastRadius + (this.radius - this.lastRadius) * partialTicks;
  	}
  
  	@Override
  	public void update() {
  		if(!this.world.isRemote) {
  			if(this.fuel > 0) {
  				if(this.fuel <= 0) {
  					this.fuel = 0;
  					this.markDirty();
  				}
  			}
  			if(this.fuel > 0 && this.hasShimmerstone) {
  				if(!this.running) {
  					this.running = true;
  					IBlockState blockState = this.world.getBlockState(this.pos);
  					this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  					this.markDirty();
  				}
  			} else if(this.fuel <= 0 || !this.hasShimmerstone) {
  				if(this.running) {
  					this.running = false;
  					IBlockState blockState = this.world.getBlockState(this.pos);
  					this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  					this.markDirty();
  				}
  			}
  			if(this.fuel < 0) {
  				this.fuel = 0;
  				IBlockState blockState = this.world.getBlockState(this.pos);
  				this.world.notifyBlockUpdate(this.pos, blockState, blockState, 2);
  				this.markDirty();
  			} else {
  				float fuelCost = 0;
  				double centerX = this.pos.getX() + 0.5F;
  				double centerY = this.pos.getY() + 1.15F;
  				double centerZ = this.pos.getZ() + 0.5F;
  				AxisAlignedBB affectedBB = new AxisAlignedBB(this.pos.getX() - this.radius - 5.0F, this.pos.getY() - this.radius - 5.0F, this.pos.getZ() - this.radius - 5.0F, this.pos.getX() + this.radius + 5.0F, this.pos.getY() + this.radius + 5.0F, this.pos.getZ() + this.radius + 5.0F);
  				List<Entity> affectedEntities = this.world.getEntitiesWithinAABB(Entity.class, affectedBB);
  				for(Entity entity : affectedEntities) {
  					if(entity instanceof IMob && entity instanceof EntityWight == false && entity instanceof IBLBoss == false) {
  						Vec3d closestPoint = this.getClosestAABBCorner(entity.getEntityBoundingBox(), centerX, centerY, centerZ);
  						if(closestPoint.squareDistanceTo(centerX, centerY, centerZ) < this.radius*this.radius) {
  							double diffX = entity.posX - centerX;
  							double diffY = entity.posY - centerY;
  							double diffZ = entity.posZ - centerZ;
  							entity.move(MoverType.PISTON, diffX*0.1F, 0.0F, diffZ*0.1F);
  							double len = Math.sqrt(diffX*diffX + diffY*diffY + diffZ*diffZ);
  							double speed = (this.radius - len) / this.radius * 1.5F + 0.5F;
  							entity.motionX = (float)(diffX / len) * speed;
  							entity.motionZ = (float)(diffZ / len) * speed;
  							if(entity instanceof EntityLivingBase) {
  								((EntityLivingBase)entity).addPotionEffect(new PotionEffect(MobEffects.SLOWNESS, 10, 6));
  							}
  							if(!entity.collidedHorizontally) {
  								fuelCost += 0.00028F * (this.radiusState / 1.5F + 1);
  							}
  						}
  					}
  					if(entity instanceof IProjectile || entity instanceof EntityFireball) {
  						Vec3d closestPoint = this.getClosestAABBCorner(entity.getEntityBoundingBox(), centerX, centerY, centerZ);
  						if(closestPoint.squareDistanceTo(centerX, centerY, centerZ) < this.radius*this.radius) {
  							double velocity = Math.sqrt(entity.motionX*entity.motionX + entity.motionY*entity.motionY + entity.motionZ*entity.motionZ);
  							double diffX = entity.posX - centerX;
  							double diffY = entity.posY - centerY;
  							double diffZ = entity.posZ - centerZ;
  							double len = Math.sqrt(diffX*diffX + diffY*diffY + diffZ*diffZ);
  							entity.move(MoverType.PISTON, diffX*0.1F, 0.0F, diffZ*0.1F);
  							entity.motionX = (float)(diffX / len) * velocity;
  							entity.motionY = (float)(diffY / len) * velocity;
  							entity.motionZ = (float)(diffZ / len) * velocity;
  							if(entity instanceof IProjectile) {
  								((IProjectile)entity).shoot(diffX / len, diffY / len, diffZ / len, 1.0F, 1.0F);
  							}
  							entity.velocityChanged = true;
  							if(!entity.collidedHorizontally && !entity.collidedVertically && !entity.onGround) {
  								fuelCost += 0.0004F * (this.radiusState / 1.5F + 1);
  							}
  						}
  					}
  				}
  
  				boolean fuelConsumed = false;
  				
  				//Limit fuel cost per tick
  				this.accumulatedCost += Math.min(fuelCost, 0.00125F) * 1000;
  				while(this.accumulatedCost > 1.0F) {
  					this.accumulatedCost -= 1.0F;
  					this.fuel--;
  					fuelConsumed = true;
  				}
  
  				if(fuelConsumed) {
  					IBlockState blockState = this.world.getBlockState(this.pos);
  					this.world.notifyBlockUpdate(this.pos, blockState, blockState, 3);
  				}
  				
  				this.markDirty();
  			}
  		} else {
  			this.renderTicks++;
  		}
  
  		if(this.prevRunning != this.running) {
  			this.deployTicks = 0;
  			this.markDirty();
  		}
  		this.prevRunning = this.running;
  
  		float desiredRadius = MAX_RADIUS / 4.0F * (this.radiusState + 1);
  		this.lastRadius = this.radius;
  		if(this.running && this.radius < desiredRadius && this.deployTicks < DEPLOY_TIME) {
  			this.deployTicks++;
  			this.radius = (float) this.easeInOut(this.deployTicks, this.radius, desiredRadius, DEPLOY_TIME);
  			if(this.radius > desiredRadius) {
  				this.radius = desiredRadius;
  			}
  			this.markDirty();
  		} else if((!this.running && this.radius > 0.0F) || this.radius > desiredRadius) {
  			this.deployTicks++;
  			this.radius = (float) this.easeInOut(this.deployTicks, !this.running ? desiredRadius : this.radius, -desiredRadius, DEPLOY_TIME);
  			if(!this.running && this.radius < 0.0F) {
  				this.radius = 0.0F;
  			} else if(this.running && this.radius < desiredRadius) {
  				this.radius = desiredRadius;
  			}
  			this.markDirty();
  		}
  	}
  
  	protected Vec3d getClosestAABBCorner(AxisAlignedBB bb, double centerX, double centerY, double centerZ) {
  		Vec3d center = new Vec3d(centerX, centerY, centerZ);
  		Vec3d closest = null;
  		for(int bcx = 0; bcx <= 1; bcx++) {
  			for(int bcy = 0; bcy <= 1; bcy++) {
  				for(int bcz = 0; bcz <= 1; bcz++) {
  					double cx = bcx == 1 ? bb.maxX : bb.minX;
  					double cy = bcy == 1 ? bb.maxY : bb.minY;
  					double cz = bcz == 1 ? bb.maxZ : bb.minZ;
  					Vec3d current = new Vec3d(cx, cy, cz);
  					if(closest == null || current.distanceTo(center) < closest.distanceTo(center)) {
  						closest = current;
  					}
  				}
  			}
  		}
  		return closest;
  	}
  
  	protected double easeInOut(float t, float b, float c, float d) {
  		t /= d/2;
  		if (t < 1) return c/2*t*t*t*t*t + b;
  		t -= 2;
  		return c/2*(t*t*t*t*t + 2) + b;
  	}
  
  	@Override
  	@SideOnly(Side.CLIENT)
  	public AxisAlignedBB getRenderBoundingBox() {
  		double radius = this.getRadius(0.0F);
  		return new AxisAlignedBB(this.pos).grow(radius, radius, radius).expand(0, 1, 0);
  	}
  
  	@Override
  	public void readFromNBT(NBTTagCompound nbt) {
  		super.readFromNBT(nbt);
  		this.fuel = nbt.getInteger("fuel");
  		this.hasShimmerstone = nbt.getBoolean("hasShimmerstone");
  		this.deployTicks = nbt.getInteger("deployTicks");
  		this.radius = nbt.getFloat("radius");
  		this.running = nbt.getBoolean("running");
  		this.prevRunning = this.running;
  		this.radiusState = nbt.getInteger("radiusState");
  		this.accumulatedCost = nbt.getFloat("accumulatedCost");
  	}
  
  	@Override
  	public NBTTagCompound writeToNBT(NBTTagCompound nbt) {
  		super.writeToNBT(nbt);
  		nbt.setInteger("fuel", this.fuel);
  		nbt.setBoolean("hasShimmerstone", this.hasShimmerstone);
  		nbt.setInteger("deployTicks", this.deployTicks);
  		nbt.setFloat("radius", this.radius);
  		nbt.setBoolean("running", this.running);
  		nbt.setInteger("radiusState", this.radiusState);
  		nbt.setFloat("accumulatedCost", this.accumulatedCost);
  		return nbt;
  	}
  
  	@Override
  	public SPacketUpdateTileEntity getUpdatePacket() {
  		NBTTagCompound nbt = new NBTTagCompound();
  		nbt.setInteger("fuel", this.fuel);
  		nbt.setBoolean("hasShimmerstone", this.hasShimmerstone);
  		nbt.setBoolean("running", this.running);
  		nbt.setInteger("deployTicks", this.deployTicks);
  		nbt.setFloat("radius", this.radius);
  		nbt.setInteger("radiusState", this.radiusState);
  		return new SPacketUpdateTileEntity(this.getPos(), 1, nbt);
  	}
  
  	@Override
  	public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
  		NBTTagCompound nbt = pkt.getNbtCompound();
  		this.fuel = nbt.getInteger("fuel");
  		this.hasShimmerstone = nbt.getBoolean("hasShimmerstone");
  		this.deployTicks = nbt.getInteger("deployTicks");
  		this.radius = nbt.getFloat("radius");
  		this.running = nbt.getBoolean("running");
  		this.prevRunning = this.running;
  		this.radiusState = nbt.getInteger("radiusState");
  	}
  
  	@Override
  	public NBTTagCompound getUpdateTag() {
  		NBTTagCompound nbt = super.getUpdateTag();
  		nbt.setInteger("fuel", this.fuel);
  		nbt.setBoolean("hasShimmerstone", this.hasShimmerstone);
  		nbt.setBoolean("running", this.running);
  		nbt.setInteger("deployTicks", this.deployTicks);
  		nbt.setFloat("radius", this.radius);
  		nbt.setInteger("radiusState", this.radiusState);
  		return nbt;
  	}
  }
  