001    package net.minecraft.entity.monster;
002    
003    import net.minecraft.entity.Entity;
004    import net.minecraft.entity.EntityFlying;
005    import net.minecraft.entity.player.EntityPlayer;
006    import net.minecraft.entity.projectile.EntityLargeFireball;
007    import net.minecraft.item.Item;
008    import net.minecraft.nbt.NBTTagCompound;
009    import net.minecraft.stats.AchievementList;
010    import net.minecraft.util.AxisAlignedBB;
011    import net.minecraft.util.DamageSource;
012    import net.minecraft.util.MathHelper;
013    import net.minecraft.util.Vec3;
014    import net.minecraft.world.World;
015    
016    public class EntityGhast extends EntityFlying implements IMob
017    {
018        public int courseChangeCooldown = 0;
019        public double waypointX;
020        public double waypointY;
021        public double waypointZ;
022        private Entity targetedEntity = null;
023    
024        /** Cooldown time between target loss and new target aquirement. */
025        private int aggroCooldown = 0;
026        public int prevAttackCounter = 0;
027        public int attackCounter = 0;
028        private int field_92009_j = 1;
029    
030        public EntityGhast(World par1World)
031        {
032            super(par1World);
033            this.texture = "/mob/ghast.png";
034            this.setSize(4.0F, 4.0F);
035            this.isImmuneToFire = true;
036            this.experienceValue = 5;
037        }
038    
039        /**
040         * Called when the entity is attacked.
041         */
042        public boolean attackEntityFrom(DamageSource par1DamageSource, int par2)
043        {
044            if (this.isEntityInvulnerable())
045            {
046                return false;
047            }
048            else if ("fireball".equals(par1DamageSource.getDamageType()) && par1DamageSource.getEntity() instanceof EntityPlayer)
049            {
050                super.attackEntityFrom(par1DamageSource, 1000);
051                ((EntityPlayer)par1DamageSource.getEntity()).triggerAchievement(AchievementList.ghast);
052                return true;
053            }
054            else
055            {
056                return super.attackEntityFrom(par1DamageSource, par2);
057            }
058        }
059    
060        protected void entityInit()
061        {
062            super.entityInit();
063            this.dataWatcher.addObject(16, Byte.valueOf((byte)0));
064        }
065    
066        public int getMaxHealth()
067        {
068            return 10;
069        }
070    
071        /**
072         * Called to update the entity's position/logic.
073         */
074        public void onUpdate()
075        {
076            super.onUpdate();
077            byte var1 = this.dataWatcher.getWatchableObjectByte(16);
078            this.texture = var1 == 1 ? "/mob/ghast_fire.png" : "/mob/ghast.png";
079        }
080    
081        protected void updateEntityActionState()
082        {
083            if (!this.worldObj.isRemote && this.worldObj.difficultySetting == 0)
084            {
085                this.setDead();
086            }
087    
088            this.despawnEntity();
089            this.prevAttackCounter = this.attackCounter;
090            double var1 = this.waypointX - this.posX;
091            double var3 = this.waypointY - this.posY;
092            double var5 = this.waypointZ - this.posZ;
093            double var7 = var1 * var1 + var3 * var3 + var5 * var5;
094    
095            if (var7 < 1.0D || var7 > 3600.0D)
096            {
097                this.waypointX = this.posX + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
098                this.waypointY = this.posY + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
099                this.waypointZ = this.posZ + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
100            }
101    
102            if (this.courseChangeCooldown-- <= 0)
103            {
104                this.courseChangeCooldown += this.rand.nextInt(5) + 2;
105                var7 = (double)MathHelper.sqrt_double(var7);
106    
107                if (this.isCourseTraversable(this.waypointX, this.waypointY, this.waypointZ, var7))
108                {
109                    this.motionX += var1 / var7 * 0.1D;
110                    this.motionY += var3 / var7 * 0.1D;
111                    this.motionZ += var5 / var7 * 0.1D;
112                }
113                else
114                {
115                    this.waypointX = this.posX;
116                    this.waypointY = this.posY;
117                    this.waypointZ = this.posZ;
118                }
119            }
120    
121            if (this.targetedEntity != null && this.targetedEntity.isDead)
122            {
123                this.targetedEntity = null;
124            }
125    
126            if (this.targetedEntity == null || this.aggroCooldown-- <= 0)
127            {
128                this.targetedEntity = this.worldObj.getClosestVulnerablePlayerToEntity(this, 100.0D);
129    
130                if (this.targetedEntity != null)
131                {
132                    this.aggroCooldown = 20;
133                }
134            }
135    
136            double var9 = 64.0D;
137    
138            if (this.targetedEntity != null && this.targetedEntity.getDistanceSqToEntity(this) < var9 * var9)
139            {
140                double var11 = this.targetedEntity.posX - this.posX;
141                double var13 = this.targetedEntity.boundingBox.minY + (double)(this.targetedEntity.height / 2.0F) - (this.posY + (double)(this.height / 2.0F));
142                double var15 = this.targetedEntity.posZ - this.posZ;
143                this.renderYawOffset = this.rotationYaw = -((float)Math.atan2(var11, var15)) * 180.0F / (float)Math.PI;
144    
145                if (this.canEntityBeSeen(this.targetedEntity))
146                {
147                    if (this.attackCounter == 10)
148                    {
149                        this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1007, (int)this.posX, (int)this.posY, (int)this.posZ, 0);
150                    }
151    
152                    ++this.attackCounter;
153    
154                    if (this.attackCounter == 20)
155                    {
156                        this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1008, (int)this.posX, (int)this.posY, (int)this.posZ, 0);
157                        EntityLargeFireball var17 = new EntityLargeFireball(this.worldObj, this, var11, var13, var15);
158                        var17.field_92012_e = this.field_92009_j;
159                        double var18 = 4.0D;
160                        Vec3 var20 = this.getLook(1.0F);
161                        var17.posX = this.posX + var20.xCoord * var18;
162                        var17.posY = this.posY + (double)(this.height / 2.0F) + 0.5D;
163                        var17.posZ = this.posZ + var20.zCoord * var18;
164                        this.worldObj.spawnEntityInWorld(var17);
165                        this.attackCounter = -40;
166                    }
167                }
168                else if (this.attackCounter > 0)
169                {
170                    --this.attackCounter;
171                }
172            }
173            else
174            {
175                this.renderYawOffset = this.rotationYaw = -((float)Math.atan2(this.motionX, this.motionZ)) * 180.0F / (float)Math.PI;
176    
177                if (this.attackCounter > 0)
178                {
179                    --this.attackCounter;
180                }
181            }
182    
183            if (!this.worldObj.isRemote)
184            {
185                byte var21 = this.dataWatcher.getWatchableObjectByte(16);
186                byte var12 = (byte)(this.attackCounter > 10 ? 1 : 0);
187    
188                if (var21 != var12)
189                {
190                    this.dataWatcher.updateObject(16, Byte.valueOf(var12));
191                }
192            }
193        }
194    
195        /**
196         * True if the ghast has an unobstructed line of travel to the waypoint.
197         */
198        private boolean isCourseTraversable(double par1, double par3, double par5, double par7)
199        {
200            double var9 = (this.waypointX - this.posX) / par7;
201            double var11 = (this.waypointY - this.posY) / par7;
202            double var13 = (this.waypointZ - this.posZ) / par7;
203            AxisAlignedBB var15 = this.boundingBox.copy();
204    
205            for (int var16 = 1; (double)var16 < par7; ++var16)
206            {
207                var15.offset(var9, var11, var13);
208    
209                if (!this.worldObj.getCollidingBoundingBoxes(this, var15).isEmpty())
210                {
211                    return false;
212                }
213            }
214    
215            return true;
216        }
217    
218        /**
219         * Returns the sound this mob makes while it's alive.
220         */
221        protected String getLivingSound()
222        {
223            return "mob.ghast.moan";
224        }
225    
226        /**
227         * Returns the sound this mob makes when it is hurt.
228         */
229        protected String getHurtSound()
230        {
231            return "mob.ghast.scream";
232        }
233    
234        /**
235         * Returns the sound this mob makes on death.
236         */
237        protected String getDeathSound()
238        {
239            return "mob.ghast.death";
240        }
241    
242        /**
243         * Returns the item ID for the item the mob drops on death.
244         */
245        protected int getDropItemId()
246        {
247            return Item.gunpowder.itemID;
248        }
249    
250        /**
251         * Drop 0-2 items of this living's type. @param par1 - Whether this entity has recently been hit by a player. @param
252         * par2 - Level of Looting used to kill this mob.
253         */
254        protected void dropFewItems(boolean par1, int par2)
255        {
256            int var3 = this.rand.nextInt(2) + this.rand.nextInt(1 + par2);
257            int var4;
258    
259            for (var4 = 0; var4 < var3; ++var4)
260            {
261                this.dropItem(Item.ghastTear.itemID, 1);
262            }
263    
264            var3 = this.rand.nextInt(3) + this.rand.nextInt(1 + par2);
265    
266            for (var4 = 0; var4 < var3; ++var4)
267            {
268                this.dropItem(Item.gunpowder.itemID, 1);
269            }
270        }
271    
272        /**
273         * Returns the volume for the sounds this mob makes.
274         */
275        protected float getSoundVolume()
276        {
277            return 10.0F;
278        }
279    
280        /**
281         * Checks if the entity's current position is a valid location to spawn this entity.
282         */
283        public boolean getCanSpawnHere()
284        {
285            return this.rand.nextInt(20) == 0 && super.getCanSpawnHere() && this.worldObj.difficultySetting > 0;
286        }
287    
288        /**
289         * Will return how many at most can spawn in a chunk at once.
290         */
291        public int getMaxSpawnedInChunk()
292        {
293            return 1;
294        }
295    
296        /**
297         * (abstract) Protected helper method to write subclass entity data to NBT.
298         */
299        public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
300        {
301            super.writeEntityToNBT(par1NBTTagCompound);
302            par1NBTTagCompound.setInteger("ExplosionPower", this.field_92009_j);
303        }
304    
305        /**
306         * (abstract) Protected helper method to read subclass entity data from NBT.
307         */
308        public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
309        {
310            super.readEntityFromNBT(par1NBTTagCompound);
311    
312            if (par1NBTTagCompound.hasKey("ExplosionPower"))
313            {
314                this.field_92009_j = par1NBTTagCompound.getInteger("ExplosionPower");
315            }
316        }
317    }