001 package net.minecraft.entity.monster;
002
003 import net.minecraft.block.Block;
004 import net.minecraft.entity.Entity;
005 import net.minecraft.entity.player.EntityPlayer;
006 import net.minecraft.item.Item;
007 import net.minecraft.item.ItemStack;
008 import net.minecraft.nbt.NBTTagCompound;
009 import net.minecraft.util.DamageSource;
010 import net.minecraft.util.EntityDamageSourceIndirect;
011 import net.minecraft.util.MathHelper;
012 import net.minecraft.util.Vec3;
013 import net.minecraft.world.World;
014
015 public class EntityEnderman extends EntityMob
016 {
017 public static boolean[] carriableBlocks = new boolean[256];
018
019 /**
020 * Counter to delay the teleportation of an enderman towards the currently attacked target
021 */
022 private int teleportDelay = 0;
023 private int field_70826_g = 0;
024
025 public EntityEnderman(World par1World)
026 {
027 super(par1World);
028 this.texture = "/mob/enderman.png";
029 this.moveSpeed = 0.2F;
030 this.setSize(0.6F, 2.9F);
031 this.stepHeight = 1.0F;
032 }
033
034 public int getMaxHealth()
035 {
036 return 40;
037 }
038
039 protected void entityInit()
040 {
041 super.entityInit();
042 this.dataWatcher.addObject(16, new Byte((byte)0));
043 this.dataWatcher.addObject(17, new Byte((byte)0));
044 this.dataWatcher.addObject(18, new Byte((byte)0));
045 }
046
047 /**
048 * (abstract) Protected helper method to write subclass entity data to NBT.
049 */
050 public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
051 {
052 super.writeEntityToNBT(par1NBTTagCompound);
053 par1NBTTagCompound.setShort("carried", (short)this.getCarried());
054 par1NBTTagCompound.setShort("carriedData", (short)this.getCarryingData());
055 }
056
057 /**
058 * (abstract) Protected helper method to read subclass entity data from NBT.
059 */
060 public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
061 {
062 super.readEntityFromNBT(par1NBTTagCompound);
063 this.setCarried(par1NBTTagCompound.getShort("carried"));
064 this.setCarryingData(par1NBTTagCompound.getShort("carriedData"));
065 }
066
067 /**
068 * Finds the closest player within 16 blocks to attack, or null if this Entity isn't interested in attacking
069 * (Animals, Spiders at day, peaceful PigZombies).
070 */
071 protected Entity findPlayerToAttack()
072 {
073 EntityPlayer var1 = this.worldObj.getClosestVulnerablePlayerToEntity(this, 64.0D);
074
075 if (var1 != null)
076 {
077 if (this.shouldAttackPlayer(var1))
078 {
079 if (this.field_70826_g == 0)
080 {
081 this.worldObj.playSoundAtEntity(var1, "mob.endermen.stare", 1.0F, 1.0F);
082 }
083
084 if (this.field_70826_g++ == 5)
085 {
086 this.field_70826_g = 0;
087 this.func_70819_e(true);
088 return var1;
089 }
090 }
091 else
092 {
093 this.field_70826_g = 0;
094 }
095 }
096
097 return null;
098 }
099
100 /**
101 * Checks to see if this enderman should be attacking this player
102 */
103 private boolean shouldAttackPlayer(EntityPlayer par1EntityPlayer)
104 {
105 ItemStack var2 = par1EntityPlayer.inventory.armorInventory[3];
106
107 if (var2 != null && var2.itemID == Block.pumpkin.blockID)
108 {
109 return false;
110 }
111 else
112 {
113 Vec3 var3 = par1EntityPlayer.getLook(1.0F).normalize();
114 Vec3 var4 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX - par1EntityPlayer.posX, this.boundingBox.minY + (double)(this.height / 2.0F) - (par1EntityPlayer.posY + (double)par1EntityPlayer.getEyeHeight()), this.posZ - par1EntityPlayer.posZ);
115 double var5 = var4.lengthVector();
116 var4 = var4.normalize();
117 double var7 = var3.dotProduct(var4);
118 return var7 > 1.0D - 0.025D / var5 ? par1EntityPlayer.canEntityBeSeen(this) : false;
119 }
120 }
121
122 /**
123 * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
124 * use this to react to sunlight and start to burn.
125 */
126 public void onLivingUpdate()
127 {
128 if (this.isWet())
129 {
130 this.attackEntityFrom(DamageSource.drown, 1);
131 }
132
133 this.moveSpeed = this.entityToAttack != null ? 6.5F : 0.3F;
134 int var1;
135
136 if (!this.worldObj.isRemote && this.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing"))
137 {
138 int var2;
139 int var3;
140 int var4;
141
142 if (this.getCarried() == 0)
143 {
144 if (this.rand.nextInt(20) == 0)
145 {
146 var1 = MathHelper.floor_double(this.posX - 2.0D + this.rand.nextDouble() * 4.0D);
147 var2 = MathHelper.floor_double(this.posY + this.rand.nextDouble() * 3.0D);
148 var3 = MathHelper.floor_double(this.posZ - 2.0D + this.rand.nextDouble() * 4.0D);
149 var4 = this.worldObj.getBlockId(var1, var2, var3);
150
151 if (carriableBlocks[var4])
152 {
153 this.setCarried(this.worldObj.getBlockId(var1, var2, var3));
154 this.setCarryingData(this.worldObj.getBlockMetadata(var1, var2, var3));
155 this.worldObj.setBlockWithNotify(var1, var2, var3, 0);
156 }
157 }
158 }
159 else if (this.rand.nextInt(2000) == 0)
160 {
161 var1 = MathHelper.floor_double(this.posX - 1.0D + this.rand.nextDouble() * 2.0D);
162 var2 = MathHelper.floor_double(this.posY + this.rand.nextDouble() * 2.0D);
163 var3 = MathHelper.floor_double(this.posZ - 1.0D + this.rand.nextDouble() * 2.0D);
164 var4 = this.worldObj.getBlockId(var1, var2, var3);
165 int var5 = this.worldObj.getBlockId(var1, var2 - 1, var3);
166
167 if (var4 == 0 && var5 > 0 && Block.blocksList[var5].renderAsNormalBlock())
168 {
169 this.worldObj.setBlockAndMetadataWithNotify(var1, var2, var3, this.getCarried(), this.getCarryingData());
170 this.setCarried(0);
171 }
172 }
173 }
174
175 for (var1 = 0; var1 < 2; ++var1)
176 {
177 this.worldObj.spawnParticle("portal", this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height - 0.25D, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, (this.rand.nextDouble() - 0.5D) * 2.0D, -this.rand.nextDouble(), (this.rand.nextDouble() - 0.5D) * 2.0D);
178 }
179
180 if (this.worldObj.isDaytime() && !this.worldObj.isRemote)
181 {
182 float var6 = this.getBrightness(1.0F);
183
184 if (var6 > 0.5F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ)) && this.rand.nextFloat() * 30.0F < (var6 - 0.4F) * 2.0F)
185 {
186 this.entityToAttack = null;
187 this.func_70819_e(false);
188 this.teleportRandomly();
189 }
190 }
191
192 if (this.isWet() || this.isBurning())
193 {
194 this.entityToAttack = null;
195 this.func_70819_e(false);
196 this.teleportRandomly();
197 }
198
199 this.isJumping = false;
200
201 if (this.entityToAttack != null)
202 {
203 this.faceEntity(this.entityToAttack, 100.0F, 100.0F);
204 }
205
206 if (!this.worldObj.isRemote && this.isEntityAlive())
207 {
208 if (this.entityToAttack != null)
209 {
210 if (this.entityToAttack instanceof EntityPlayer && this.shouldAttackPlayer((EntityPlayer)this.entityToAttack))
211 {
212 this.moveStrafing = this.moveForward = 0.0F;
213 this.moveSpeed = 0.0F;
214
215 if (this.entityToAttack.getDistanceSqToEntity(this) < 16.0D)
216 {
217 this.teleportRandomly();
218 }
219
220 this.teleportDelay = 0;
221 }
222 else if (this.entityToAttack.getDistanceSqToEntity(this) > 256.0D && this.teleportDelay++ >= 30 && this.teleportToEntity(this.entityToAttack))
223 {
224 this.teleportDelay = 0;
225 }
226 }
227 else
228 {
229 this.func_70819_e(false);
230 this.teleportDelay = 0;
231 }
232 }
233
234 super.onLivingUpdate();
235 }
236
237 /**
238 * Teleport the enderman to a random nearby position
239 */
240 protected boolean teleportRandomly()
241 {
242 double var1 = this.posX + (this.rand.nextDouble() - 0.5D) * 64.0D;
243 double var3 = this.posY + (double)(this.rand.nextInt(64) - 32);
244 double var5 = this.posZ + (this.rand.nextDouble() - 0.5D) * 64.0D;
245 return this.teleportTo(var1, var3, var5);
246 }
247
248 /**
249 * Teleport the enderman to another entity
250 */
251 protected boolean teleportToEntity(Entity par1Entity)
252 {
253 Vec3 var2 = this.worldObj.getWorldVec3Pool().getVecFromPool(this.posX - par1Entity.posX, this.boundingBox.minY + (double)(this.height / 2.0F) - par1Entity.posY + (double)par1Entity.getEyeHeight(), this.posZ - par1Entity.posZ);
254 var2 = var2.normalize();
255 double var3 = 16.0D;
256 double var5 = this.posX + (this.rand.nextDouble() - 0.5D) * 8.0D - var2.xCoord * var3;
257 double var7 = this.posY + (double)(this.rand.nextInt(16) - 8) - var2.yCoord * var3;
258 double var9 = this.posZ + (this.rand.nextDouble() - 0.5D) * 8.0D - var2.zCoord * var3;
259 return this.teleportTo(var5, var7, var9);
260 }
261
262 /**
263 * Teleport the enderman
264 */
265 protected boolean teleportTo(double par1, double par3, double par5)
266 {
267 double var7 = this.posX;
268 double var9 = this.posY;
269 double var11 = this.posZ;
270 this.posX = par1;
271 this.posY = par3;
272 this.posZ = par5;
273 boolean var13 = false;
274 int var14 = MathHelper.floor_double(this.posX);
275 int var15 = MathHelper.floor_double(this.posY);
276 int var16 = MathHelper.floor_double(this.posZ);
277 int var18;
278
279 if (this.worldObj.blockExists(var14, var15, var16))
280 {
281 boolean var17 = false;
282
283 while (!var17 && var15 > 0)
284 {
285 var18 = this.worldObj.getBlockId(var14, var15 - 1, var16);
286
287 if (var18 != 0 && Block.blocksList[var18].blockMaterial.blocksMovement())
288 {
289 var17 = true;
290 }
291 else
292 {
293 --this.posY;
294 --var15;
295 }
296 }
297
298 if (var17)
299 {
300 this.setPosition(this.posX, this.posY, this.posZ);
301
302 if (this.worldObj.getCollidingBoundingBoxes(this, this.boundingBox).isEmpty() && !this.worldObj.isAnyLiquid(this.boundingBox))
303 {
304 var13 = true;
305 }
306 }
307 }
308
309 if (!var13)
310 {
311 this.setPosition(var7, var9, var11);
312 return false;
313 }
314 else
315 {
316 short var30 = 128;
317
318 for (var18 = 0; var18 < var30; ++var18)
319 {
320 double var19 = (double)var18 / ((double)var30 - 1.0D);
321 float var21 = (this.rand.nextFloat() - 0.5F) * 0.2F;
322 float var22 = (this.rand.nextFloat() - 0.5F) * 0.2F;
323 float var23 = (this.rand.nextFloat() - 0.5F) * 0.2F;
324 double var24 = var7 + (this.posX - var7) * var19 + (this.rand.nextDouble() - 0.5D) * (double)this.width * 2.0D;
325 double var26 = var9 + (this.posY - var9) * var19 + this.rand.nextDouble() * (double)this.height;
326 double var28 = var11 + (this.posZ - var11) * var19 + (this.rand.nextDouble() - 0.5D) * (double)this.width * 2.0D;
327 this.worldObj.spawnParticle("portal", var24, var26, var28, (double)var21, (double)var22, (double)var23);
328 }
329
330 this.worldObj.playSoundEffect(var7, var9, var11, "mob.endermen.portal", 1.0F, 1.0F);
331 this.playSound("mob.endermen.portal", 1.0F, 1.0F);
332 return true;
333 }
334 }
335
336 /**
337 * Returns the sound this mob makes while it's alive.
338 */
339 protected String getLivingSound()
340 {
341 return this.func_70823_r() ? "mob.endermen.scream" : "mob.endermen.idle";
342 }
343
344 /**
345 * Returns the sound this mob makes when it is hurt.
346 */
347 protected String getHurtSound()
348 {
349 return "mob.endermen.hit";
350 }
351
352 /**
353 * Returns the sound this mob makes on death.
354 */
355 protected String getDeathSound()
356 {
357 return "mob.endermen.death";
358 }
359
360 /**
361 * Returns the item ID for the item the mob drops on death.
362 */
363 protected int getDropItemId()
364 {
365 return Item.enderPearl.itemID;
366 }
367
368 /**
369 * Drop 0-2 items of this living's type. @param par1 - Whether this entity has recently been hit by a player. @param
370 * par2 - Level of Looting used to kill this mob.
371 */
372 protected void dropFewItems(boolean par1, int par2)
373 {
374 int var3 = this.getDropItemId();
375
376 if (var3 > 0)
377 {
378 int var4 = this.rand.nextInt(2 + par2);
379
380 for (int var5 = 0; var5 < var4; ++var5)
381 {
382 this.dropItem(var3, 1);
383 }
384 }
385 }
386
387 /**
388 * Set the id of the block an enderman carries
389 */
390 public void setCarried(int par1)
391 {
392 this.dataWatcher.updateObject(16, Byte.valueOf((byte)(par1 & 255)));
393 }
394
395 /**
396 * Get the id of the block an enderman carries
397 */
398 public int getCarried()
399 {
400 return this.dataWatcher.getWatchableObjectByte(16);
401 }
402
403 /**
404 * Set the metadata of the block an enderman carries
405 */
406 public void setCarryingData(int par1)
407 {
408 this.dataWatcher.updateObject(17, Byte.valueOf((byte)(par1 & 255)));
409 }
410
411 /**
412 * Get the metadata of the block an enderman carries
413 */
414 public int getCarryingData()
415 {
416 return this.dataWatcher.getWatchableObjectByte(17);
417 }
418
419 /**
420 * Called when the entity is attacked.
421 */
422 public boolean attackEntityFrom(DamageSource par1DamageSource, int par2)
423 {
424 if (this.isEntityInvulnerable())
425 {
426 return false;
427 }
428 else
429 {
430 this.func_70819_e(true);
431
432 if (par1DamageSource instanceof EntityDamageSourceIndirect)
433 {
434 for (int var3 = 0; var3 < 64; ++var3)
435 {
436 if (this.teleportRandomly())
437 {
438 return true;
439 }
440 }
441
442 return false;
443 }
444 else
445 {
446 return super.attackEntityFrom(par1DamageSource, par2);
447 }
448 }
449 }
450
451 public boolean func_70823_r()
452 {
453 return this.dataWatcher.getWatchableObjectByte(18) > 0;
454 }
455
456 public void func_70819_e(boolean par1)
457 {
458 this.dataWatcher.updateObject(18, Byte.valueOf((byte)(par1 ? 1 : 0)));
459 }
460
461 /**
462 * Returns the amount of damage a mob should deal.
463 */
464 public int getAttackStrength(Entity par1Entity)
465 {
466 return 7;
467 }
468
469 static
470 {
471 carriableBlocks[Block.grass.blockID] = true;
472 carriableBlocks[Block.dirt.blockID] = true;
473 carriableBlocks[Block.sand.blockID] = true;
474 carriableBlocks[Block.gravel.blockID] = true;
475 carriableBlocks[Block.plantYellow.blockID] = true;
476 carriableBlocks[Block.plantRed.blockID] = true;
477 carriableBlocks[Block.mushroomBrown.blockID] = true;
478 carriableBlocks[Block.mushroomRed.blockID] = true;
479 carriableBlocks[Block.tnt.blockID] = true;
480 carriableBlocks[Block.cactus.blockID] = true;
481 carriableBlocks[Block.blockClay.blockID] = true;
482 carriableBlocks[Block.pumpkin.blockID] = true;
483 carriableBlocks[Block.melon.blockID] = true;
484 carriableBlocks[Block.mycelium.blockID] = true;
485 }
486 }