001 package net.minecraft.entity.boss; 002 003 import cpw.mods.fml.relauncher.Side; 004 import cpw.mods.fml.relauncher.SideOnly; 005 import java.util.List; 006 import net.minecraft.block.Block; 007 import net.minecraft.command.IEntitySelector; 008 import net.minecraft.entity.Entity; 009 import net.minecraft.entity.EntityLiving; 010 import net.minecraft.entity.EnumCreatureAttribute; 011 import net.minecraft.entity.IRangedAttackMob; 012 import net.minecraft.entity.ai.EntityAIArrowAttack; 013 import net.minecraft.entity.ai.EntityAIHurtByTarget; 014 import net.minecraft.entity.ai.EntityAILookIdle; 015 import net.minecraft.entity.ai.EntityAINearestAttackableTarget; 016 import net.minecraft.entity.ai.EntityAISwimming; 017 import net.minecraft.entity.ai.EntityAIWander; 018 import net.minecraft.entity.ai.EntityAIWatchClosest; 019 import net.minecraft.entity.monster.EntityMob; 020 import net.minecraft.entity.player.EntityPlayer; 021 import net.minecraft.entity.projectile.EntityArrow; 022 import net.minecraft.entity.projectile.EntityWitherSkull; 023 import net.minecraft.item.Item; 024 import net.minecraft.nbt.NBTTagCompound; 025 import net.minecraft.potion.PotionEffect; 026 import net.minecraft.util.DamageSource; 027 import net.minecraft.util.MathHelper; 028 import net.minecraft.world.World; 029 030 public class EntityWither extends EntityMob implements IBossDisplayData, IRangedAttackMob 031 { 032 private float[] field_82220_d = new float[2]; 033 private float[] field_82221_e = new float[2]; 034 private float[] field_82217_f = new float[2]; 035 private float[] field_82218_g = new float[2]; 036 private int[] field_82223_h = new int[2]; 037 private int[] field_82224_i = new int[2]; 038 private int field_82222_j; 039 040 /** Selector used to determine the entities a wither boss should attack. */ 041 private static final IEntitySelector attackEntitySelector = new EntityWitherAttackFilter(); 042 043 public EntityWither(World par1World) 044 { 045 super(par1World); 046 this.setEntityHealth(this.getMaxHealth()); 047 this.texture = "/mob/wither.png"; 048 this.setSize(0.9F, 4.0F); 049 this.isImmuneToFire = true; 050 this.moveSpeed = 0.6F; 051 this.getNavigator().setCanSwim(true); 052 this.tasks.addTask(0, new EntityAISwimming(this)); 053 this.tasks.addTask(2, new EntityAIArrowAttack(this, this.moveSpeed, 40, 20.0F)); 054 this.tasks.addTask(5, new EntityAIWander(this, this.moveSpeed)); 055 this.tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F)); 056 this.tasks.addTask(7, new EntityAILookIdle(this)); 057 this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); 058 this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityLiving.class, 30.0F, 0, false, false, attackEntitySelector)); 059 this.experienceValue = 50; 060 } 061 062 protected void entityInit() 063 { 064 super.entityInit(); 065 this.dataWatcher.addObject(16, new Integer(100)); 066 this.dataWatcher.addObject(17, new Integer(0)); 067 this.dataWatcher.addObject(18, new Integer(0)); 068 this.dataWatcher.addObject(19, new Integer(0)); 069 this.dataWatcher.addObject(20, new Integer(0)); 070 } 071 072 /** 073 * (abstract) Protected helper method to write subclass entity data to NBT. 074 */ 075 public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound) 076 { 077 super.writeEntityToNBT(par1NBTTagCompound); 078 par1NBTTagCompound.setInteger("Invul", this.func_82212_n()); 079 } 080 081 /** 082 * (abstract) Protected helper method to read subclass entity data from NBT. 083 */ 084 public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound) 085 { 086 super.readEntityFromNBT(par1NBTTagCompound); 087 this.func_82215_s(par1NBTTagCompound.getInteger("Invul")); 088 this.dataWatcher.updateObject(16, Integer.valueOf(this.health)); 089 } 090 091 @SideOnly(Side.CLIENT) 092 public float getShadowSize() 093 { 094 return this.height / 8.0F; 095 } 096 097 /** 098 * Returns the sound this mob makes while it's alive. 099 */ 100 protected String getLivingSound() 101 { 102 return "mob.wither.idle"; 103 } 104 105 /** 106 * Returns the sound this mob makes when it is hurt. 107 */ 108 protected String getHurtSound() 109 { 110 return "mob.wither.hurt"; 111 } 112 113 /** 114 * Returns the sound this mob makes on death. 115 */ 116 protected String getDeathSound() 117 { 118 return "mob.wither.death"; 119 } 120 121 @SideOnly(Side.CLIENT) 122 123 /** 124 * Returns the texture's file path as a String. 125 */ 126 public String getTexture() 127 { 128 int var1 = this.func_82212_n(); 129 return var1 > 0 && (var1 > 80 || var1 / 5 % 2 != 1) ? "/mob/wither_invul.png" : "/mob/wither.png"; 130 } 131 132 /** 133 * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons 134 * use this to react to sunlight and start to burn. 135 */ 136 public void onLivingUpdate() 137 { 138 if (!this.worldObj.isRemote) 139 { 140 this.dataWatcher.updateObject(16, Integer.valueOf(this.health)); 141 } 142 143 this.motionY *= 0.6000000238418579D; 144 double var4; 145 double var6; 146 double var8; 147 148 if (!this.worldObj.isRemote && this.getWatchedTargetId(0) > 0) 149 { 150 Entity var1 = this.worldObj.getEntityByID(this.getWatchedTargetId(0)); 151 152 if (var1 != null) 153 { 154 if (this.posY < var1.posY || !this.isArmored() && this.posY < var1.posY + 5.0D) 155 { 156 if (this.motionY < 0.0D) 157 { 158 this.motionY = 0.0D; 159 } 160 161 this.motionY += (0.5D - this.motionY) * 0.6000000238418579D; 162 } 163 164 double var2 = var1.posX - this.posX; 165 var4 = var1.posZ - this.posZ; 166 var6 = var2 * var2 + var4 * var4; 167 168 if (var6 > 9.0D) 169 { 170 var8 = (double)MathHelper.sqrt_double(var6); 171 this.motionX += (var2 / var8 * 0.5D - this.motionX) * 0.6000000238418579D; 172 this.motionZ += (var4 / var8 * 0.5D - this.motionZ) * 0.6000000238418579D; 173 } 174 } 175 } 176 177 if (this.motionX * this.motionX + this.motionZ * this.motionZ > 0.05000000074505806D) 178 { 179 this.rotationYaw = (float)Math.atan2(this.motionZ, this.motionX) * (180F / (float)Math.PI) - 90.0F; 180 } 181 182 super.onLivingUpdate(); 183 int var20; 184 185 for (var20 = 0; var20 < 2; ++var20) 186 { 187 this.field_82218_g[var20] = this.field_82221_e[var20]; 188 this.field_82217_f[var20] = this.field_82220_d[var20]; 189 } 190 191 int var21; 192 193 for (var20 = 0; var20 < 2; ++var20) 194 { 195 var21 = this.getWatchedTargetId(var20 + 1); 196 Entity var3 = null; 197 198 if (var21 > 0) 199 { 200 var3 = this.worldObj.getEntityByID(var21); 201 } 202 203 if (var3 != null) 204 { 205 var4 = this.func_82214_u(var20 + 1); 206 var6 = this.func_82208_v(var20 + 1); 207 var8 = this.func_82213_w(var20 + 1); 208 double var10 = var3.posX - var4; 209 double var12 = var3.posY + (double)var3.getEyeHeight() - var6; 210 double var14 = var3.posZ - var8; 211 double var16 = (double)MathHelper.sqrt_double(var10 * var10 + var14 * var14); 212 float var18 = (float)(Math.atan2(var14, var10) * 180.0D / Math.PI) - 90.0F; 213 float var19 = (float)(-(Math.atan2(var12, var16) * 180.0D / Math.PI)); 214 this.field_82220_d[var20] = this.func_82204_b(this.field_82220_d[var20], var19, 40.0F); 215 this.field_82221_e[var20] = this.func_82204_b(this.field_82221_e[var20], var18, 10.0F); 216 } 217 else 218 { 219 this.field_82221_e[var20] = this.func_82204_b(this.field_82221_e[var20], this.renderYawOffset, 10.0F); 220 } 221 } 222 223 boolean var22 = this.isArmored(); 224 225 for (var21 = 0; var21 < 3; ++var21) 226 { 227 double var23 = this.func_82214_u(var21); 228 double var5 = this.func_82208_v(var21); 229 double var7 = this.func_82213_w(var21); 230 this.worldObj.spawnParticle("smoke", var23 + this.rand.nextGaussian() * 0.30000001192092896D, var5 + this.rand.nextGaussian() * 0.30000001192092896D, var7 + this.rand.nextGaussian() * 0.30000001192092896D, 0.0D, 0.0D, 0.0D); 231 232 if (var22 && this.worldObj.rand.nextInt(4) == 0) 233 { 234 this.worldObj.spawnParticle("mobSpell", var23 + this.rand.nextGaussian() * 0.30000001192092896D, var5 + this.rand.nextGaussian() * 0.30000001192092896D, var7 + this.rand.nextGaussian() * 0.30000001192092896D, 0.699999988079071D, 0.699999988079071D, 0.5D); 235 } 236 } 237 238 if (this.func_82212_n() > 0) 239 { 240 for (var21 = 0; var21 < 3; ++var21) 241 { 242 this.worldObj.spawnParticle("mobSpell", this.posX + this.rand.nextGaussian() * 1.0D, this.posY + (double)(this.rand.nextFloat() * 3.3F), this.posZ + this.rand.nextGaussian() * 1.0D, 0.699999988079071D, 0.699999988079071D, 0.8999999761581421D); 243 } 244 } 245 } 246 247 protected void updateAITasks() 248 { 249 int var1; 250 251 if (this.func_82212_n() > 0) 252 { 253 var1 = this.func_82212_n() - 1; 254 255 if (var1 <= 0) 256 { 257 this.worldObj.newExplosion(this, this.posX, this.posY + (double)this.getEyeHeight(), this.posZ, 7.0F, false, this.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")); 258 this.worldObj.func_82739_e(1013, (int)this.posX, (int)this.posY, (int)this.posZ, 0); 259 } 260 261 this.func_82215_s(var1); 262 263 if (this.ticksExisted % 10 == 0) 264 { 265 this.heal(10); 266 } 267 } 268 else 269 { 270 super.updateAITasks(); 271 int var13; 272 273 for (var1 = 1; var1 < 3; ++var1) 274 { 275 if (this.ticksExisted >= this.field_82223_h[var1 - 1]) 276 { 277 this.field_82223_h[var1 - 1] = this.ticksExisted + 10 + this.rand.nextInt(10); 278 279 if (this.worldObj.difficultySetting >= 2) 280 { 281 int var10001 = var1 - 1; 282 int var10003 = this.field_82224_i[var1 - 1]; 283 this.field_82224_i[var10001] = this.field_82224_i[var1 - 1] + 1; 284 285 if (var10003 > 15) 286 { 287 float var2 = 10.0F; 288 float var3 = 5.0F; 289 double var4 = MathHelper.getRandomDoubleInRange(this.rand, this.posX - (double)var2, this.posX + (double)var2); 290 double var6 = MathHelper.getRandomDoubleInRange(this.rand, this.posY - (double)var3, this.posY + (double)var3); 291 double var8 = MathHelper.getRandomDoubleInRange(this.rand, this.posZ - (double)var2, this.posZ + (double)var2); 292 this.func_82209_a(var1 + 1, var4, var6, var8, true); 293 this.field_82224_i[var1 - 1] = 0; 294 } 295 } 296 297 var13 = this.getWatchedTargetId(var1); 298 299 if (var13 > 0) 300 { 301 Entity var15 = this.worldObj.getEntityByID(var13); 302 303 if (var15 != null && var15.isEntityAlive() && this.getDistanceSqToEntity(var15) <= 900.0D && this.canEntityBeSeen(var15)) 304 { 305 this.func_82216_a(var1 + 1, (EntityLiving)var15); 306 this.field_82223_h[var1 - 1] = this.ticksExisted + 40 + this.rand.nextInt(20); 307 this.field_82224_i[var1 - 1] = 0; 308 } 309 else 310 { 311 this.func_82211_c(var1, 0); 312 } 313 } 314 else 315 { 316 List var14 = this.worldObj.selectEntitiesWithinAABB(EntityLiving.class, this.boundingBox.expand(20.0D, 8.0D, 20.0D), attackEntitySelector); 317 318 for (int var17 = 0; var17 < 10 && !var14.isEmpty(); ++var17) 319 { 320 EntityLiving var5 = (EntityLiving)var14.get(this.rand.nextInt(var14.size())); 321 322 if (var5 != this && var5.isEntityAlive() && this.canEntityBeSeen(var5)) 323 { 324 if (var5 instanceof EntityPlayer) 325 { 326 if (!((EntityPlayer)var5).capabilities.disableDamage) 327 { 328 this.func_82211_c(var1, var5.entityId); 329 } 330 } 331 else 332 { 333 this.func_82211_c(var1, var5.entityId); 334 } 335 336 break; 337 } 338 339 var14.remove(var5); 340 } 341 } 342 } 343 } 344 345 if (this.getAttackTarget() != null) 346 { 347 this.func_82211_c(0, this.getAttackTarget().entityId); 348 } 349 else 350 { 351 this.func_82211_c(0, 0); 352 } 353 354 if (this.field_82222_j > 0) 355 { 356 --this.field_82222_j; 357 358 if (this.field_82222_j == 0 && this.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")) 359 { 360 var1 = MathHelper.floor_double(this.posY); 361 var13 = MathHelper.floor_double(this.posX); 362 int var16 = MathHelper.floor_double(this.posZ); 363 boolean var19 = false; 364 365 for (int var18 = -1; var18 <= 1; ++var18) 366 { 367 for (int var20 = -1; var20 <= 1; ++var20) 368 { 369 for (int var7 = 0; var7 <= 3; ++var7) 370 { 371 int var21 = var13 + var18; 372 int var9 = var1 + var7; 373 int var10 = var16 + var20; 374 int var11 = this.worldObj.getBlockId(var21, var9, var10); 375 376 if (var11 > 0 && var11 != Block.bedrock.blockID && var11 != Block.endPortal.blockID && var11 != Block.endPortalFrame.blockID) 377 { 378 int var12 = this.worldObj.getBlockMetadata(var21, var9, var10); 379 this.worldObj.playAuxSFX(2001, var21, var9, var10, var11 + (var12 << 12)); 380 Block.blocksList[var11].dropBlockAsItem(this.worldObj, var21, var9, var10, var12, 0); 381 this.worldObj.setBlockWithNotify(var21, var9, var10, 0); 382 var19 = true; 383 } 384 } 385 } 386 } 387 388 if (var19) 389 { 390 this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1012, (int)this.posX, (int)this.posY, (int)this.posZ, 0); 391 } 392 } 393 } 394 395 if (this.ticksExisted % 20 == 0) 396 { 397 this.heal(1); 398 } 399 } 400 } 401 402 public void func_82206_m() 403 { 404 this.func_82215_s(220); 405 this.setEntityHealth(this.getMaxHealth() / 3); 406 } 407 408 /** 409 * Sets the Entity inside a web block. 410 */ 411 public void setInWeb() {} 412 413 /** 414 * Returns the current armor value as determined by a call to InventoryPlayer.getTotalArmorValue 415 */ 416 public int getTotalArmorValue() 417 { 418 return 4; 419 } 420 421 private double func_82214_u(int par1) 422 { 423 if (par1 <= 0) 424 { 425 return this.posX; 426 } 427 else 428 { 429 float var2 = (this.renderYawOffset + (float)(180 * (par1 - 1))) / 180.0F * (float)Math.PI; 430 float var3 = MathHelper.cos(var2); 431 return this.posX + (double)var3 * 1.3D; 432 } 433 } 434 435 private double func_82208_v(int par1) 436 { 437 return par1 <= 0 ? this.posY + 3.0D : this.posY + 2.2D; 438 } 439 440 private double func_82213_w(int par1) 441 { 442 if (par1 <= 0) 443 { 444 return this.posZ; 445 } 446 else 447 { 448 float var2 = (this.renderYawOffset + (float)(180 * (par1 - 1))) / 180.0F * (float)Math.PI; 449 float var3 = MathHelper.sin(var2); 450 return this.posZ + (double)var3 * 1.3D; 451 } 452 } 453 454 private float func_82204_b(float par1, float par2, float par3) 455 { 456 float var4 = MathHelper.wrapAngleTo180_float(par2 - par1); 457 458 if (var4 > par3) 459 { 460 var4 = par3; 461 } 462 463 if (var4 < -par3) 464 { 465 var4 = -par3; 466 } 467 468 return par1 + var4; 469 } 470 471 private void func_82216_a(int par1, EntityLiving par2EntityLiving) 472 { 473 this.func_82209_a(par1, par2EntityLiving.posX, par2EntityLiving.posY + (double)par2EntityLiving.getEyeHeight() * 0.5D, par2EntityLiving.posZ, par1 == 0 && this.rand.nextFloat() < 0.001F); 474 } 475 476 private void func_82209_a(int par1, double par2, double par4, double par6, boolean par8) 477 { 478 this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1014, (int)this.posX, (int)this.posY, (int)this.posZ, 0); 479 double var9 = this.func_82214_u(par1); 480 double var11 = this.func_82208_v(par1); 481 double var13 = this.func_82213_w(par1); 482 double var15 = par2 - var9; 483 double var17 = par4 - var11; 484 double var19 = par6 - var13; 485 EntityWitherSkull var21 = new EntityWitherSkull(this.worldObj, this, var15, var17, var19); 486 487 if (par8) 488 { 489 var21.setInvulnerable(true); 490 } 491 492 var21.posY = var11; 493 var21.posX = var9; 494 var21.posZ = var13; 495 this.worldObj.spawnEntityInWorld(var21); 496 } 497 498 /** 499 * Attack the specified entity using a ranged attack. 500 */ 501 public void attackEntityWithRangedAttack(EntityLiving par1EntityLiving) 502 { 503 this.func_82216_a(0, par1EntityLiving); 504 } 505 506 /** 507 * Called when the entity is attacked. 508 */ 509 public boolean attackEntityFrom(DamageSource par1DamageSource, int par2) 510 { 511 if (this.isEntityInvulnerable()) 512 { 513 return false; 514 } 515 else if (par1DamageSource == DamageSource.drown) 516 { 517 return false; 518 } 519 else if (this.func_82212_n() > 0) 520 { 521 return false; 522 } 523 else 524 { 525 Entity var3; 526 527 if (this.isArmored()) 528 { 529 var3 = par1DamageSource.getSourceOfDamage(); 530 531 if (var3 instanceof EntityArrow) 532 { 533 return false; 534 } 535 } 536 537 var3 = par1DamageSource.getEntity(); 538 539 if (var3 != null && !(var3 instanceof EntityPlayer) && var3 instanceof EntityLiving && ((EntityLiving)var3).getCreatureAttribute() == this.getCreatureAttribute()) 540 { 541 return false; 542 } 543 else 544 { 545 if (this.field_82222_j <= 0) 546 { 547 this.field_82222_j = 20; 548 } 549 550 for (int var4 = 0; var4 < this.field_82224_i.length; ++var4) 551 { 552 this.field_82224_i[var4] += 3; 553 } 554 555 return super.attackEntityFrom(par1DamageSource, par2); 556 } 557 } 558 } 559 560 /** 561 * Drop 0-2 items of this living's type. @param par1 - Whether this entity has recently been hit by a player. @param 562 * par2 - Level of Looting used to kill this mob. 563 */ 564 protected void dropFewItems(boolean par1, int par2) 565 { 566 this.dropItem(Item.netherStar.itemID, 1); 567 } 568 569 /** 570 * Makes the entity despawn if requirements are reached 571 */ 572 protected void despawnEntity() 573 { 574 this.entityAge = 0; 575 } 576 577 @SideOnly(Side.CLIENT) 578 public int getBrightnessForRender(float par1) 579 { 580 return 15728880; 581 } 582 583 /** 584 * Returns true if other Entities should be prevented from moving through this Entity. 585 */ 586 public boolean canBeCollidedWith() 587 { 588 return !this.isDead; 589 } 590 591 /** 592 * Returns the health points of the dragon. 593 */ 594 public int getDragonHealth() 595 { 596 return this.dataWatcher.getWatchableObjectInt(16); 597 } 598 599 /** 600 * Called when the mob is falling. Calculates and applies fall damage. 601 */ 602 protected void fall(float par1) {} 603 604 /** 605 * adds a PotionEffect to the entity 606 */ 607 public void addPotionEffect(PotionEffect par1PotionEffect) {} 608 609 /** 610 * Returns true if the newer Entity AI code should be run 611 */ 612 protected boolean isAIEnabled() 613 { 614 return true; 615 } 616 617 public int getMaxHealth() 618 { 619 return 300; 620 } 621 622 @SideOnly(Side.CLIENT) 623 public float func_82207_a(int par1) 624 { 625 return this.field_82221_e[par1]; 626 } 627 628 @SideOnly(Side.CLIENT) 629 public float func_82210_r(int par1) 630 { 631 return this.field_82220_d[par1]; 632 } 633 634 public int func_82212_n() 635 { 636 return this.dataWatcher.getWatchableObjectInt(20); 637 } 638 639 public void func_82215_s(int par1) 640 { 641 this.dataWatcher.updateObject(20, Integer.valueOf(par1)); 642 } 643 644 /** 645 * Returns the target entity ID if present, or -1 if not @param par1 The target offset, should be from 0-2 646 */ 647 public int getWatchedTargetId(int par1) 648 { 649 return this.dataWatcher.getWatchableObjectInt(17 + par1); 650 } 651 652 public void func_82211_c(int par1, int par2) 653 { 654 this.dataWatcher.updateObject(17 + par1, Integer.valueOf(par2)); 655 } 656 657 /** 658 * Returns whether the wither is armored with its boss armor or not by checking whether its health is below half of 659 * its maximum. 660 */ 661 public boolean isArmored() 662 { 663 return this.getDragonHealth() <= this.getMaxHealth() / 2; 664 } 665 666 /** 667 * Get this Entity's EnumCreatureAttribute 668 */ 669 public EnumCreatureAttribute getCreatureAttribute() 670 { 671 return EnumCreatureAttribute.UNDEAD; 672 } 673 }