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