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.setCanPickUpLoot(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}