001package net.minecraft.entity.projectile;
002
003import cpw.mods.fml.relauncher.Side;
004import cpw.mods.fml.relauncher.SideOnly;
005import java.util.List;
006import net.minecraft.block.Block;
007import net.minecraft.entity.Entity;
008import net.minecraft.entity.EntityLiving;
009import net.minecraft.entity.IProjectile;
010import net.minecraft.entity.player.EntityPlayer;
011import net.minecraft.nbt.NBTTagCompound;
012import net.minecraft.util.AxisAlignedBB;
013import net.minecraft.util.EnumMovingObjectType;
014import net.minecraft.util.MathHelper;
015import net.minecraft.util.MovingObjectPosition;
016import net.minecraft.util.Vec3;
017import net.minecraft.world.World;
018
019public abstract class EntityThrowable extends Entity implements IProjectile
020{
021    private int xTile = -1;
022    private int yTile = -1;
023    private int zTile = -1;
024    private int inTile = 0;
025    protected boolean inGround = false;
026    public int throwableShake = 0;
027
028    /**
029     * Is the entity that throws this 'thing' (snowball, ender pearl, eye of ender or potion)
030     */
031    private EntityLiving thrower;
032    private String throwerName = null;
033    private int ticksInGround;
034    private int ticksInAir = 0;
035
036    public EntityThrowable(World par1World)
037    {
038        super(par1World);
039        this.setSize(0.25F, 0.25F);
040    }
041
042    protected void entityInit() {}
043
044    @SideOnly(Side.CLIENT)
045
046    /**
047     * Checks if the entity is in range to render by using the past in distance and comparing it to its average edge
048     * length * 64 * renderDistanceWeight Args: distance
049     */
050    public boolean isInRangeToRenderDist(double par1)
051    {
052        double d1 = this.boundingBox.getAverageEdgeLength() * 4.0D;
053        d1 *= 64.0D;
054        return par1 < d1 * d1;
055    }
056
057    public EntityThrowable(World par1World, EntityLiving par2EntityLiving)
058    {
059        super(par1World);
060        this.thrower = par2EntityLiving;
061        this.setSize(0.25F, 0.25F);
062        this.setLocationAndAngles(par2EntityLiving.posX, par2EntityLiving.posY + (double)par2EntityLiving.getEyeHeight(), par2EntityLiving.posZ, par2EntityLiving.rotationYaw, par2EntityLiving.rotationPitch);
063        this.posX -= (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F);
064        this.posY -= 0.10000000149011612D;
065        this.posZ -= (double)(MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * 0.16F);
066        this.setPosition(this.posX, this.posY, this.posZ);
067        this.yOffset = 0.0F;
068        float f = 0.4F;
069        this.motionX = (double)(-MathHelper.sin(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI) * f);
070        this.motionZ = (double)(MathHelper.cos(this.rotationYaw / 180.0F * (float)Math.PI) * MathHelper.cos(this.rotationPitch / 180.0F * (float)Math.PI) * f);
071        this.motionY = (double)(-MathHelper.sin((this.rotationPitch + this.func_70183_g()) / 180.0F * (float)Math.PI) * f);
072        this.setThrowableHeading(this.motionX, this.motionY, this.motionZ, this.func_70182_d(), 1.0F);
073    }
074
075    public EntityThrowable(World par1World, double par2, double par4, double par6)
076    {
077        super(par1World);
078        this.ticksInGround = 0;
079        this.setSize(0.25F, 0.25F);
080        this.setPosition(par2, par4, par6);
081        this.yOffset = 0.0F;
082    }
083
084    protected float func_70182_d()
085    {
086        return 1.5F;
087    }
088
089    protected float func_70183_g()
090    {
091        return 0.0F;
092    }
093
094    /**
095     * Similar to setArrowHeading, it's point the throwable entity to a x, y, z direction.
096     */
097    public void setThrowableHeading(double par1, double par3, double par5, float par7, float par8)
098    {
099        float f2 = MathHelper.sqrt_double(par1 * par1 + par3 * par3 + par5 * par5);
100        par1 /= (double)f2;
101        par3 /= (double)f2;
102        par5 /= (double)f2;
103        par1 += this.rand.nextGaussian() * 0.007499999832361937D * (double)par8;
104        par3 += this.rand.nextGaussian() * 0.007499999832361937D * (double)par8;
105        par5 += this.rand.nextGaussian() * 0.007499999832361937D * (double)par8;
106        par1 *= (double)par7;
107        par3 *= (double)par7;
108        par5 *= (double)par7;
109        this.motionX = par1;
110        this.motionY = par3;
111        this.motionZ = par5;
112        float f3 = MathHelper.sqrt_double(par1 * par1 + par5 * par5);
113        this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(par1, par5) * 180.0D / Math.PI);
114        this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(par3, (double)f3) * 180.0D / Math.PI);
115        this.ticksInGround = 0;
116    }
117
118    @SideOnly(Side.CLIENT)
119
120    /**
121     * Sets the velocity to the args. Args: x, y, z
122     */
123    public void setVelocity(double par1, double par3, double par5)
124    {
125        this.motionX = par1;
126        this.motionY = par3;
127        this.motionZ = par5;
128
129        if (this.prevRotationPitch == 0.0F && this.prevRotationYaw == 0.0F)
130        {
131            float f = MathHelper.sqrt_double(par1 * par1 + par5 * par5);
132            this.prevRotationYaw = this.rotationYaw = (float)(Math.atan2(par1, par5) * 180.0D / Math.PI);
133            this.prevRotationPitch = this.rotationPitch = (float)(Math.atan2(par3, (double)f) * 180.0D / Math.PI);
134        }
135    }
136
137    /**
138     * Called to update the entity's position/logic.
139     */
140    public void onUpdate()
141    {
142        this.lastTickPosX = this.posX;
143        this.lastTickPosY = this.posY;
144        this.lastTickPosZ = this.posZ;
145        super.onUpdate();
146
147        if (this.throwableShake > 0)
148        {
149            --this.throwableShake;
150        }
151
152        if (this.inGround)
153        {
154            int i = this.worldObj.getBlockId(this.xTile, this.yTile, this.zTile);
155
156            if (i == this.inTile)
157            {
158                ++this.ticksInGround;
159
160                if (this.ticksInGround == 1200)
161                {
162                    this.setDead();
163                }
164
165                return;
166            }
167
168            this.inGround = false;
169            this.motionX *= (double)(this.rand.nextFloat() * 0.2F);
170            this.motionY *= (double)(this.rand.nextFloat() * 0.2F);
171            this.motionZ *= (double)(this.rand.nextFloat() * 0.2F);
172            this.ticksInGround = 0;
173            this.ticksInAir = 0;
174        }
175        else
176        {
177            ++this.ticksInAir;
178        }
179
180        Vec3 vec3 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX, this.posY, this.posZ);
181        Vec3 vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
182        MovingObjectPosition movingobjectposition = this.worldObj.rayTraceBlocks(vec3, vec31);
183        vec3 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX, this.posY, this.posZ);
184        vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX + this.motionX, this.posY + this.motionY, this.posZ + this.motionZ);
185
186        if (movingobjectposition != null)
187        {
188            vec31 = this.worldObj.getWorldVec3Pool().getVecFromPool(movingobjectposition.hitVec.xCoord, movingobjectposition.hitVec.yCoord, movingobjectposition.hitVec.zCoord);
189        }
190
191        if (!this.worldObj.isRemote)
192        {
193            Entity entity = null;
194            List list = this.worldObj.getEntitiesWithinAABBExcludingEntity(this, this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(1.0D, 1.0D, 1.0D));
195            double d0 = 0.0D;
196            EntityLiving entityliving = this.getThrower();
197
198            for (int j = 0; j < list.size(); ++j)
199            {
200                Entity entity1 = (Entity)list.get(j);
201
202                if (entity1.canBeCollidedWith() && (entity1 != entityliving || this.ticksInAir >= 5))
203                {
204                    float f = 0.3F;
205                    AxisAlignedBB axisalignedbb = entity1.boundingBox.expand((double)f, (double)f, (double)f);
206                    MovingObjectPosition movingobjectposition1 = axisalignedbb.calculateIntercept(vec3, vec31);
207
208                    if (movingobjectposition1 != null)
209                    {
210                        double d1 = vec3.distanceTo(movingobjectposition1.hitVec);
211
212                        if (d1 < d0 || d0 == 0.0D)
213                        {
214                            entity = entity1;
215                            d0 = d1;
216                        }
217                    }
218                }
219            }
220
221            if (entity != null)
222            {
223                movingobjectposition = new MovingObjectPosition(entity);
224            }
225        }
226
227        if (movingobjectposition != null)
228        {
229            if (movingobjectposition.typeOfHit == EnumMovingObjectType.TILE && this.worldObj.getBlockId(movingobjectposition.blockX, movingobjectposition.blockY, movingobjectposition.blockZ) == Block.portal.blockID)
230            {
231                this.setInPortal();
232            }
233            else
234            {
235                this.onImpact(movingobjectposition);
236            }
237        }
238
239        this.posX += this.motionX;
240        this.posY += this.motionY;
241        this.posZ += this.motionZ;
242        float f1 = MathHelper.sqrt_double(this.motionX * this.motionX + this.motionZ * this.motionZ);
243        this.rotationYaw = (float)(Math.atan2(this.motionX, this.motionZ) * 180.0D / Math.PI);
244
245        for (this.rotationPitch = (float)(Math.atan2(this.motionY, (double)f1) * 180.0D / Math.PI); this.rotationPitch - this.prevRotationPitch < -180.0F; this.prevRotationPitch -= 360.0F)
246        {
247            ;
248        }
249
250        while (this.rotationPitch - this.prevRotationPitch >= 180.0F)
251        {
252            this.prevRotationPitch += 360.0F;
253        }
254
255        while (this.rotationYaw - this.prevRotationYaw < -180.0F)
256        {
257            this.prevRotationYaw -= 360.0F;
258        }
259
260        while (this.rotationYaw - this.prevRotationYaw >= 180.0F)
261        {
262            this.prevRotationYaw += 360.0F;
263        }
264
265        this.rotationPitch = this.prevRotationPitch + (this.rotationPitch - this.prevRotationPitch) * 0.2F;
266        this.rotationYaw = this.prevRotationYaw + (this.rotationYaw - this.prevRotationYaw) * 0.2F;
267        float f2 = 0.99F;
268        float f3 = this.getGravityVelocity();
269
270        if (this.isInWater())
271        {
272            for (int k = 0; k < 4; ++k)
273            {
274                float f4 = 0.25F;
275                this.worldObj.spawnParticle("bubble", this.posX - this.motionX * (double)f4, this.posY - this.motionY * (double)f4, this.posZ - this.motionZ * (double)f4, this.motionX, this.motionY, this.motionZ);
276            }
277
278            f2 = 0.8F;
279        }
280
281        this.motionX *= (double)f2;
282        this.motionY *= (double)f2;
283        this.motionZ *= (double)f2;
284        this.motionY -= (double)f3;
285        this.setPosition(this.posX, this.posY, this.posZ);
286    }
287
288    /**
289     * Gets the amount of gravity to apply to the thrown entity with each tick.
290     */
291    protected float getGravityVelocity()
292    {
293        return 0.03F;
294    }
295
296    /**
297     * Called when this EntityThrowable hits a block or entity.
298     */
299    protected abstract void onImpact(MovingObjectPosition movingobjectposition);
300
301    /**
302     * (abstract) Protected helper method to write subclass entity data to NBT.
303     */
304    public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
305    {
306        par1NBTTagCompound.setShort("xTile", (short)this.xTile);
307        par1NBTTagCompound.setShort("yTile", (short)this.yTile);
308        par1NBTTagCompound.setShort("zTile", (short)this.zTile);
309        par1NBTTagCompound.setByte("inTile", (byte)this.inTile);
310        par1NBTTagCompound.setByte("shake", (byte)this.throwableShake);
311        par1NBTTagCompound.setByte("inGround", (byte)(this.inGround ? 1 : 0));
312
313        if ((this.throwerName == null || this.throwerName.length() == 0) && this.thrower != null && this.thrower instanceof EntityPlayer)
314        {
315            this.throwerName = this.thrower.getEntityName();
316        }
317
318        par1NBTTagCompound.setString("ownerName", this.throwerName == null ? "" : this.throwerName);
319    }
320
321    /**
322     * (abstract) Protected helper method to read subclass entity data from NBT.
323     */
324    public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
325    {
326        this.xTile = par1NBTTagCompound.getShort("xTile");
327        this.yTile = par1NBTTagCompound.getShort("yTile");
328        this.zTile = par1NBTTagCompound.getShort("zTile");
329        this.inTile = par1NBTTagCompound.getByte("inTile") & 255;
330        this.throwableShake = par1NBTTagCompound.getByte("shake") & 255;
331        this.inGround = par1NBTTagCompound.getByte("inGround") == 1;
332        this.throwerName = par1NBTTagCompound.getString("ownerName");
333
334        if (this.throwerName != null && this.throwerName.length() == 0)
335        {
336            this.throwerName = null;
337        }
338    }
339
340    @SideOnly(Side.CLIENT)
341    public float getShadowSize()
342    {
343        return 0.0F;
344    }
345
346    public EntityLiving getThrower()
347    {
348        if (this.thrower == null && this.throwerName != null && this.throwerName.length() > 0)
349        {
350            this.thrower = this.worldObj.getPlayerEntityByName(this.throwerName);
351        }
352
353        return this.thrower;
354    }
355}