    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}