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