001 package net.minecraft.entity.monster; 002 003 import cpw.mods.fml.relauncher.Side; 004 import cpw.mods.fml.relauncher.SideOnly; 005 import java.util.Calendar; 006 import net.minecraft.block.Block; 007 import net.minecraft.entity.Entity; 008 import net.minecraft.entity.EntityLiving; 009 import net.minecraft.entity.EnumCreatureAttribute; 010 import net.minecraft.entity.ai.EntityAIAttackOnCollide; 011 import net.minecraft.entity.ai.EntityAIBreakDoor; 012 import net.minecraft.entity.ai.EntityAIHurtByTarget; 013 import net.minecraft.entity.ai.EntityAILookIdle; 014 import net.minecraft.entity.ai.EntityAIMoveThroughVillage; 015 import net.minecraft.entity.ai.EntityAIMoveTwardsRestriction; 016 import net.minecraft.entity.ai.EntityAINearestAttackableTarget; 017 import net.minecraft.entity.ai.EntityAISwimming; 018 import net.minecraft.entity.ai.EntityAIWander; 019 import net.minecraft.entity.ai.EntityAIWatchClosest; 020 import net.minecraft.entity.passive.EntityVillager; 021 import net.minecraft.entity.player.EntityPlayer; 022 import net.minecraft.item.Item; 023 import net.minecraft.item.ItemStack; 024 import net.minecraft.nbt.NBTTagCompound; 025 import net.minecraft.potion.Potion; 026 import net.minecraft.potion.PotionEffect; 027 import net.minecraft.util.MathHelper; 028 import net.minecraft.world.World; 029 030 public class EntityZombie extends EntityMob 031 { 032 /** 033 * Ticker used to determine the time remaining for this zombie to convert into a villager when cured. 034 */ 035 private int conversionTime = 0; 036 037 public EntityZombie(World par1World) 038 { 039 super(par1World); 040 this.texture = "/mob/zombie.png"; 041 this.moveSpeed = 0.23F; 042 this.getNavigator().setBreakDoors(true); 043 this.tasks.addTask(0, new EntityAISwimming(this)); 044 this.tasks.addTask(1, new EntityAIBreakDoor(this)); 045 this.tasks.addTask(2, new EntityAIAttackOnCollide(this, EntityPlayer.class, this.moveSpeed, false)); 046 this.tasks.addTask(3, new EntityAIAttackOnCollide(this, EntityVillager.class, this.moveSpeed, true)); 047 this.tasks.addTask(4, new EntityAIMoveTwardsRestriction(this, this.moveSpeed)); 048 this.tasks.addTask(5, new EntityAIMoveThroughVillage(this, this.moveSpeed, false)); 049 this.tasks.addTask(6, new EntityAIWander(this, this.moveSpeed)); 050 this.tasks.addTask(7, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F)); 051 this.tasks.addTask(7, new EntityAILookIdle(this)); 052 this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); 053 this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityPlayer.class, 16.0F, 0, true)); 054 this.targetTasks.addTask(2, new EntityAINearestAttackableTarget(this, EntityVillager.class, 16.0F, 0, false)); 055 } 056 057 /** 058 * This method returns a value to be applied directly to entity speed, this factor is less than 1 when a slowdown 059 * potion effect is applied, more than 1 when a haste potion effect is applied and 2 for fleeing entities. 060 */ 061 public float getSpeedModifier() 062 { 063 return super.getSpeedModifier() * (this.isChild() ? 1.5F : 1.0F); 064 } 065 066 protected void entityInit() 067 { 068 super.entityInit(); 069 this.getDataWatcher().addObject(12, Byte.valueOf((byte)0)); 070 this.getDataWatcher().addObject(13, Byte.valueOf((byte)0)); 071 this.getDataWatcher().addObject(14, Byte.valueOf((byte)0)); 072 } 073 074 @SideOnly(Side.CLIENT) 075 076 /** 077 * Returns the texture's file path as a String. 078 */ 079 public String getTexture() 080 { 081 return this.isVillager() ? "/mob/zombie_villager.png" : "/mob/zombie.png"; 082 } 083 084 public int getMaxHealth() 085 { 086 return 20; 087 } 088 089 /** 090 * Returns the current armor value as determined by a call to InventoryPlayer.getTotalArmorValue 091 */ 092 public int getTotalArmorValue() 093 { 094 int var1 = super.getTotalArmorValue() + 2; 095 096 if (var1 > 20) 097 { 098 var1 = 20; 099 } 100 101 return var1; 102 } 103 104 /** 105 * Returns true if the newer Entity AI code should be run 106 */ 107 protected boolean isAIEnabled() 108 { 109 return true; 110 } 111 112 /** 113 * If Animal, checks if the age timer is negative 114 */ 115 public boolean isChild() 116 { 117 return this.getDataWatcher().getWatchableObjectByte(12) == 1; 118 } 119 120 /** 121 * Set whether this zombie is a child. 122 */ 123 public void setChild(boolean par1) 124 { 125 this.getDataWatcher().updateObject(12, Byte.valueOf((byte)1)); 126 } 127 128 /** 129 * Return whether this zombie is a villager. 130 */ 131 public boolean isVillager() 132 { 133 return this.getDataWatcher().getWatchableObjectByte(13) == 1; 134 } 135 136 /** 137 * Set whether this zombie is a villager. 138 */ 139 public void setVillager(boolean par1) 140 { 141 this.getDataWatcher().updateObject(13, Byte.valueOf((byte)(par1 ? 1 : 0))); 142 } 143 144 /** 145 * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons 146 * use this to react to sunlight and start to burn. 147 */ 148 public void onLivingUpdate() 149 { 150 if (this.worldObj.isDaytime() && !this.worldObj.isRemote && !this.isChild()) 151 { 152 float var1 = this.getBrightness(1.0F); 153 154 if (var1 > 0.5F && this.rand.nextFloat() * 30.0F < (var1 - 0.4F) * 2.0F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ))) 155 { 156 boolean var2 = true; 157 ItemStack var3 = this.getCurrentItemOrArmor(4); 158 159 if (var3 != null) 160 { 161 if (var3.isItemStackDamageable()) 162 { 163 var3.setItemDamage(var3.getItemDamageForDisplay() + this.rand.nextInt(2)); 164 165 if (var3.getItemDamageForDisplay() >= var3.getMaxDamage()) 166 { 167 this.renderBrokenItemStack(var3); 168 this.setCurrentItemOrArmor(4, (ItemStack)null); 169 } 170 } 171 172 var2 = false; 173 } 174 175 if (var2) 176 { 177 this.setFire(8); 178 } 179 } 180 } 181 182 super.onLivingUpdate(); 183 } 184 185 /** 186 * Called to update the entity's position/logic. 187 */ 188 public void onUpdate() 189 { 190 if (!this.worldObj.isRemote && this.func_82230_o()) 191 { 192 int var1 = this.getConversionTimeBoost(); 193 this.conversionTime -= var1; 194 195 if (this.conversionTime <= 0) 196 { 197 this.convertToVillager(); 198 } 199 } 200 201 super.onUpdate(); 202 } 203 204 /** 205 * Returns the amount of damage a mob should deal. 206 */ 207 public int getAttackStrength(Entity par1Entity) 208 { 209 ItemStack var2 = this.getHeldItem(); 210 int var3 = 4; 211 212 if (var2 != null) 213 { 214 var3 += var2.getDamageVsEntity(this); 215 } 216 217 return var3; 218 } 219 220 /** 221 * Returns the sound this mob makes while it's alive. 222 */ 223 protected String getLivingSound() 224 { 225 return "mob.zombie.say"; 226 } 227 228 /** 229 * Returns the sound this mob makes when it is hurt. 230 */ 231 protected String getHurtSound() 232 { 233 return "mob.zombie.hurt"; 234 } 235 236 /** 237 * Returns the sound this mob makes on death. 238 */ 239 protected String getDeathSound() 240 { 241 return "mob.zombie.death"; 242 } 243 244 /** 245 * Plays step sound at given x, y, z for the entity 246 */ 247 protected void playStepSound(int par1, int par2, int par3, int par4) 248 { 249 this.playSound("mob.zombie.step", 0.15F, 1.0F); 250 } 251 252 /** 253 * Returns the item ID for the item the mob drops on death. 254 */ 255 protected int getDropItemId() 256 { 257 return Item.rottenFlesh.itemID; 258 } 259 260 /** 261 * Get this Entity's EnumCreatureAttribute 262 */ 263 public EnumCreatureAttribute getCreatureAttribute() 264 { 265 return EnumCreatureAttribute.UNDEAD; 266 } 267 268 protected void dropRareDrop(int par1) 269 { 270 switch (this.rand.nextInt(3)) 271 { 272 case 0: 273 this.dropItem(Item.ingotIron.itemID, 1); 274 break; 275 case 1: 276 this.dropItem(Item.carrot.itemID, 1); 277 break; 278 case 2: 279 this.dropItem(Item.potato.itemID, 1); 280 } 281 } 282 283 protected void func_82164_bB() 284 { 285 super.func_82164_bB(); 286 287 if (this.rand.nextFloat() < (this.worldObj.difficultySetting == 3 ? 0.05F : 0.01F)) 288 { 289 int var1 = this.rand.nextInt(3); 290 291 if (var1 == 0) 292 { 293 this.setCurrentItemOrArmor(0, new ItemStack(Item.swordSteel)); 294 } 295 else 296 { 297 this.setCurrentItemOrArmor(0, new ItemStack(Item.shovelSteel)); 298 } 299 } 300 } 301 302 /** 303 * (abstract) Protected helper method to write subclass entity data to NBT. 304 */ 305 public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound) 306 { 307 super.writeEntityToNBT(par1NBTTagCompound); 308 309 if (this.isChild()) 310 { 311 par1NBTTagCompound.setBoolean("IsBaby", true); 312 } 313 314 if (this.isVillager()) 315 { 316 par1NBTTagCompound.setBoolean("IsVillager", true); 317 } 318 319 par1NBTTagCompound.setInteger("ConversionTime", this.func_82230_o() ? this.conversionTime : -1); 320 } 321 322 /** 323 * (abstract) Protected helper method to read subclass entity data from NBT. 324 */ 325 public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound) 326 { 327 super.readEntityFromNBT(par1NBTTagCompound); 328 329 if (par1NBTTagCompound.getBoolean("IsBaby")) 330 { 331 this.setChild(true); 332 } 333 334 if (par1NBTTagCompound.getBoolean("IsVillager")) 335 { 336 this.setVillager(true); 337 } 338 339 if (par1NBTTagCompound.hasKey("ConversionTime") && par1NBTTagCompound.getInteger("ConversionTime") > -1) 340 { 341 this.startConversion(par1NBTTagCompound.getInteger("ConversionTime")); 342 } 343 } 344 345 /** 346 * This method gets called when the entity kills another one. 347 */ 348 public void onKillEntity(EntityLiving par1EntityLiving) 349 { 350 super.onKillEntity(par1EntityLiving); 351 352 if (this.worldObj.difficultySetting >= 2 && par1EntityLiving instanceof EntityVillager) 353 { 354 if (this.worldObj.difficultySetting == 2 && this.rand.nextBoolean()) 355 { 356 return; 357 } 358 359 EntityZombie var2 = new EntityZombie(this.worldObj); 360 var2.func_82149_j(par1EntityLiving); 361 this.worldObj.setEntityDead(par1EntityLiving); 362 var2.initCreature(); 363 var2.setVillager(true); 364 365 if (par1EntityLiving.isChild()) 366 { 367 var2.setChild(true); 368 } 369 370 this.worldObj.spawnEntityInWorld(var2); 371 this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1016, (int)this.posX, (int)this.posY, (int)this.posZ, 0); 372 } 373 } 374 375 /** 376 * Initialize this creature. 377 */ 378 public void initCreature() 379 { 380 this.canPickUpLoot = this.rand.nextFloat() < pickUpLootProability[this.worldObj.difficultySetting]; 381 382 if (this.worldObj.rand.nextFloat() < 0.05F) 383 { 384 this.setVillager(true); 385 } 386 387 this.func_82164_bB(); 388 this.func_82162_bC(); 389 390 if (this.getCurrentItemOrArmor(4) == null) 391 { 392 Calendar var1 = this.worldObj.getCurrentDate(); 393 394 if (var1.get(2) + 1 == 10 && var1.get(5) == 31 && this.rand.nextFloat() < 0.25F) 395 { 396 this.setCurrentItemOrArmor(4, new ItemStack(this.rand.nextFloat() < 0.1F ? Block.pumpkinLantern : Block.pumpkin)); 397 this.equipmentDropChances[4] = 0.0F; 398 } 399 } 400 } 401 402 /** 403 * Called when a player interacts with a mob. e.g. gets milk from a cow, gets into the saddle on a pig. 404 */ 405 public boolean interact(EntityPlayer par1EntityPlayer) 406 { 407 ItemStack var2 = par1EntityPlayer.getCurrentEquippedItem(); 408 409 if (var2 != null && var2.getItem() == Item.appleGold && var2.getItemDamage() == 0 && this.isVillager() && this.isPotionActive(Potion.weakness)) 410 { 411 if (!par1EntityPlayer.capabilities.isCreativeMode) 412 { 413 --var2.stackSize; 414 } 415 416 if (var2.stackSize <= 0) 417 { 418 par1EntityPlayer.inventory.setInventorySlotContents(par1EntityPlayer.inventory.currentItem, (ItemStack)null); 419 } 420 421 if (!this.worldObj.isRemote) 422 { 423 this.startConversion(this.rand.nextInt(2401) + 3600); 424 } 425 426 return true; 427 } 428 else 429 { 430 return false; 431 } 432 } 433 434 /** 435 * Starts converting this zombie into a villager. The zombie converts into a villager after the specified time in 436 * ticks. 437 */ 438 protected void startConversion(int par1) 439 { 440 this.conversionTime = par1; 441 this.getDataWatcher().updateObject(14, Byte.valueOf((byte)1)); 442 this.removePotionEffect(Potion.weakness.id); 443 this.addPotionEffect(new PotionEffect(Potion.damageBoost.id, par1, Math.min(this.worldObj.difficultySetting - 1, 0))); 444 this.worldObj.setEntityState(this, (byte)16); 445 } 446 447 @SideOnly(Side.CLIENT) 448 public void handleHealthUpdate(byte par1) 449 { 450 if (par1 == 16) 451 { 452 this.worldObj.playSound(this.posX + 0.5D, this.posY + 0.5D, this.posZ + 0.5D, "mob.zombie.remedy", 1.0F + this.rand.nextFloat(), this.rand.nextFloat() * 0.7F + 0.3F, false); 453 } 454 else 455 { 456 super.handleHealthUpdate(par1); 457 } 458 } 459 460 public boolean func_82230_o() 461 { 462 return this.getDataWatcher().getWatchableObjectByte(14) == 1; 463 } 464 465 /** 466 * Convert this zombie into a villager. 467 */ 468 protected void convertToVillager() 469 { 470 EntityVillager var1 = new EntityVillager(this.worldObj); 471 var1.func_82149_j(this); 472 var1.initCreature(); 473 var1.func_82187_q(); 474 475 if (this.isChild()) 476 { 477 var1.setGrowingAge(-24000); 478 } 479 480 this.worldObj.setEntityDead(this); 481 this.worldObj.spawnEntityInWorld(var1); 482 var1.addPotionEffect(new PotionEffect(Potion.confusion.id, 200, 0)); 483 this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1017, (int)this.posX, (int)this.posY, (int)this.posZ, 0); 484 } 485 486 /** 487 * Return the amount of time decremented from conversionTime every tick. 488 */ 489 protected int getConversionTimeBoost() 490 { 491 int var1 = 1; 492 493 if (this.rand.nextFloat() < 0.01F) 494 { 495 int var2 = 0; 496 497 for (int var3 = (int)this.posX - 4; var3 < (int)this.posX + 4 && var2 < 14; ++var3) 498 { 499 for (int var4 = (int)this.posY - 4; var4 < (int)this.posY + 4 && var2 < 14; ++var4) 500 { 501 for (int var5 = (int)this.posZ - 4; var5 < (int)this.posZ + 4 && var2 < 14; ++var5) 502 { 503 int var6 = this.worldObj.getBlockId(var3, var4, var5); 504 505 if (var6 == Block.fenceIron.blockID || var6 == Block.bed.blockID) 506 { 507 if (this.rand.nextFloat() < 0.3F) 508 { 509 ++var1; 510 } 511 512 ++var2; 513 } 514 } 515 } 516 } 517 } 518 519 return var1; 520 } 521 }