    001package net.minecraft.entity.monster;
    002
    003import net.minecraft.entity.Entity;
    004import net.minecraft.entity.EntityFlying;
    005import net.minecraft.entity.player.EntityPlayer;
    006import net.minecraft.entity.projectile.EntityLargeFireball;
    007import net.minecraft.item.Item;
    008import net.minecraft.nbt.NBTTagCompound;
    009import net.minecraft.stats.AchievementList;
    010import net.minecraft.util.AxisAlignedBB;
    011import net.minecraft.util.DamageSource;
    012import net.minecraft.util.MathHelper;
    013import net.minecraft.util.Vec3;
    014import net.minecraft.world.World;
    015
    016public class EntityGhast extends EntityFlying implements IMob
    017{
    018    public int courseChangeCooldown = 0;
    019    public double waypointX;
    020    public double waypointY;
    021    public double waypointZ;
    022    private Entity targetedEntity = null;
    023
    024    /** Cooldown time between target loss and new target aquirement. */
    025    private int aggroCooldown = 0;
    026    public int prevAttackCounter = 0;
    027    public int attackCounter = 0;
    028
    029    /** The explosion radius of spawned fireballs. */
    030    private int explosionStrength = 1;
    031
    032    public EntityGhast(World par1World)
    033    {
    034        super(par1World);
    035        this.texture = "/mob/ghast.png";
    036        this.setSize(4.0F, 4.0F);
    037        this.isImmuneToFire = true;
    038        this.experienceValue = 5;
    039    }
    040
    041    /**
    042     * Called when the entity is attacked.
    043     */
    044    public boolean attackEntityFrom(DamageSource par1DamageSource, int par2)
    045    {
    046        if (this.isEntityInvulnerable())
    047        {
    048            return false;
    049        }
    050        else if ("fireball".equals(par1DamageSource.getDamageType()) && par1DamageSource.getEntity() instanceof EntityPlayer)
    051        {
    052            super.attackEntityFrom(par1DamageSource, 1000);
    053            ((EntityPlayer)par1DamageSource.getEntity()).triggerAchievement(AchievementList.ghast);
    054            return true;
    055        }
    056        else
    057        {
    058            return super.attackEntityFrom(par1DamageSource, par2);
    059        }
    060    }
    061
    062    protected void entityInit()
    063    {
    064        super.entityInit();
    065        this.dataWatcher.addObject(16, Byte.valueOf((byte)0));
    066    }
    067
    068    public int getMaxHealth()
    069    {
    070        return 10;
    071    }
    072
    073    /**
    074     * Called to update the entity's position/logic.
    075     */
    076    public void onUpdate()
    077    {
    078        super.onUpdate();
    079        byte b0 = this.dataWatcher.getWatchableObjectByte(16);
    080        this.texture = b0 == 1 ? "/mob/ghast_fire.png" : "/mob/ghast.png";
    081    }
    082
    083    protected void updateEntityActionState()
    084    {
    085        if (!this.worldObj.isRemote && this.worldObj.difficultySetting == 0)
    086        {
    087            this.setDead();
    088        }
    089
    090        this.despawnEntity();
    091        this.prevAttackCounter = this.attackCounter;
    092        double d0 = this.waypointX - this.posX;
    093        double d1 = this.waypointY - this.posY;
    094        double d2 = this.waypointZ - this.posZ;
    095        double d3 = d0 * d0 + d1 * d1 + d2 * d2;
    096
    097        if (d3 < 1.0D || d3 > 3600.0D)
    098        {
    099            this.waypointX = this.posX + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
    100            this.waypointY = this.posY + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
    101            this.waypointZ = this.posZ + (double)((this.rand.nextFloat() * 2.0F - 1.0F) * 16.0F);
    102        }
    103
    104        if (this.courseChangeCooldown-- <= 0)
    105        {
    106            this.courseChangeCooldown += this.rand.nextInt(5) + 2;
    107            d3 = (double)MathHelper.sqrt_double(d3);
    108
    109            if (this.isCourseTraversable(this.waypointX, this.waypointY, this.waypointZ, d3))
    110            {
    111                this.motionX += d0 / d3 * 0.1D;
    112                this.motionY += d1 / d3 * 0.1D;
    113                this.motionZ += d2 / d3 * 0.1D;
    114            }
    115            else
    116            {
    117                this.waypointX = this.posX;
    118                this.waypointY = this.posY;
    119                this.waypointZ = this.posZ;
    120            }
    121        }
    122
    123        if (this.targetedEntity != null && this.targetedEntity.isDead)
    124        {
    125            this.targetedEntity = null;
    126        }
    127
    128        if (this.targetedEntity == null || this.aggroCooldown-- <= 0)
    129        {
    130            this.targetedEntity = this.worldObj.getClosestVulnerablePlayerToEntity(this, 100.0D);
    131
    132            if (this.targetedEntity != null)
    133            {
    134                this.aggroCooldown = 20;
    135            }
    136        }
    137
    138        double d4 = 64.0D;
    139
    140        if (this.targetedEntity != null && this.targetedEntity.getDistanceSqToEntity(this) < d4 * d4)
    141        {
    142            double d5 = this.targetedEntity.posX - this.posX;
    143            double d6 = this.targetedEntity.boundingBox.minY + (double)(this.targetedEntity.height / 2.0F) - (this.posY + (double)(this.height / 2.0F));
    144            double d7 = this.targetedEntity.posZ - this.posZ;
    145            this.renderYawOffset = this.rotationYaw = -((float)Math.atan2(d5, d7)) * 180.0F / (float)Math.PI;
    146
    147            if (this.canEntityBeSeen(this.targetedEntity))
    148            {
    149                if (this.attackCounter == 10)
    150                {
    151                    this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1007, (int)this.posX, (int)this.posY, (int)this.posZ, 0);
    152                }
    153
    154                ++this.attackCounter;
    155
    156                if (this.attackCounter == 20)
    157                {
    158                    this.worldObj.playAuxSFXAtEntity((EntityPlayer)null, 1008, (int)this.posX, (int)this.posY, (int)this.posZ, 0);
    159                    EntityLargeFireball entitylargefireball = new EntityLargeFireball(this.worldObj, this, d5, d6, d7);
    160                    entitylargefireball.field_92057_e = this.explosionStrength;
    161                    double d8 = 4.0D;
    162                    Vec3 vec3 = this.getLook(1.0F);
    163                    entitylargefireball.posX = this.posX + vec3.xCoord * d8;
    164                    entitylargefireball.posY = this.posY + (double)(this.height / 2.0F) + 0.5D;
    165                    entitylargefireball.posZ = this.posZ + vec3.zCoord * d8;
    166                    this.worldObj.spawnEntityInWorld(entitylargefireball);
    167                    this.attackCounter = -40;
    168                }
    169            }
    170            else if (this.attackCounter > 0)
    171            {
    172                --this.attackCounter;
    173            }
    174        }
    175        else
    176        {
    177            this.renderYawOffset = this.rotationYaw = -((float)Math.atan2(this.motionX, this.motionZ)) * 180.0F / (float)Math.PI;
    178
    179            if (this.attackCounter > 0)
    180            {
    181                --this.attackCounter;
    182            }
    183        }
    184
    185        if (!this.worldObj.isRemote)
    186        {
    187            byte b0 = this.dataWatcher.getWatchableObjectByte(16);
    188            byte b1 = (byte)(this.attackCounter > 10 ? 1 : 0);
    189
    190            if (b0 != b1)
    191            {
    192                this.dataWatcher.updateObject(16, Byte.valueOf(b1));
    193            }
    194        }
    195    }
    196
    197    /**
    198     * True if the ghast has an unobstructed line of travel to the waypoint.
    199     */
    200    private boolean isCourseTraversable(double par1, double par3, double par5, double par7)
    201    {
    202        double d4 = (this.waypointX - this.posX) / par7;
    203        double d5 = (this.waypointY - this.posY) / par7;
    204        double d6 = (this.waypointZ - this.posZ) / par7;
    205        AxisAlignedBB axisalignedbb = this.boundingBox.copy();
    206
    207        for (int i = 1; (double)i < par7; ++i)
    208        {
    209            axisalignedbb.offset(d4, d5, d6);
    210
    211            if (!this.worldObj.getCollidingBoundingBoxes(this, axisalignedbb).isEmpty())
    212            {
    213                return false;
    214            }
    215        }
    216
    217        return true;
    218    }
    219
    220    /**
    221     * Returns the sound this mob makes while it's alive.
    222     */
    223    protected String getLivingSound()
    224    {
    225        return "mob.ghast.moan";
    226    }
    227
    228    /**
    229     * Returns the sound this mob makes when it is hurt.
    230     */
    231    protected String getHurtSound()
    232    {
    233        return "mob.ghast.scream";
    234    }
    235
    236    /**
    237     * Returns the sound this mob makes on death.
    238     */
    239    protected String getDeathSound()
    240    {
    241        return "mob.ghast.death";
    242    }
    243
    244    /**
    245     * Returns the item ID for the item the mob drops on death.
    246     */
    247    protected int getDropItemId()
    248    {
    249        return Item.gunpowder.itemID;
    250    }
    251
    252    /**
    253     * Drop 0-2 items of this living's type. @param par1 - Whether this entity has recently been hit by a player. @param
    254     * par2 - Level of Looting used to kill this mob.
    255     */
    256    protected void dropFewItems(boolean par1, int par2)
    257    {
    258        int j = this.rand.nextInt(2) + this.rand.nextInt(1 + par2);
    259        int k;
    260
    261        for (k = 0; k < j; ++k)
    262        {
    263            this.dropItem(Item.ghastTear.itemID, 1);
    264        }
    265
    266        j = this.rand.nextInt(3) + this.rand.nextInt(1 + par2);
    267
    268        for (k = 0; k < j; ++k)
    269        {
    270            this.dropItem(Item.gunpowder.itemID, 1);
    271        }
    272    }
    273
    274    /**
    275     * Returns the volume for the sounds this mob makes.
    276     */
    277    protected float getSoundVolume()
    278    {
    279        return 10.0F;
    280    }
    281
    282    /**
    283     * Checks if the entity's current position is a valid location to spawn this entity.
    284     */
    285    public boolean getCanSpawnHere()
    286    {
    287        return this.rand.nextInt(20) == 0 && super.getCanSpawnHere() && this.worldObj.difficultySetting > 0;
    288    }
    289
    290    /**
    291     * Will return how many at most can spawn in a chunk at once.
    292     */
    293    public int getMaxSpawnedInChunk()
    294    {
    295        return 1;
    296    }
    297
    298    /**
    299     * (abstract) Protected helper method to write subclass entity data to NBT.
    300     */
    301    public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
    302    {
    303        super.writeEntityToNBT(par1NBTTagCompound);
    304        par1NBTTagCompound.setInteger("ExplosionPower", this.explosionStrength);
    305    }
    306
    307    /**
    308     * (abstract) Protected helper method to read subclass entity data from NBT.
    309     */
    310    public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
    311    {
    312        super.readEntityFromNBT(par1NBTTagCompound);
    313
    314        if (par1NBTTagCompound.hasKey("ExplosionPower"))
    315        {
    316            this.explosionStrength = par1NBTTagCompound.getInteger("ExplosionPower");
    317        }
    318    }
    319}