    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.enchantment.Enchantment;
    008import net.minecraft.enchantment.EnchantmentHelper;
    009import net.minecraft.entity.Entity;
    010import net.minecraft.entity.EntityLiving;
    011import net.minecraft.entity.EnumCreatureAttribute;
    012import net.minecraft.entity.IRangedAttackMob;
    013import net.minecraft.entity.ai.EntityAIArrowAttack;
    014import net.minecraft.entity.ai.EntityAIAttackOnCollide;
    015import net.minecraft.entity.ai.EntityAIFleeSun;
    016import net.minecraft.entity.ai.EntityAIHurtByTarget;
    017import net.minecraft.entity.ai.EntityAILookIdle;
    018import net.minecraft.entity.ai.EntityAINearestAttackableTarget;
    019import net.minecraft.entity.ai.EntityAIRestrictSun;
    020import net.minecraft.entity.ai.EntityAISwimming;
    021import net.minecraft.entity.ai.EntityAIWander;
    022import net.minecraft.entity.ai.EntityAIWatchClosest;
    023import net.minecraft.entity.player.EntityPlayer;
    024import net.minecraft.entity.projectile.EntityArrow;
    025import net.minecraft.item.Item;
    026import net.minecraft.item.ItemStack;
    027import net.minecraft.nbt.NBTTagCompound;
    028import net.minecraft.potion.Potion;
    029import net.minecraft.potion.PotionEffect;
    030import net.minecraft.stats.AchievementList;
    031import net.minecraft.util.DamageSource;
    032import net.minecraft.util.MathHelper;
    033import net.minecraft.world.World;
    034import net.minecraft.world.WorldProviderHell;
    035
    036public class EntitySkeleton extends EntityMob implements IRangedAttackMob
    037{
    038    private EntityAIArrowAttack aiArrowAttack = new EntityAIArrowAttack(this, 0.25F, 20, 60, 15.0F);
    039    private EntityAIAttackOnCollide aiAttackOnCollide = new EntityAIAttackOnCollide(this, EntityPlayer.class, 0.31F, false);
    040
    041    public EntitySkeleton(World par1World)
    042    {
    043        super(par1World);
    044        this.texture = "/mob/skeleton.png";
    045        this.moveSpeed = 0.25F;
    046        this.tasks.addTask(1, new EntityAISwimming(this));
    047        this.tasks.addTask(2, new EntityAIRestrictSun(this));
    048        this.tasks.addTask(3, new EntityAIFleeSun(this, this.moveSpeed));
    049        this.tasks.addTask(5, new EntityAIWander(this, this.moveSpeed));
    050        this.tasks.addTask(6, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
    051        this.tasks.addTask(6, 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
    055        if (par1World != null && !par1World.isRemote)
    056        {
    057            this.setCombatTask();
    058        }
    059    }
    060
    061    protected void entityInit()
    062    {
    063        super.entityInit();
    064        this.dataWatcher.addObject(13, new Byte((byte)0));
    065    }
    066
    067    /**
    068     * Returns true if the newer Entity AI code should be run
    069     */
    070    public boolean isAIEnabled()
    071    {
    072        return true;
    073    }
    074
    075    public int getMaxHealth()
    076    {
    077        return 20;
    078    }
    079
    080    /**
    081     * Returns the sound this mob makes while it's alive.
    082     */
    083    protected String getLivingSound()
    084    {
    085        return "mob.skeleton.say";
    086    }
    087
    088    /**
    089     * Returns the sound this mob makes when it is hurt.
    090     */
    091    protected String getHurtSound()
    092    {
    093        return "mob.skeleton.hurt";
    094    }
    095
    096    /**
    097     * Returns the sound this mob makes on death.
    098     */
    099    protected String getDeathSound()
    100    {
    101        return "mob.skeleton.death";
    102    }
    103
    104    /**
    105     * Plays step sound at given x, y, z for the entity
    106     */
    107    protected void playStepSound(int par1, int par2, int par3, int par4)
    108    {
    109        this.playSound("mob.skeleton.step", 0.15F, 1.0F);
    110    }
    111
    112    public boolean attackEntityAsMob(Entity par1Entity)
    113    {
    114        if (super.attackEntityAsMob(par1Entity))
    115        {
    116            if (this.getSkeletonType() == 1 && par1Entity instanceof EntityLiving)
    117            {
    118                ((EntityLiving)par1Entity).addPotionEffect(new PotionEffect(Potion.wither.id, 200));
    119            }
    120
    121            return true;
    122        }
    123        else
    124        {
    125            return false;
    126        }
    127    }
    128
    129    /**
    130     * Returns the amount of damage a mob should deal.
    131     */
    132    public int getAttackStrength(Entity par1Entity)
    133    {
    134        if (this.getSkeletonType() == 1)
    135        {
    136            ItemStack itemstack = this.getHeldItem();
    137            int i = 4;
    138
    139            if (itemstack != null)
    140            {
    141                i += itemstack.getDamageVsEntity(this);
    142            }
    143
    144            return i;
    145        }
    146        else
    147        {
    148            return super.getAttackStrength(par1Entity);
    149        }
    150    }
    151
    152    /**
    153     * Get this Entity's EnumCreatureAttribute
    154     */
    155    public EnumCreatureAttribute getCreatureAttribute()
    156    {
    157        return EnumCreatureAttribute.UNDEAD;
    158    }
    159
    160    /**
    161     * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
    162     * use this to react to sunlight and start to burn.
    163     */
    164    public void onLivingUpdate()
    165    {
    166        if (this.worldObj.isDaytime() && !this.worldObj.isRemote)
    167        {
    168            float f = this.getBrightness(1.0F);
    169
    170            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)))
    171            {
    172                boolean flag = true;
    173                ItemStack itemstack = this.getCurrentItemOrArmor(4);
    174
    175                if (itemstack != null)
    176                {
    177                    if (itemstack.isItemStackDamageable())
    178                    {
    179                        itemstack.setItemDamage(itemstack.getItemDamageForDisplay() + this.rand.nextInt(2));
    180
    181                        if (itemstack.getItemDamageForDisplay() >= itemstack.getMaxDamage())
    182                        {
    183                            this.renderBrokenItemStack(itemstack);
    184                            this.setCurrentItemOrArmor(4, (ItemStack)null);
    185                        }
    186                    }
    187
    188                    flag = false;
    189                }
    190
    191                if (flag)
    192                {
    193                    this.setFire(8);
    194                }
    195            }
    196        }
    197
    198        if (this.worldObj.isRemote && this.getSkeletonType() == 1)
    199        {
    200            this.setSize(0.72F, 2.34F);
    201        }
    202
    203        super.onLivingUpdate();
    204    }
    205
    206    /**
    207     * Called when the mob's health reaches 0.
    208     */
    209    public void onDeath(DamageSource par1DamageSource)
    210    {
    211        super.onDeath(par1DamageSource);
    212
    213        if (par1DamageSource.getSourceOfDamage() instanceof EntityArrow && par1DamageSource.getEntity() instanceof EntityPlayer)
    214        {
    215            EntityPlayer entityplayer = (EntityPlayer)par1DamageSource.getEntity();
    216            double d0 = entityplayer.posX - this.posX;
    217            double d1 = entityplayer.posZ - this.posZ;
    218
    219            if (d0 * d0 + d1 * d1 >= 2500.0D)
    220            {
    221                entityplayer.triggerAchievement(AchievementList.snipeSkeleton);
    222            }
    223        }
    224    }
    225
    226    /**
    227     * Returns the item ID for the item the mob drops on death.
    228     */
    229    protected int getDropItemId()
    230    {
    231        return Item.arrow.itemID;
    232    }
    233
    234    /**
    235     * Drop 0-2 items of this living's type. @param par1 - Whether this entity has recently been hit by a player. @param
    236     * par2 - Level of Looting used to kill this mob.
    237     */
    238    protected void dropFewItems(boolean par1, int par2)
    239    {
    240        int j;
    241        int k;
    242
    243        if (this.getSkeletonType() == 1)
    244        {
    245            j = this.rand.nextInt(3 + par2) - 1;
    246
    247            for (k = 0; k < j; ++k)
    248            {
    249                this.dropItem(Item.coal.itemID, 1);
    250            }
    251        }
    252        else
    253        {
    254            j = this.rand.nextInt(3 + par2);
    255
    256            for (k = 0; k < j; ++k)
    257            {
    258                this.dropItem(Item.arrow.itemID, 1);
    259            }
    260        }
    261
    262        j = this.rand.nextInt(3 + par2);
    263
    264        for (k = 0; k < j; ++k)
    265        {
    266            this.dropItem(Item.bone.itemID, 1);
    267        }
    268    }
    269
    270    protected void dropRareDrop(int par1)
    271    {
    272        if (this.getSkeletonType() == 1)
    273        {
    274            this.entityDropItem(new ItemStack(Item.skull.itemID, 1, 1), 0.0F);
    275        }
    276    }
    277
    278    /**
    279     * Makes entity wear random armor based on difficulty
    280     */
    281    protected void addRandomArmor()
    282    {
    283        super.addRandomArmor();
    284        this.setCurrentItemOrArmor(0, new ItemStack(Item.bow));
    285    }
    286
    287    @SideOnly(Side.CLIENT)
    288
    289    /**
    290     * Returns the texture's file path as a String.
    291     */
    292    public String getTexture()
    293    {
    294        return this.getSkeletonType() == 1 ? "/mob/skeleton_wither.png" : super.getTexture();
    295    }
    296
    297    /**
    298     * Initialize this creature.
    299     */
    300    public void initCreature()
    301    {
    302        if (this.worldObj.provider instanceof WorldProviderHell && this.getRNG().nextInt(5) > 0)
    303        {
    304            this.tasks.addTask(4, this.aiAttackOnCollide);
    305            this.setSkeletonType(1);
    306            this.setCurrentItemOrArmor(0, new ItemStack(Item.swordStone));
    307        }
    308        else
    309        {
    310            this.tasks.addTask(4, this.aiArrowAttack);
    311            this.addRandomArmor();
    312            this.func_82162_bC();
    313        }
    314
    315        this.func_98053_h(this.rand.nextFloat() < pickUpLootProability[this.worldObj.difficultySetting]);
    316
    317        if (this.getCurrentItemOrArmor(4) == null)
    318        {
    319            Calendar calendar = this.worldObj.getCurrentDate();
    320
    321            if (calendar.get(2) + 1 == 10 && calendar.get(5) == 31 && this.rand.nextFloat() < 0.25F)
    322            {
    323                this.setCurrentItemOrArmor(4, new ItemStack(this.rand.nextFloat() < 0.1F ? Block.pumpkinLantern : Block.pumpkin));
    324                this.equipmentDropChances[4] = 0.0F;
    325            }
    326        }
    327    }
    328
    329    /**
    330     * sets this entity's combat AI.
    331     */
    332    public void setCombatTask()
    333    {
    334        this.tasks.removeTask(this.aiAttackOnCollide);
    335        this.tasks.removeTask(this.aiArrowAttack);
    336        ItemStack itemstack = this.getHeldItem();
    337
    338        if (itemstack != null && itemstack.itemID == Item.bow.itemID)
    339        {
    340            this.tasks.addTask(4, this.aiArrowAttack);
    341        }
    342        else
    343        {
    344            this.tasks.addTask(4, this.aiAttackOnCollide);
    345        }
    346    }
    347
    348    /**
    349     * Attack the specified entity using a ranged attack.
    350     */
    351    public void attackEntityWithRangedAttack(EntityLiving par1EntityLiving, float par2)
    352    {
    353        EntityArrow entityarrow = new EntityArrow(this.worldObj, this, par1EntityLiving, 1.6F, (float)(14 - this.worldObj.difficultySetting * 4));
    354        int i = EnchantmentHelper.getEnchantmentLevel(Enchantment.power.effectId, this.getHeldItem());
    355        int j = EnchantmentHelper.getEnchantmentLevel(Enchantment.punch.effectId, this.getHeldItem());
    356        entityarrow.setDamage((double)(par2 * 2.0F) + this.rand.nextGaussian() * 0.25D + (double)((float)this.worldObj.difficultySetting * 0.11F));
    357
    358        if (i > 0)
    359        {
    360            entityarrow.setDamage(entityarrow.getDamage() + (double)i * 0.5D + 0.5D);
    361        }
    362
    363        if (j > 0)
    364        {
    365            entityarrow.setKnockbackStrength(j);
    366        }
    367
    368        if (EnchantmentHelper.getEnchantmentLevel(Enchantment.flame.effectId, this.getHeldItem()) > 0 || this.getSkeletonType() == 1)
    369        {
    370            entityarrow.setFire(100);
    371        }
    372
    373        this.playSound("random.bow", 1.0F, 1.0F / (this.getRNG().nextFloat() * 0.4F + 0.8F));
    374        this.worldObj.spawnEntityInWorld(entityarrow);
    375    }
    376
    377    /**
    378     * Return this skeleton's type.
    379     */
    380    public int getSkeletonType()
    381    {
    382        return this.dataWatcher.getWatchableObjectByte(13);
    383    }
    384
    385    /**
    386     * Set this skeleton's type.
    387     */
    388    public void setSkeletonType(int par1)
    389    {
    390        this.dataWatcher.updateObject(13, Byte.valueOf((byte)par1));
    391        this.isImmuneToFire = par1 == 1;
    392
    393        if (par1 == 1)
    394        {
    395            this.setSize(0.72F, 2.34F);
    396        }
    397        else
    398        {
    399            this.setSize(0.6F, 1.8F);
    400        }
    401    }
    402
    403    /**
    404     * (abstract) Protected helper method to read subclass entity data from NBT.
    405     */
    406    public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
    407    {
    408        super.readEntityFromNBT(par1NBTTagCompound);
    409
    410        if (par1NBTTagCompound.hasKey("SkeletonType"))
    411        {
    412            byte b0 = par1NBTTagCompound.getByte("SkeletonType");
    413            this.setSkeletonType(b0);
    414        }
    415
    416        this.setCombatTask();
    417    }
    418
    419    /**
    420     * (abstract) Protected helper method to write subclass entity data to NBT.
    421     */
    422    public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
    423    {
    424        super.writeEntityToNBT(par1NBTTagCompound);
    425        par1NBTTagCompound.setByte("SkeletonType", (byte)this.getSkeletonType());
    426    }
    427
    428    /**
    429     * Sets the held item, or an armor slot. Slot 0 is held item. Slot 1-4 is armor. Params: Item, slot
    430     */
    431    public void setCurrentItemOrArmor(int par1, ItemStack par2ItemStack)
    432    {
    433        super.setCurrentItemOrArmor(par1, par2ItemStack);
    434
    435        if (!this.worldObj.isRemote && par1 == 0)
    436        {
    437            this.setCombatTask();
    438        }
    439    }
    440}