001package net.minecraft.entity.monster;
002
003import net.minecraft.entity.EntityLiving;
004import net.minecraft.entity.player.EntityPlayer;
005import net.minecraft.item.Item;
006import net.minecraft.nbt.NBTTagCompound;
007import net.minecraft.util.DamageSource;
008import net.minecraft.util.MathHelper;
009import net.minecraft.world.World;
010import net.minecraft.world.WorldType;
011import net.minecraft.world.biome.BiomeGenBase;
012import net.minecraft.world.chunk.Chunk;
013
014public class EntitySlime extends EntityLiving implements IMob
015{
016    /** Chances for slimes to spawn in swamps for every moon phase. */
017    private static final float[] spawnChances = new float[] {1.0F, 0.75F, 0.5F, 0.25F, 0.0F, 0.25F, 0.5F, 0.75F};
018    public float field_70813_a;
019    public float field_70811_b;
020    public float field_70812_c;
021
022    /** the time between each jump of the slime */
023    private int slimeJumpDelay = 0;
024
025    public EntitySlime(World par1World)
026    {
027        super(par1World);
028        this.texture = "/mob/slime.png";
029        int i = 1 << this.rand.nextInt(3);
030        this.yOffset = 0.0F;
031        this.slimeJumpDelay = this.rand.nextInt(20) + 10;
032        this.setSlimeSize(i);
033    }
034
035    protected void entityInit()
036    {
037        super.entityInit();
038        this.dataWatcher.addObject(16, new Byte((byte)1));
039    }
040
041    protected void setSlimeSize(int par1)
042    {
043        this.dataWatcher.updateObject(16, new Byte((byte)par1));
044        this.setSize(0.6F * (float)par1, 0.6F * (float)par1);
045        this.setPosition(this.posX, this.posY, this.posZ);
046        this.setEntityHealth(this.getMaxHealth());
047        this.experienceValue = par1;
048    }
049
050    public int getMaxHealth()
051    {
052        int i = this.getSlimeSize();
053        return i * i;
054    }
055
056    /**
057     * Returns the size of the slime.
058     */
059    public int getSlimeSize()
060    {
061        return this.dataWatcher.getWatchableObjectByte(16);
062    }
063
064    /**
065     * (abstract) Protected helper method to write subclass entity data to NBT.
066     */
067    public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
068    {
069        super.writeEntityToNBT(par1NBTTagCompound);
070        par1NBTTagCompound.setInteger("Size", this.getSlimeSize() - 1);
071    }
072
073    /**
074     * (abstract) Protected helper method to read subclass entity data from NBT.
075     */
076    public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
077    {
078        super.readEntityFromNBT(par1NBTTagCompound);
079        this.setSlimeSize(par1NBTTagCompound.getInteger("Size") + 1);
080    }
081
082    /**
083     * Returns the name of a particle effect that may be randomly created by EntitySlime.onUpdate()
084     */
085    protected String getSlimeParticle()
086    {
087        return "slime";
088    }
089
090    /**
091     * Returns the name of the sound played when the slime jumps.
092     */
093    protected String getJumpSound()
094    {
095        return "mob.slime." + (this.getSlimeSize() > 1 ? "big" : "small");
096    }
097
098    /**
099     * Called to update the entity's position/logic.
100     */
101    public void onUpdate()
102    {
103        if (!this.worldObj.isRemote && this.worldObj.difficultySetting == 0 && this.getSlimeSize() > 0)
104        {
105            this.isDead = true;
106        }
107
108        this.field_70811_b += (this.field_70813_a - this.field_70811_b) * 0.5F;
109        this.field_70812_c = this.field_70811_b;
110        boolean flag = this.onGround;
111        super.onUpdate();
112        int i;
113
114        if (this.onGround && !flag)
115        {
116            i = this.getSlimeSize();
117
118            for (int j = 0; j < i * 8; ++j)
119            {
120                float f = this.rand.nextFloat() * (float)Math.PI * 2.0F;
121                float f1 = this.rand.nextFloat() * 0.5F + 0.5F;
122                float f2 = MathHelper.sin(f) * (float)i * 0.5F * f1;
123                float f3 = MathHelper.cos(f) * (float)i * 0.5F * f1;
124                this.worldObj.spawnParticle(this.getSlimeParticle(), this.posX + (double)f2, this.boundingBox.minY, this.posZ + (double)f3, 0.0D, 0.0D, 0.0D);
125            }
126
127            if (this.makesSoundOnLand())
128            {
129                this.playSound(this.getJumpSound(), this.getSoundVolume(), ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 1.0F) / 0.8F);
130            }
131
132            this.field_70813_a = -0.5F;
133        }
134        else if (!this.onGround && flag)
135        {
136            this.field_70813_a = 1.0F;
137        }
138
139        this.func_70808_l();
140
141        if (this.worldObj.isRemote)
142        {
143            i = this.getSlimeSize();
144            this.setSize(0.6F * (float)i, 0.6F * (float)i);
145        }
146    }
147
148    protected void updateEntityActionState()
149    {
150        this.despawnEntity();
151        EntityPlayer entityplayer = this.worldObj.getClosestVulnerablePlayerToEntity(this, 16.0D);
152
153        if (entityplayer != null)
154        {
155            this.faceEntity(entityplayer, 10.0F, 20.0F);
156        }
157
158        if (this.onGround && this.slimeJumpDelay-- <= 0)
159        {
160            this.slimeJumpDelay = this.getJumpDelay();
161
162            if (entityplayer != null)
163            {
164                this.slimeJumpDelay /= 3;
165            }
166
167            this.isJumping = true;
168
169            if (this.makesSoundOnJump())
170            {
171                this.playSound(this.getJumpSound(), this.getSoundVolume(), ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 1.0F) * 0.8F);
172            }
173
174            this.moveStrafing = 1.0F - this.rand.nextFloat() * 2.0F;
175            this.moveForward = (float)(1 * this.getSlimeSize());
176        }
177        else
178        {
179            this.isJumping = false;
180
181            if (this.onGround)
182            {
183                this.moveStrafing = this.moveForward = 0.0F;
184            }
185        }
186    }
187
188    protected void func_70808_l()
189    {
190        this.field_70813_a *= 0.6F;
191    }
192
193    /**
194     * Gets the amount of time the slime needs to wait between jumps.
195     */
196    protected int getJumpDelay()
197    {
198        return this.rand.nextInt(20) + 10;
199    }
200
201    protected EntitySlime createInstance()
202    {
203        return new EntitySlime(this.worldObj);
204    }
205
206    /**
207     * Will get destroyed next tick.
208     */
209    public void setDead()
210    {
211        int i = this.getSlimeSize();
212
213        if (!this.worldObj.isRemote && i > 1 && this.getHealth() <= 0)
214        {
215            int j = 2 + this.rand.nextInt(3);
216
217            for (int k = 0; k < j; ++k)
218            {
219                float f = ((float)(k % 2) - 0.5F) * (float)i / 4.0F;
220                float f1 = ((float)(k / 2) - 0.5F) * (float)i / 4.0F;
221                EntitySlime entityslime = this.createInstance();
222                entityslime.setSlimeSize(i / 2);
223                entityslime.setLocationAndAngles(this.posX + (double)f, this.posY + 0.5D, this.posZ + (double)f1, this.rand.nextFloat() * 360.0F, 0.0F);
224                this.worldObj.spawnEntityInWorld(entityslime);
225            }
226        }
227
228        super.setDead();
229    }
230
231    /**
232     * Called by a player entity when they collide with an entity
233     */
234    public void onCollideWithPlayer(EntityPlayer par1EntityPlayer)
235    {
236        if (this.canDamagePlayer())
237        {
238            int i = this.getSlimeSize();
239
240            if (this.canEntityBeSeen(par1EntityPlayer) && this.getDistanceSqToEntity(par1EntityPlayer) < 0.6D * (double)i * 0.6D * (double)i && par1EntityPlayer.attackEntityFrom(DamageSource.causeMobDamage(this), this.getAttackStrength()))
241            {
242                this.playSound("mob.attack", 1.0F, (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 1.0F);
243            }
244        }
245    }
246
247    /**
248     * Indicates weather the slime is able to damage the player (based upon the slime's size)
249     */
250    protected boolean canDamagePlayer()
251    {
252        return this.getSlimeSize() > 1;
253    }
254
255    /**
256     * Gets the amount of damage dealt to the player when "attacked" by the slime.
257     */
258    protected int getAttackStrength()
259    {
260        return this.getSlimeSize();
261    }
262
263    /**
264     * Returns the sound this mob makes when it is hurt.
265     */
266    protected String getHurtSound()
267    {
268        return "mob.slime." + (this.getSlimeSize() > 1 ? "big" : "small");
269    }
270
271    /**
272     * Returns the sound this mob makes on death.
273     */
274    protected String getDeathSound()
275    {
276        return "mob.slime." + (this.getSlimeSize() > 1 ? "big" : "small");
277    }
278
279    /**
280     * Returns the item ID for the item the mob drops on death.
281     */
282    protected int getDropItemId()
283    {
284        return this.getSlimeSize() == 1 ? Item.slimeBall.itemID : 0;
285    }
286
287    /**
288     * Checks if the entity's current position is a valid location to spawn this entity.
289     */
290    public boolean getCanSpawnHere()
291    {
292        Chunk chunk = this.worldObj.getChunkFromBlockCoords(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posZ));
293
294        if (this.worldObj.getWorldInfo().getTerrainType().handleSlimeSpawnReduction(rand, worldObj))
295        {
296            return false;
297        }
298        else
299        {
300            if (this.getSlimeSize() == 1 || this.worldObj.difficultySetting > 0)
301            {
302                BiomeGenBase biomegenbase = this.worldObj.getBiomeGenForCoords(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posZ));
303
304                if (biomegenbase == BiomeGenBase.swampland && this.posY > 50.0D && this.posY < 70.0D && this.rand.nextFloat() < 0.5F && this.rand.nextFloat() < spawnChances[this.worldObj.getMoonPhase()] && this.worldObj.getBlockLightValue(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ)) <= this.rand.nextInt(8))
305                {
306                    return super.getCanSpawnHere();
307                }
308
309                if (this.rand.nextInt(10) == 0 && chunk.getRandomWithSeed(987234911L).nextInt(10) == 0 && this.posY < 40.0D)
310                {
311                    return super.getCanSpawnHere();
312                }
313            }
314
315            return false;
316        }
317    }
318
319    /**
320     * Returns the volume for the sounds this mob makes.
321     */
322    protected float getSoundVolume()
323    {
324        return 0.4F * (float)this.getSlimeSize();
325    }
326
327    /**
328     * The speed it takes to move the entityliving's rotationPitch through the faceEntity method. This is only currently
329     * use in wolves.
330     */
331    public int getVerticalFaceSpeed()
332    {
333        return 0;
334    }
335
336    /**
337     * Returns true if the slime makes a sound when it jumps (based upon the slime's size)
338     */
339    protected boolean makesSoundOnJump()
340    {
341        return this.getSlimeSize() > 0;
342    }
343
344    /**
345     * Returns true if the slime makes a sound when it lands after a jump (based upon the slime's size)
346     */
347    protected boolean makesSoundOnLand()
348    {
349        return this.getSlimeSize() > 2;
350    }
351}