Advertisement
Raven6990

player.cs

Nov 30th, 2016
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.33 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Xml;
  8.  
  9. namespace Engine
  10. {
  11. public class Player : LivingCreature
  12. {
  13. private int _gold;
  14. private int _experiencePoints;
  15. private Location _currentLocation;
  16. private Monster _currentMonster;
  17.  
  18. public event EventHandler<MessageEventArgs> OnMessage;
  19.  
  20. public int Gold
  21. {
  22. get { return _gold; }
  23. set
  24. {
  25. _gold = value;
  26. OnPropertyChanged("Gold");
  27. }
  28. }
  29.  
  30. public int ExperiencePoints
  31. {
  32. get { return _experiencePoints; }
  33. private set
  34. {
  35. _experiencePoints = value;
  36. OnPropertyChanged("ExperiencePoints");
  37. OnPropertyChanged("Level");
  38. }
  39. }
  40.  
  41. public int Level
  42. {
  43. get { return ((ExperiencePoints / 100) + 1); }
  44. }
  45.  
  46. public Location CurrentLocation
  47. {
  48. get { return _currentLocation; }
  49. set
  50. {
  51. _currentLocation = value;
  52. OnPropertyChanged("CurrentLocation");
  53. }
  54. }
  55.  
  56. public Weapon CurrentWeapon { get; set; }
  57.  
  58. public BindingList<InventoryItem> Inventory { get; set; }
  59.  
  60. public List<Weapon> Weapons
  61. {
  62. get { return Inventory.Where(x => x.Details is Weapon).Select(x => x.Details as Weapon).ToList(); }
  63. }
  64.  
  65. public List<HealingPotion> Potions
  66. {
  67. get { return Inventory.Where(x => x.Details is HealingPotion).Select(x => x.Details as HealingPotion).ToList(); }
  68. }
  69.  
  70. public BindingList<PlayerQuest> Quests { get; set; }
  71.  
  72. private Player(int currentHitPoints, int maximumHitPoints, int gold, int experiencePoints) : base(currentHitPoints, maximumHitPoints)
  73. {
  74. Gold = gold;
  75. ExperiencePoints = experiencePoints;
  76.  
  77. Inventory = new BindingList<InventoryItem>();
  78. Quests = new BindingList<PlayerQuest>();
  79. }
  80.  
  81. public static Player CreateDefaultPlayer()
  82. {
  83. Player player = new Player(10, 10, 20, 0);
  84. player.Inventory.Add(new InventoryItem(World.ItemByID(World.ITEM_ID_RUSTY_KNIFE), 1));
  85. player.CurrentLocation = World.LocationByID(World.LOCATION_ID_HOME);
  86.  
  87. return player;
  88. }
  89.  
  90. public void AddExperiencePoints(int experiencePointsToAdd)
  91. {
  92. ExperiencePoints += experiencePointsToAdd;
  93. MaximumHitPoints = (Level * 10);
  94. }
  95.  
  96. public static Player CreatePlayerFromXmlString(string xmlPlayerData)
  97. {
  98. try
  99. {
  100. XmlDocument playerData = new XmlDocument();
  101.  
  102. playerData.LoadXml(xmlPlayerData);
  103.  
  104. int currentHitPoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentHitPoints").InnerText);
  105. int maximumHitPoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/MaximumHitPoints").InnerText);
  106. int gold = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/Gold").InnerText);
  107. int experiencePoints = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/ExperiencePoints").InnerText);
  108.  
  109. Player player = new Player(currentHitPoints, maximumHitPoints, gold, experiencePoints);
  110.  
  111. int currentLocationID = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentLocation").InnerText);
  112. player.CurrentLocation = World.LocationByID(currentLocationID);
  113.  
  114. if (playerData.SelectSingleNode("/Player/Stats/CurrentWeapon") != null)
  115. {
  116. int currentWeaponID = Convert.ToInt32(playerData.SelectSingleNode("/Player/Stats/CurrentWeapon").InnerText);
  117. player.CurrentWeapon = (Weapon)World.ItemByID(currentWeaponID);
  118. }
  119.  
  120. foreach (XmlNode node in playerData.SelectNodes("/Player/InventoryItems/InventoryItem"))
  121. {
  122. int id = Convert.ToInt32(node.Attributes["ID"].Value);
  123. int quantity = Convert.ToInt32(node.Attributes["Quantity"].Value);
  124.  
  125. for (int i = 0; i < quantity; i++)
  126. {
  127. player.AddItemToInventory(World.ItemByID(id));
  128. }
  129. }
  130.  
  131. foreach (XmlNode node in playerData.SelectNodes("/Player/PlayerQuests/PlayerQuest"))
  132. {
  133. int id = Convert.ToInt32(node.Attributes["ID"].Value);
  134. bool isCompleted = Convert.ToBoolean(node.Attributes["IsCompleted"].Value);
  135.  
  136. PlayerQuest playerQuest = new PlayerQuest(World.QuestByID(id));
  137. playerQuest.IsCompleted = isCompleted;
  138.  
  139. player.Quests.Add(playerQuest);
  140. }
  141.  
  142. return player;
  143. }
  144. catch
  145. {
  146. // If there was an error with the XML data, return a default player object
  147. return Player.CreateDefaultPlayer();
  148. }
  149. }
  150.  
  151. public bool HasRequiredItemToEnterThisLocation(Location location)
  152. {
  153. if (location.ItemRequiredToEnter == null)
  154. {
  155. // There is no required item for this location, so return "true"
  156. return true;
  157. }
  158.  
  159. // See if the player has the required item in their inventory
  160. return Inventory.Any(ii => ii.Details.ID == location.ItemRequiredToEnter.ID);
  161. }
  162.  
  163. public bool HasThisQuest(Quest quest)
  164. {
  165. return Quests.Any(pq => pq.Details.ID == quest.ID);
  166. }
  167.  
  168. public bool CompletedThisQuest(Quest quest)
  169. {
  170. foreach (PlayerQuest playerQuest in Quests)
  171. {
  172. if (playerQuest.Details.ID == quest.ID)
  173. {
  174. return playerQuest.IsCompleted;
  175. }
  176. }
  177.  
  178. return false;
  179. }
  180.  
  181. public bool HasAllQuestCompletionItems(Quest quest)
  182. {
  183. // See if the player has all the items needed to complete the quest here
  184. foreach (QuestCompletionItem qci in quest.QuestCompletionItems)
  185. {
  186. // Check each item in the player's inventory, to see if they have it, and enough of it
  187. if (!Inventory.Any(ii => ii.Details.ID == qci.Details.ID && ii.Quantity >= qci.Quantity))
  188. {
  189. return false;
  190. }
  191. }
  192.  
  193. // If we got here, then the player must have all the required items, and enough of them, to complete the quest.
  194. return true;
  195. }
  196.  
  197. public void RemoveQuestCompletionItems(Quest quest)
  198. {
  199. foreach (QuestCompletionItem qci in quest.QuestCompletionItems)
  200. {
  201. // Subtract the quantity from the player's inventory that was needed to complete the quest
  202. InventoryItem item = Inventory.SingleOrDefault(ii => ii.Details.ID == qci.Details.ID);
  203.  
  204. if (item != null)
  205. {
  206. RemoveItemFromInventory(item.Details, qci.Quantity);
  207. }
  208. }
  209. }
  210.  
  211. public void AddItemToInventory(Item itemToAdd, int quantity = 1)
  212. {
  213. InventoryItem item = Inventory.SingleOrDefault(ii => ii.Details.ID == itemToAdd.ID);
  214.  
  215. if (item == null)
  216. {
  217. // They didn't have the item, so add it to their inventory
  218. Inventory.Add(new InventoryItem(itemToAdd, quantity));
  219. }
  220. else
  221. {
  222. // They have the item in their inventory, so increase the quantity
  223. item.Quantity += quantity;
  224. }
  225.  
  226. RaiseInventoryChangedEvent(itemToAdd);
  227. }
  228.  
  229. public void RemoveItemFromInventory(Item itemToRemove, int quantity = 1)
  230. {
  231. InventoryItem item = Inventory.SingleOrDefault(ii => ii.Details.ID == itemToRemove.ID);
  232.  
  233. if (item == null)
  234. {
  235. // The item is not in the player's inventory, so ignore it.
  236. // We might want to raise an error for this situation
  237. }
  238. else
  239. {
  240. // They have the item in their inventory, so decrease the quantity
  241. item.Quantity -= quantity;
  242.  
  243. // Don't allow negative quantities.
  244. // We might want to raise an error for this situation
  245. if (item.Quantity < 0)
  246. {
  247. item.Quantity = 0;
  248. }
  249.  
  250. // If the quantity is zero, remove the item from the list
  251. if (item.Quantity == 0)
  252. {
  253. Inventory.Remove(item);
  254. }
  255.  
  256. // Notify the UI that the inventory has changed
  257. RaiseInventoryChangedEvent(itemToRemove);
  258. }
  259. }
  260.  
  261. private void RaiseInventoryChangedEvent(Item item)
  262. {
  263. if (item is Weapon)
  264. {
  265. OnPropertyChanged("Weapons");
  266. }
  267.  
  268. if (item is HealingPotion)
  269. {
  270. OnPropertyChanged("Potions");
  271. }
  272. }
  273.  
  274. public void MarkQuestCompleted(Quest quest)
  275. {
  276. // Find the quest in the player's quest list
  277. PlayerQuest playerQuest = Quests.SingleOrDefault(pq => pq.Details.ID == quest.ID);
  278.  
  279. if (playerQuest != null)
  280. {
  281. playerQuest.IsCompleted = true;
  282. }
  283. }
  284.  
  285. private void RaiseMessage(string message, bool addExtraNewLine = false)
  286. {
  287. if (OnMessage != null)
  288. {
  289. OnMessage(this, new MessageEventArgs(message, addExtraNewLine));
  290. }
  291. }
  292.  
  293. public void MoveTo(Location newLocation)
  294. {
  295. //Does the location have any required items
  296. if (!HasRequiredItemToEnterThisLocation(newLocation))
  297. {
  298. RaiseMessage("You must have a " + newLocation.ItemRequiredToEnter.Name + " to enter this location.");
  299. return;
  300. }
  301.  
  302. // Update the player's current location
  303. CurrentLocation = newLocation;
  304.  
  305. // Completely heal the player
  306. CurrentHitPoints = MaximumHitPoints;
  307.  
  308. // Does the location have a quest?
  309. if (newLocation.QuestAvailableHere != null)
  310. {
  311. // See if the player already has the quest, and if they've completed it
  312. bool playerAlreadyHasQuest = HasThisQuest(newLocation.QuestAvailableHere);
  313. bool playerAlreadyCompletedQuest = CompletedThisQuest(newLocation.QuestAvailableHere);
  314.  
  315. // See if the player already has the quest
  316. if (playerAlreadyHasQuest)
  317. {
  318. // If the player has not completed the quest yet
  319. if (!playerAlreadyCompletedQuest)
  320. {
  321. // See if the player has all the items needed to complete the quest
  322. bool playerHasAllItemsToCompleteQuest = HasAllQuestCompletionItems(newLocation.QuestAvailableHere);
  323.  
  324. // The player has all items required to complete the quest
  325. if (playerHasAllItemsToCompleteQuest)
  326. {
  327. // Display message
  328. RaiseMessage("");
  329. RaiseMessage("You complete the '" + newLocation.QuestAvailableHere.Name + "' quest.");
  330.  
  331. // Remove quest items from inventory
  332. RemoveQuestCompletionItems(newLocation.QuestAvailableHere);
  333.  
  334. // Give quest rewards
  335. RaiseMessage("You receive: ");
  336. RaiseMessage(newLocation.QuestAvailableHere.RewardExperiencePoints + " experience points");
  337. RaiseMessage(newLocation.QuestAvailableHere.RewardGold + " gold");
  338. RaiseMessage(newLocation.QuestAvailableHere.RewardItem.Name, true);
  339.  
  340. AddExperiencePoints(newLocation.QuestAvailableHere.RewardExperiencePoints);
  341. Gold += newLocation.QuestAvailableHere.RewardGold;
  342.  
  343. // Add the reward item to the player's inventory
  344. AddItemToInventory(newLocation.QuestAvailableHere.RewardItem);
  345.  
  346. // Mark the quest as completed
  347. MarkQuestCompleted(newLocation.QuestAvailableHere);
  348. }
  349. }
  350. }
  351. else
  352. {
  353. // The player does not already have the quest
  354.  
  355. // Display the messages
  356. RaiseMessage("You receive the " + newLocation.QuestAvailableHere.Name + " quest.");
  357. RaiseMessage(newLocation.QuestAvailableHere.Description);
  358. RaiseMessage("To complete it, return with:");
  359. foreach (QuestCompletionItem qci in newLocation.QuestAvailableHere.QuestCompletionItems)
  360. {
  361. if (qci.Quantity == 1)
  362. {
  363. RaiseMessage(qci.Quantity + " " + qci.Details.Name);
  364. }
  365. else
  366. {
  367. RaiseMessage(qci.Quantity + " " + qci.Details.NamePlural);
  368. }
  369. }
  370. RaiseMessage("");
  371.  
  372. // Add the quest to the player's quest list
  373. Quests.Add(new PlayerQuest(newLocation.QuestAvailableHere));
  374. }
  375. }
  376.  
  377. // Does the location have a monster?
  378. if (newLocation.MonsterLivingHere != null)
  379. {
  380. RaiseMessage("You see a " + newLocation.MonsterLivingHere.Name);
  381.  
  382. // Make a new monster, using the values from the standard monster in the World.Monster list
  383. Monster standardMonster = World.MonsterByID(newLocation.MonsterLivingHere.ID);
  384.  
  385. _currentMonster = new Monster(standardMonster.ID, standardMonster.Name, standardMonster.MaximumDamage,
  386. standardMonster.RewardExperiencePoints, standardMonster.RewardGold, standardMonster.CurrentHitPoints, standardMonster.MaximumHitPoints);
  387.  
  388. foreach (LootItem lootItem in standardMonster.LootTable)
  389. {
  390. _currentMonster.LootTable.Add(lootItem);
  391. }
  392. }
  393. else
  394. {
  395. _currentMonster = null;
  396. }
  397. }
  398.  
  399. public void UseWeapon(Weapon weapon)
  400. {
  401. // Determine the amount of damage to do to the monster
  402. int damageToMonster = RandomNumberGenerator.NumberBetween(weapon.MinimumDamage, weapon.MaximumDamage);
  403.  
  404. // Apply the damage to the monster's CurrentHitPoints
  405. _currentMonster.CurrentHitPoints -= damageToMonster;
  406.  
  407. // Display message
  408. RaiseMessage("You hit the " + _currentMonster.Name + " for " + damageToMonster + " points.");
  409.  
  410. // Check if the monster is dead
  411. if (_currentMonster.CurrentHitPoints <= 0)
  412. {
  413. // Monster is dead
  414. RaiseMessage("");
  415. RaiseMessage("You defeated the " + _currentMonster.Name);
  416.  
  417. // Give player experience points for killing the monster
  418. AddExperiencePoints(_currentMonster.RewardExperiencePoints);
  419. RaiseMessage("You receive " + _currentMonster.RewardExperiencePoints + " experience points");
  420.  
  421. // Give player gold for killing the monster
  422. Gold += _currentMonster.RewardGold;
  423. RaiseMessage("You receive " + _currentMonster.RewardGold + " gold");
  424.  
  425. // Get random loot items from the monster
  426. List<InventoryItem> lootedItems = new List<InventoryItem>();
  427.  
  428. // Add items to the lootedItems list, comparing a random number to the drop percentage
  429. foreach (LootItem lootItem in _currentMonster.LootTable)
  430. {
  431. if (RandomNumberGenerator.NumberBetween(1, 100) <= lootItem.DropPercentage)
  432. {
  433. lootedItems.Add(new InventoryItem(lootItem.Details, 1));
  434. }
  435. }
  436.  
  437. // If no items were randomly selected, then add the default loot item(s).
  438. if (lootedItems.Count == 0)
  439. {
  440. foreach (LootItem lootItem in _currentMonster.LootTable)
  441. {
  442. if (lootItem.IsDefaultItem)
  443. {
  444. lootedItems.Add(new InventoryItem(lootItem.Details, 1));
  445. }
  446. }
  447. }
  448.  
  449. // Add the looted items to the player's inventory
  450. foreach (InventoryItem inventoryItem in lootedItems)
  451. {
  452. AddItemToInventory(inventoryItem.Details);
  453.  
  454. if (inventoryItem.Quantity == 1)
  455. {
  456. RaiseMessage("You loot " + inventoryItem.Quantity + " " + inventoryItem.Details.Name);
  457. }
  458. else
  459. {
  460. RaiseMessage("You loot " + inventoryItem.Quantity + " " + inventoryItem.Details.NamePlural);
  461. }
  462. }
  463.  
  464. // Add a blank line to the messages box, just for appearance.
  465. RaiseMessage("");
  466.  
  467. // Move player to current location (to heal player and create a new monster to fight)
  468. MoveTo(CurrentLocation);
  469. }
  470. else
  471. {
  472. // Monster is still alive
  473.  
  474. // Determine the amount of damage the monster does to the player
  475. int damageToPlayer = RandomNumberGenerator.NumberBetween(0, _currentMonster.MaximumDamage);
  476.  
  477. // Display message
  478. RaiseMessage("The " + _currentMonster.Name + " did " + damageToPlayer + " points of damage.");
  479.  
  480. // Subtract damage from player
  481. CurrentHitPoints -= damageToPlayer;
  482.  
  483. if (CurrentHitPoints <= 0)
  484. {
  485. // Display message
  486. RaiseMessage("The " + _currentMonster.Name + " killed you.");
  487.  
  488. // Move player to "Home"
  489. MoveHome();
  490. }
  491. }
  492. }
  493.  
  494. public void UsePotion(HealingPotion potion)
  495. {
  496. // Add healing amount to the player's current hit points
  497. CurrentHitPoints = (CurrentHitPoints + potion.AmountToHeal);
  498.  
  499. // CurrentHitPoints cannot exceed player's MaximumHitPoints
  500. if (CurrentHitPoints > MaximumHitPoints)
  501. {
  502. CurrentHitPoints = MaximumHitPoints;
  503. }
  504.  
  505. // Remove the potion from the player's inventory
  506. RemoveItemFromInventory(potion, 1);
  507.  
  508. // Display message
  509. RaiseMessage("You drink a " + potion.Name);
  510.  
  511. // Monster gets their turn to attack
  512.  
  513. // Determine the amount of damage the monster does to the player
  514. int damageToPlayer = RandomNumberGenerator.NumberBetween(0, _currentMonster.MaximumDamage);
  515.  
  516. // Display message
  517. RaiseMessage("The " + _currentMonster.Name + " did " + damageToPlayer + " points of damage.");
  518.  
  519. // Subtract damage from player
  520. CurrentHitPoints -= damageToPlayer;
  521.  
  522. if (CurrentHitPoints <= 0)
  523. {
  524. // Display message
  525. RaiseMessage("The " + _currentMonster.Name + " killed you.");
  526.  
  527. // Move player to "Home"
  528. MoveHome();
  529. }
  530. }
  531.  
  532. private void MoveHome()
  533. {
  534. MoveTo(World.LocationByID(World.LOCATION_ID_HOME));
  535. }
  536.  
  537. public void MoveNorth()
  538. {
  539. if (CurrentLocation.LocationToNorth != null)
  540. {
  541. MoveTo(CurrentLocation.LocationToNorth);
  542. }
  543. }
  544.  
  545. public void MoveEast()
  546. {
  547. if (CurrentLocation.LocationToEast != null)
  548. {
  549. MoveTo(CurrentLocation.LocationToEast);
  550. }
  551. }
  552.  
  553. public void MoveSouth()
  554. {
  555. if (CurrentLocation.LocationToSouth != null)
  556. {
  557. MoveTo(CurrentLocation.LocationToSouth);
  558. }
  559. }
  560.  
  561. public void MoveWest()
  562. {
  563. if (CurrentLocation.LocationToWest != null)
  564. {
  565. MoveTo(CurrentLocation.LocationToWest);
  566. }
  567. }
  568.  
  569. public string ToXmlString()
  570. {
  571. XmlDocument playerData = new XmlDocument();
  572.  
  573. // Create the top-level XML node
  574. XmlNode player = playerData.CreateElement("Player");
  575. playerData.AppendChild(player);
  576.  
  577. // Create the "Stats" child node to hold the other player statistics nodes
  578. XmlNode stats = playerData.CreateElement("Stats");
  579. player.AppendChild(stats);
  580.  
  581. // Create the child nodes for the "Stats" node
  582. XmlNode currentHitPoints = playerData.CreateElement("CurrentHitPoints");
  583. currentHitPoints.AppendChild(playerData.CreateTextNode(this.CurrentHitPoints.ToString()));
  584. stats.AppendChild(currentHitPoints);
  585.  
  586. XmlNode maximumHitPoints = playerData.CreateElement("MaximumHitPoints");
  587. maximumHitPoints.AppendChild(playerData.CreateTextNode(this.MaximumHitPoints.ToString()));
  588. stats.AppendChild(maximumHitPoints);
  589.  
  590. XmlNode gold = playerData.CreateElement("Gold");
  591. gold.AppendChild(playerData.CreateTextNode(this.Gold.ToString()));
  592. stats.AppendChild(gold);
  593.  
  594. XmlNode experiencePoints = playerData.CreateElement("ExperiencePoints");
  595. experiencePoints.AppendChild(playerData.CreateTextNode(this.ExperiencePoints.ToString()));
  596. stats.AppendChild(experiencePoints);
  597.  
  598. XmlNode currentLocation = playerData.CreateElement("CurrentLocation");
  599. currentLocation.AppendChild(playerData.CreateTextNode(this.CurrentLocation.ID.ToString()));
  600. stats.AppendChild(currentLocation);
  601.  
  602. if (CurrentWeapon != null)
  603. {
  604. XmlNode currentWeapon = playerData.CreateElement("CurrentWeapon");
  605. currentWeapon.AppendChild(playerData.CreateTextNode(this.CurrentWeapon.ID.ToString()));
  606. stats.AppendChild(currentWeapon);
  607. }
  608.  
  609. // Create the "InventoryItems" child node to hold each InventoryItem node
  610. XmlNode inventoryItems = playerData.CreateElement("InventoryItems");
  611. player.AppendChild(inventoryItems);
  612.  
  613. // Create an "InventoryItem" node for each item in the player's inventory
  614. foreach (InventoryItem item in this.Inventory)
  615. {
  616. XmlNode inventoryItem = playerData.CreateElement("InventoryItem");
  617.  
  618. XmlAttribute idAttribute = playerData.CreateAttribute("ID");
  619. idAttribute.Value = item.Details.ID.ToString();
  620. inventoryItem.Attributes.Append(idAttribute);
  621.  
  622. XmlAttribute quantityAttribute = playerData.CreateAttribute("Quantity");
  623. quantityAttribute.Value = item.Quantity.ToString();
  624. inventoryItem.Attributes.Append(quantityAttribute);
  625.  
  626. inventoryItems.AppendChild(inventoryItem);
  627. }
  628.  
  629. // Create the "PlayerQuests" child node to hold each PlayerQuest node
  630. XmlNode playerQuests = playerData.CreateElement("PlayerQuests");
  631. player.AppendChild(playerQuests);
  632.  
  633. // Create a "PlayerQuest" node for each quest the player has acquired
  634. foreach (PlayerQuest quest in this.Quests)
  635. {
  636. XmlNode playerQuest = playerData.CreateElement("PlayerQuest");
  637.  
  638. XmlAttribute idAttribute = playerData.CreateAttribute("ID");
  639. idAttribute.Value = quest.Details.ID.ToString();
  640. playerQuest.Attributes.Append(idAttribute);
  641.  
  642. XmlAttribute isCompletedAttribute = playerData.CreateAttribute("IsCompleted");
  643. isCompletedAttribute.Value = quest.IsCompleted.ToString();
  644. playerQuest.Attributes.Append(isCompletedAttribute);
  645.  
  646. playerQuests.AppendChild(playerQuest);
  647. }
  648.  
  649. return playerData.InnerXml; // The XML document, as a string, so we can save the data to disk
  650. }
  651. }
  652. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement