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 }