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 }