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}