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