Guest User

Untitled

a guest
Sep 13th, 2014
256
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.94 KB | None | 0 0
  1. package mymod.entity.ape;
  2.  
  3. import net.minecraft.block.BlockColored;
  4. import net.minecraft.entity.Entity;
  5. import net.minecraft.entity.EntityAgeable;
  6. import net.minecraft.entity.EntityLivingBase;
  7. import net.minecraft.entity.SharedMonsterAttributes;
  8. import net.minecraft.entity.ai.EntityAIAttackOnCollide;
  9. import net.minecraft.entity.ai.EntityAIFollowOwner;
  10. import net.minecraft.entity.ai.EntityAIHurtByTarget;
  11. import net.minecraft.entity.ai.EntityAILeapAtTarget;
  12. import net.minecraft.entity.ai.EntityAILookIdle;
  13. import net.minecraft.entity.ai.EntityAIMate;
  14. import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget;
  15. import net.minecraft.entity.ai.EntityAIOwnerHurtTarget;
  16. import net.minecraft.entity.ai.EntityAISwimming;
  17. import net.minecraft.entity.ai.EntityAITargetNonTamed;
  18. import net.minecraft.entity.ai.EntityAITempt;
  19. import net.minecraft.entity.ai.EntityAIWander;
  20. import net.minecraft.entity.ai.EntityAIWatchClosest;
  21. import net.minecraft.entity.monster.EntityCreeper;
  22. import net.minecraft.entity.monster.EntityGhast;
  23. import net.minecraft.entity.passive.EntityAnimal;
  24. import net.minecraft.entity.passive.EntityChicken;
  25. import net.minecraft.entity.passive.EntityHorse;
  26. import net.minecraft.entity.passive.EntityOcelot;
  27. import net.minecraft.entity.passive.EntityTameable;
  28. import net.minecraft.entity.player.EntityPlayer;
  29. import net.minecraft.entity.projectile.EntityArrow;
  30. import net.minecraft.init.Items;
  31. import net.minecraft.item.Item;
  32. import net.minecraft.item.ItemFood;
  33. import net.minecraft.item.ItemStack;
  34. import net.minecraft.nbt.NBTTagCompound;
  35. import net.minecraft.pathfinding.PathEntity;
  36. import net.minecraft.util.DamageSource;
  37. import net.minecraft.util.MathHelper;
  38. import net.minecraft.world.World;
  39. import cpw.mods.fml.relauncher.Side;
  40. import cpw.mods.fml.relauncher.SideOnly;
  41.  
  42. public class MyEntityApe extends EntityTameable
  43. {
  44. private float field_70926_e;
  45. private float field_70924_f;
  46.  
  47. /** true is the wolf is wet else false */
  48. private boolean isShaking;
  49. private boolean field_70928_h;
  50.  
  51. /**
  52. * This time increases while wolf is shaking and emitting water particles.
  53. */
  54. private float timeWolfIsShaking;
  55. private float prevTimeWolfIsShaking;
  56.  
  57. public MyEntityApe(World par1World)
  58. {
  59. super(par1World);
  60. this.setSize(0.8F, 1.0F);
  61. this.getNavigator().setAvoidsWater(false);
  62. this.tasks.addTask(1, new EntityAISwimming(this));
  63. this.tasks.addTask(2, this.aiSit);
  64. this.tasks.addTask(3, new EntityAILeapAtTarget(this, 0.7F));
  65. this.tasks.addTask(4, new EntityAIAttackOnCollide(this, 1.0D, true));
  66. this.tasks.addTask(5, new EntityAIFollowOwner(this, 1.0D, 10.0F, 2.0F));
  67. this.tasks.addTask(6, new EntityAIMate(this, 1.0D));
  68. this.tasks.addTask(7, new EntityAIWander(this, 1.0D));
  69. //this.tasks.addTask(8, new EntityAIBeg(this, 8.0F));
  70. this.tasks.addTask(9, new EntityAIWatchClosest(this, EntityOcelot.class, 8.0F));
  71. this.tasks.addTask(9, new EntityAILookIdle(this));
  72. this.targetTasks.addTask(1, new EntityAIOwnerHurtByTarget(this));
  73. this.targetTasks.addTask(2, new EntityAIOwnerHurtTarget(this));
  74. this.targetTasks.addTask(3, new EntityAIHurtByTarget(this, true));
  75. this.targetTasks.addTask(4, new EntityAITargetNonTamed(this, EntityChicken.class, 200, false));
  76. this.setTamed(false);
  77. this.tasks.addTask(3, new EntityAITempt(this, 1.0D, Items.apple, false));
  78. }
  79.  
  80. protected void applyEntityAttributes()
  81. {
  82. super.applyEntityAttributes();
  83. this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.30000001192092896D);
  84.  
  85. if (this.isTamed())
  86. {
  87. this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(20.0D);
  88. }
  89. else
  90. {
  91. this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(8.0D);
  92. }
  93. }
  94.  
  95.  
  96.  
  97. /**
  98. * Returns true if the newer Entity AI code should be run
  99. */
  100. public boolean isAIEnabled()
  101. {
  102. return true;
  103. }
  104.  
  105. /**
  106. * Sets the active target the Task system uses for tracking
  107. */
  108. public void setAttackTarget(EntityLivingBase par1EntityLivingBase)
  109. {
  110. super.setAttackTarget(par1EntityLivingBase);
  111.  
  112. if (par1EntityLivingBase == null)
  113. {
  114. this.setAngry(false);
  115. }
  116. else if (!this.isTamed())
  117. {
  118. this.setAngry(true);
  119. }
  120. }
  121.  
  122. /**
  123. * main AI tick function, replaces updateEntityActionState
  124. */
  125. protected void updateAITick()
  126. {
  127. this.dataWatcher.updateObject(18, Float.valueOf(this.getHealth()));
  128. }
  129.  
  130. protected void entityInit()
  131. {
  132. super.entityInit();
  133. this.dataWatcher.addObject(18, new Float(this.getHealth()));
  134. this.dataWatcher.addObject(19, new Byte((byte)0));
  135. this.dataWatcher.addObject(20, new Byte((byte)BlockColored.func_150032_b(1)));
  136. }
  137.  
  138. /**
  139. * Plays step sound at given x, y, z for the entity
  140. */
  141. protected void playStepSound(int par1, int par2, int par3, int par4)
  142. {
  143. this.playSound("mob.wolf.step", 0.15F, 1.0F);
  144. }
  145.  
  146. /**
  147. * (abstract) Protected helper method to write subclass entity data to NBT.
  148. */
  149. public void writeEntityToNBT(NBTTagCompound par1NBTTagCompound)
  150. {
  151. super.writeEntityToNBT(par1NBTTagCompound);
  152. par1NBTTagCompound.setBoolean("Angry", this.isAngry());
  153. par1NBTTagCompound.setByte("CollarColor", (byte)this.getCollarColor());
  154. }
  155.  
  156. /**
  157. * (abstract) Protected helper method to read subclass entity data from NBT.
  158. */
  159. public void readEntityFromNBT(NBTTagCompound par1NBTTagCompound)
  160. {
  161. super.readEntityFromNBT(par1NBTTagCompound);
  162. this.setAngry(par1NBTTagCompound.getBoolean("Angry"));
  163.  
  164. if (par1NBTTagCompound.hasKey("CollarColor"))
  165. {
  166. this.setCollarColor(par1NBTTagCompound.getByte("CollarColor"));
  167. }
  168. }
  169.  
  170. /**
  171. * Returns the sound this mob makes while it's alive.
  172. */
  173. protected String getLivingSound()
  174. {
  175. return this.isAngry() ? "mob.bat.hurt" : (this.rand.nextInt(3) == 0 ? (this.isTamed() && this.dataWatcher.getWatchableObjectFloat(18) < 10.0F ? "mob.bat.hurt" : "mob.bat.hurt") : "mob.bat.hurt");
  176. }
  177.  
  178. /**
  179. * Returns the sound this mob makes when it is hurt.
  180. */
  181. protected String getHurtSound()
  182. {
  183. return "mob.bat.hurt";
  184. }
  185.  
  186. /**
  187. * Returns the sound this mob makes on death.
  188. */
  189. protected String getDeathSound()
  190. {
  191. return "mob.bat.death";
  192. }
  193.  
  194. /**
  195. * Returns the volume for the sounds this mob makes.
  196. */
  197. protected float getSoundVolume()
  198. {
  199. return 0.4F;
  200. }
  201.  
  202. /**
  203. * Returns the item ID for the item the mob drops on death.
  204. */
  205. protected Item getDropItemId()
  206. {
  207. return Items.leather;
  208. }
  209.  
  210. /**
  211. * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
  212. * use this to react to sunlight and start to burn.
  213. */
  214. public void onLivingUpdate()
  215. {
  216. super.onLivingUpdate();
  217.  
  218. if (!this.worldObj.isRemote && this.isShaking && !this.field_70928_h && !this.hasPath() && this.onGround)
  219. {
  220. this.field_70928_h = true;
  221. this.timeWolfIsShaking = 0.0F;
  222. this.prevTimeWolfIsShaking = 0.0F;
  223. this.worldObj.setEntityState(this, (byte)8);
  224. }
  225. }
  226.  
  227. /**
  228. * Called to update the entity's position/logic.
  229. */
  230. public void onUpdate()
  231. {
  232. super.onUpdate();
  233. this.field_70924_f = this.field_70926_e;
  234.  
  235. if (this.func_70922_bv())
  236. {
  237. this.field_70926_e += (1.0F - this.field_70926_e) * 0.4F;
  238. }
  239. else
  240. {
  241. this.field_70926_e += (0.0F - this.field_70926_e) * 0.4F;
  242. }
  243.  
  244. if (this.func_70922_bv())
  245. {
  246. this.numTicksToChaseTarget = 10;
  247. }
  248.  
  249. if (this.isWet())
  250. {
  251. this.isShaking = true;
  252. this.field_70928_h = false;
  253. this.timeWolfIsShaking = 0.0F;
  254. this.prevTimeWolfIsShaking = 0.0F;
  255. }
  256. else if ((this.isShaking || this.field_70928_h) && this.field_70928_h)
  257. {
  258. if (this.timeWolfIsShaking == 0.0F)
  259. {
  260. this.playSound("mob.wolf.shake", this.getSoundVolume(), (this.rand.nextFloat() - this.rand.nextFloat()) * 0.2F + 1.0F);
  261. }
  262.  
  263. this.prevTimeWolfIsShaking = this.timeWolfIsShaking;
  264. this.timeWolfIsShaking += 0.05F;
  265.  
  266. if (this.prevTimeWolfIsShaking >= 2.0F)
  267. {
  268. this.isShaking = false;
  269. this.field_70928_h = false;
  270. this.prevTimeWolfIsShaking = 0.0F;
  271. this.timeWolfIsShaking = 0.0F;
  272. }
  273.  
  274. if (this.timeWolfIsShaking > 0.4F)
  275. {
  276. float f = (float)this.boundingBox.minY;
  277. int i = (int)(MathHelper.sin((this.timeWolfIsShaking - 0.4F) * (float)Math.PI) * 7.0F);
  278.  
  279. for (int j = 0; j < i; ++j)
  280. {
  281. float f1 = (this.rand.nextFloat() * 2.0F - 1.0F) * this.width * 0.5F;
  282. float f2 = (this.rand.nextFloat() * 2.0F - 1.0F) * this.width * 0.5F;
  283. this.worldObj.spawnParticle("splash", this.posX + (double)f1, (double)(f + 0.8F), this.posZ + (double)f2, this.motionX, this.motionY, this.motionZ);
  284. }
  285. }
  286. }
  287. }
  288.  
  289. @SideOnly(Side.CLIENT)
  290. public boolean getWolfShaking()
  291. {
  292. return this.isShaking;
  293. }
  294.  
  295. @SideOnly(Side.CLIENT)
  296.  
  297. /**
  298. * Used when calculating the amount of shading to apply while the wolf is shaking.
  299. */
  300. public float getShadingWhileShaking(float par1)
  301. {
  302. return 0.75F + (this.prevTimeWolfIsShaking + (this.timeWolfIsShaking - this.prevTimeWolfIsShaking) * par1) / 2.0F * 0.25F;
  303. }
  304.  
  305. @SideOnly(Side.CLIENT)
  306. public float getShakeAngle(float par1, float par2)
  307. {
  308. float f2 = (this.prevTimeWolfIsShaking + (this.timeWolfIsShaking - this.prevTimeWolfIsShaking) * par1 + par2) / 1.8F;
  309.  
  310. if (f2 < 0.0F)
  311. {
  312. f2 = 0.0F;
  313. }
  314. else if (f2 > 1.0F)
  315. {
  316. f2 = 1.0F;
  317. }
  318.  
  319. return MathHelper.sin(f2 * (float)Math.PI) * MathHelper.sin(f2 * (float)Math.PI * 11.0F) * 0.15F * (float)Math.PI;
  320. }
  321.  
  322. @SideOnly(Side.CLIENT)
  323. public float getInterestedAngle(float par1)
  324. {
  325. return (this.field_70924_f + (this.field_70926_e - this.field_70924_f) * par1) * 0.15F * (float)Math.PI;
  326. }
  327.  
  328. public float getEyeHeight()
  329. {
  330. return this.height * 0.8F;
  331. }
  332.  
  333. /**
  334. * The speed it takes to move the entityliving's rotationPitch through the faceEntity method. This is only currently
  335. * use in wolves.
  336. */
  337. public int getVerticalFaceSpeed()
  338. {
  339. return this.isSitting() ? 20 : super.getVerticalFaceSpeed();
  340. }
  341.  
  342. /**
  343. * Called when the entity is attacked.
  344. */
  345. public boolean attackEntityFrom(DamageSource par1DamageSource, float par2)
  346. {
  347. if (this.isEntityInvulnerable())
  348. {
  349. return false;
  350. }
  351. else
  352. {
  353. Entity entity = par1DamageSource.getEntity();
  354. this.aiSit.setSitting(false);
  355.  
  356. if (entity != null && !(entity instanceof EntityPlayer) && !(entity instanceof EntityArrow))
  357. {
  358. par2 = (par2 + 1.0F) / 2.0F;
  359. }
  360.  
  361. return super.attackEntityFrom(par1DamageSource, par2);
  362.  
  363. }
  364.  
  365. }
  366.  
  367.  
  368. public boolean attackEntityAsMob(Entity par1Entity)
  369. {
  370. int i = this.isTamed() ? 4 : 2;
  371. return par1Entity.attackEntityFrom(DamageSource.causeMobDamage(this), (float)i);
  372. }
  373.  
  374. public void setTamed(boolean par1)
  375. {
  376. super.setTamed(par1);
  377.  
  378. if (par1)
  379. {
  380. this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(20.0D);
  381. }
  382. else
  383. {
  384. this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(8.0D);
  385. }
  386. }
  387.  
  388. /**
  389. * Called when a player interacts with a mob. e.g. gets milk from a cow, gets into the saddle on a pig.
  390. */
  391. public boolean interact(EntityPlayer par1EntityPlayer)
  392. {
  393. ItemStack itemstack = par1EntityPlayer.inventory.getCurrentItem();
  394.  
  395. if (this.isTamed())
  396. {
  397. if (itemstack != null)
  398. {
  399. if (itemstack.getItem() instanceof ItemFood)
  400. {
  401. ItemFood itemfood = (ItemFood)itemstack.getItem();
  402.  
  403. if (itemfood.isWolfsFavoriteMeat() && this.dataWatcher.getWatchableObjectFloat(18) < 20.0F)
  404. {
  405. if (!par1EntityPlayer.capabilities.isCreativeMode)
  406. {
  407. --itemstack.stackSize;
  408. }
  409.  
  410. this.heal((float)itemfood.func_150905_g(itemstack));
  411.  
  412. if (itemstack.stackSize <= 0)
  413. {
  414. par1EntityPlayer.inventory.setInventorySlotContents(par1EntityPlayer.inventory.currentItem, (ItemStack)null);
  415. }
  416.  
  417. return true;
  418. }
  419. }
  420. else if (itemstack.getItem() == Items.dye)
  421. {
  422. int i = BlockColored.func_150032_b(itemstack.getItemDamage());
  423.  
  424. if (i != this.getCollarColor())
  425. {
  426. this.setCollarColor(i);
  427.  
  428. if (!par1EntityPlayer.capabilities.isCreativeMode && --itemstack.stackSize <= 0)
  429. {
  430. par1EntityPlayer.inventory.setInventorySlotContents(par1EntityPlayer.inventory.currentItem, (ItemStack)null);
  431. }
  432.  
  433. return true;
  434. }
  435. }
  436. }
  437.  
  438. if (par1EntityPlayer.getCommandSenderName().equalsIgnoreCase(this.getOwnerName()) && !this.worldObj.isRemote && !this.isBreedingItem(itemstack))
  439. {
  440. this.aiSit.setSitting(!this.isSitting());
  441. this.isJumping = false;
  442. this.setPathToEntity((PathEntity)null);
  443. this.setTarget((Entity)null);
  444. this.setAttackTarget((EntityLivingBase)null);
  445. }
  446. }
  447. else if (itemstack != null && itemstack.getItem() == Items.apple && !this.isAngry())
  448. {
  449. if (!par1EntityPlayer.capabilities.isCreativeMode)
  450. {
  451. --itemstack.stackSize;
  452. }
  453.  
  454. if (itemstack.stackSize <= 0)
  455. {
  456. par1EntityPlayer.inventory.setInventorySlotContents(par1EntityPlayer.inventory.currentItem, (ItemStack)null);
  457. }
  458.  
  459. if (!this.worldObj.isRemote)
  460. {
  461. if (this.rand.nextInt(3) == 0)
  462. {
  463. this.setTamed(true);
  464. this.setPathToEntity((PathEntity)null);
  465. this.setAttackTarget((EntityLivingBase)null);
  466. this.aiSit.setSitting(true);
  467. this.setHealth(20.0F);
  468. this.setOwner(par1EntityPlayer.getCommandSenderName());
  469. this.playTameEffect(true);
  470. this.worldObj.setEntityState(this, (byte)7);
  471. }
  472. else
  473. {
  474. this.playTameEffect(false);
  475. this.worldObj.setEntityState(this, (byte)6);
  476. }
  477. }
  478.  
  479. return true;
  480. }
  481.  
  482. return super.interact(par1EntityPlayer);
  483. }
  484.  
  485. @SideOnly(Side.CLIENT)
  486. public void handleHealthUpdate(byte par1)
  487. {
  488. if (par1 == 8)
  489. {
  490. this.field_70928_h = true;
  491. this.timeWolfIsShaking = 0.0F;
  492. this.prevTimeWolfIsShaking = 0.0F;
  493. }
  494. else
  495. {
  496. super.handleHealthUpdate(par1);
  497. }
  498. }
  499.  
  500. @SideOnly(Side.CLIENT)
  501. public float getTailRotation()
  502. {
  503. return this.isAngry() ? 1.5393804F : (this.isTamed() ? (0.55F - (20.0F - this.dataWatcher.getWatchableObjectFloat(18)) * 0.02F) * (float)Math.PI : ((float)Math.PI / 5F));
  504. }
  505.  
  506. /**
  507. * Checks if the parameter is an item which this animal can be fed to breed it (wheat, carrots or seeds depending on
  508. * the animal type)
  509. */
  510. public boolean isBreedingItem(ItemStack par1ItemStack)
  511. {
  512. return par1ItemStack == null ? false : (!(par1ItemStack.getItem() instanceof ItemFood) ? false : ((ItemFood)par1ItemStack.getItem()).isWolfsFavoriteMeat());
  513. }
  514.  
  515. /**
  516. * Will return how many at most can spawn in a chunk at once.
  517. */
  518. public int getMaxSpawnedInChunk()
  519. {
  520. return 18;
  521. }
  522.  
  523. /**
  524. * Determines whether this wolf is angry or not.
  525. */
  526. public boolean isAngry()
  527. {
  528. return (this.dataWatcher.getWatchableObjectByte(16) & 2) != 0;
  529. }
  530.  
  531. /**
  532. * Sets whether this wolf is angry or not.
  533. */
  534. public void setAngry(boolean par1)
  535. {
  536. byte b0 = this.dataWatcher.getWatchableObjectByte(16);
  537.  
  538. if (par1)
  539. {
  540. this.dataWatcher.updateObject(16, Byte.valueOf((byte)(b0 | 2)));
  541. }
  542. else
  543. {
  544. this.dataWatcher.updateObject(16, Byte.valueOf((byte)(b0 & -3)));
  545. }
  546. }
  547.  
  548. /**
  549. * Return this wolf's collar color.
  550. */
  551. public int getCollarColor()
  552. {
  553. return this.dataWatcher.getWatchableObjectByte(20) & 15;
  554. }
  555.  
  556. /**
  557. * Set this wolf's collar color.
  558. */
  559. public void setCollarColor(int par1)
  560. {
  561. this.dataWatcher.updateObject(20, Byte.valueOf((byte)(par1 & 15)));
  562. }
  563.  
  564. /**
  565. * This function is used when two same-species animals in 'love mode' breed to generate the new baby animal.
  566. */
  567. public MyEntityApe spawnBabyAnimal(EntityAgeable par1EntityAgeable)
  568. {
  569. MyEntityApe entitywolf = new MyEntityApe(this.worldObj);
  570. String s = this.getOwnerName();
  571.  
  572. if (s != null && s.trim().length() > 0)
  573. {
  574. entitywolf.setOwner(s);
  575. entitywolf.setTamed(true);
  576. }
  577.  
  578. return entitywolf;
  579. }
  580.  
  581. public void func_70918_i(boolean par1)
  582. {
  583. if (par1)
  584. {
  585. this.dataWatcher.updateObject(19, Byte.valueOf((byte)1));
  586. }
  587. else
  588. {
  589. this.dataWatcher.updateObject(19, Byte.valueOf((byte)0));
  590. }
  591. }
  592.  
  593. /**
  594. * Returns true if the mob is currently able to mate with the specified mob.
  595. */
  596. public boolean canMateWith(EntityAnimal par1EntityAnimal)
  597. {
  598. if (par1EntityAnimal == this)
  599. {
  600. return false;
  601. }
  602. else if (!this.isTamed())
  603. {
  604. return false;
  605. }
  606. else if (!(par1EntityAnimal instanceof MyEntityApe))
  607. {
  608. return false;
  609. }
  610. else
  611. {
  612. MyEntityApe entitywolf = (MyEntityApe)par1EntityAnimal;
  613. return !entitywolf.isTamed() ? false : (entitywolf.isSitting() ? false : this.isInLove() && entitywolf.isInLove());
  614. }
  615. }
  616.  
  617. public boolean func_70922_bv()
  618. {
  619. return this.dataWatcher.getWatchableObjectByte(19) == 1;
  620. }
  621.  
  622. /**
  623. * Determines if an entity can be despawned, used on idle far away entities
  624. */
  625. protected boolean canDespawn()
  626. {
  627. return !this.isTamed() && this.ticksExisted > 2400;
  628. }
  629.  
  630. public boolean func_142018_a(EntityLivingBase par1EntityLivingBase, EntityLivingBase par2EntityLivingBase)
  631. {
  632. if (!(par1EntityLivingBase instanceof EntityCreeper) && !(par1EntityLivingBase instanceof EntityGhast))
  633. {
  634. if (par1EntityLivingBase instanceof MyEntityApe)
  635. {
  636. MyEntityApe entitywolf = (MyEntityApe)par1EntityLivingBase;
  637.  
  638. if (entitywolf.isTamed() && entitywolf.getOwner() == par2EntityLivingBase)
  639. {
  640. return false;
  641. }
  642. }
  643.  
  644. return par1EntityLivingBase instanceof EntityPlayer && par2EntityLivingBase instanceof EntityPlayer && !((EntityPlayer)par2EntityLivingBase).canAttackPlayer((EntityPlayer)par1EntityLivingBase) ? false : !(par1EntityLivingBase instanceof EntityHorse) || !((EntityHorse)par1EntityLivingBase).isTame();
  645. }
  646. else
  647. {
  648. return false;
  649. }
  650. }
  651.  
  652. public EntityAgeable createChild(EntityAgeable par1EntityAgeable)
  653. {
  654. return this.spawnBabyAnimal(par1EntityAgeable);
  655. }
  656.  
  657. }
Advertisement
Add Comment
Please, Sign In to add comment