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 }