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 b0 = this.dataWatcher.getWatchableObjectByte(16); 080 this.texture = b0 == 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 d0 = this.waypointX - this.posX; 093 double d1 = this.waypointY - this.posY; 094 double d2 = this.waypointZ - this.posZ; 095 double d3 = d0 * d0 + d1 * d1 + d2 * d2; 096 097 if (d3 < 1.0D || d3 > 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 d3 = (double)MathHelper.sqrt_double(d3); 108 109 if (this.isCourseTraversable(this.waypointX, this.waypointY, this.waypointZ, d3)) 110 { 111 this.motionX += d0 / d3 * 0.1D; 112 this.motionY += d1 / d3 * 0.1D; 113 this.motionZ += d2 / d3 * 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 d4 = 64.0D; 139 140 if (this.targetedEntity != null && this.targetedEntity.getDistanceSqToEntity(this) < d4 * d4) 141 { 142 double d5 = this.targetedEntity.posX - this.posX; 143 double d6 = this.targetedEntity.boundingBox.minY + (double)(this.targetedEntity.height / 2.0F) - (this.posY + (double)(this.height / 2.0F)); 144 double d7 = this.targetedEntity.posZ - this.posZ; 145 this.renderYawOffset = this.rotationYaw = -((float)Math.atan2(d5, d7)) * 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 entitylargefireball = new EntityLargeFireball(this.worldObj, this, d5, d6, d7); 160 entitylargefireball.field_92057_e = this.explosionStrength; 161 double d8 = 4.0D; 162 Vec3 vec3 = this.getLook(1.0F); 163 entitylargefireball.posX = this.posX + vec3.xCoord * d8; 164 entitylargefireball.posY = this.posY + (double)(this.height / 2.0F) + 0.5D; 165 entitylargefireball.posZ = this.posZ + vec3.zCoord * d8; 166 this.worldObj.spawnEntityInWorld(entitylargefireball); 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 b0 = this.dataWatcher.getWatchableObjectByte(16); 188 byte b1 = (byte)(this.attackCounter > 10 ? 1 : 0); 189 190 if (b0 != b1) 191 { 192 this.dataWatcher.updateObject(16, Byte.valueOf(b1)); 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 d4 = (this.waypointX - this.posX) / par7; 203 double d5 = (this.waypointY - this.posY) / par7; 204 double d6 = (this.waypointZ - this.posZ) / par7; 205 AxisAlignedBB axisalignedbb = this.boundingBox.copy(); 206 207 for (int i = 1; (double)i < par7; ++i) 208 { 209 axisalignedbb.offset(d4, d5, d6); 210 211 if (!this.worldObj.getCollidingBoundingBoxes(this, axisalignedbb).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 j = this.rand.nextInt(2) + this.rand.nextInt(1 + par2); 259 int k; 260 261 for (k = 0; k < j; ++k) 262 { 263 this.dropItem(Item.ghastTear.itemID, 1); 264 } 265 266 j = this.rand.nextInt(3) + this.rand.nextInt(1 + par2); 267 268 for (k = 0; k < j; ++k) 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}