FightingCharacter.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. //-----------------------------------------------------------------------------
  2. // FightingCharacter.cs
  3. //
  4. // Microsoft XNA Community Game Platform
  5. // Copyright (C) Microsoft Corporation. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Collections.Generic;
  9. using Microsoft.Xna.Framework.Content;
  10. namespace RolePlaying.Data
  11. {
  12. /// <summary>
  13. /// A character that engages in combat.
  14. /// </summary>
  15. public abstract class FightingCharacter : Character
  16. {
  17. /// <summary>
  18. /// The name of the character class.
  19. /// </summary>
  20. private string characterClassContentName;
  21. /// <summary>
  22. /// The name of the character class.
  23. /// </summary>
  24. public string CharacterClassContentName
  25. {
  26. get { return characterClassContentName; }
  27. set { characterClassContentName = value; }
  28. }
  29. /// <summary>
  30. /// The character class itself.
  31. /// </summary>
  32. private CharacterClass characterClass;
  33. /// <summary>
  34. /// The character class itself.
  35. /// </summary>
  36. [ContentSerializerIgnore]
  37. public CharacterClass CharacterClass
  38. {
  39. get { return characterClass; }
  40. set
  41. {
  42. characterClass = value;
  43. ResetBaseStatistics();
  44. }
  45. }
  46. /// <summary>
  47. /// The level of the character.
  48. /// </summary>
  49. private int characterLevel = 1;
  50. /// <summary>
  51. /// The level of the character.
  52. /// </summary>
  53. public int CharacterLevel
  54. {
  55. get { return characterLevel; }
  56. set
  57. {
  58. characterLevel = value;
  59. ResetBaseStatistics();
  60. spells = null;
  61. }
  62. }
  63. /// <summary>
  64. /// Returns true if the character is at the maximum level allowed by their class.
  65. /// </summary>
  66. public bool IsMaximumCharacterLevel
  67. {
  68. get
  69. {
  70. return characterLevel >= characterClass.LevelEntries.Count;
  71. }
  72. }
  73. /// <summary>
  74. /// The cached list of spells for this level.
  75. /// </summary>
  76. private List<Spell> spells = null;
  77. /// <summary>
  78. /// The cached list of spells for this level.
  79. /// </summary>
  80. [ContentSerializerIgnore]
  81. public List<Spell> Spells
  82. {
  83. get
  84. {
  85. if ((spells == null) && (characterClass != null))
  86. {
  87. spells = characterClass.GetAllSpellsForLevel(characterLevel);
  88. }
  89. return spells;
  90. }
  91. }
  92. /// <summary>
  93. /// The amount of experience points that this character has.
  94. /// </summary>
  95. private int experience;
  96. /// <summary>
  97. /// The amount of experience points that this character has.
  98. /// </summary>
  99. [ContentSerializerIgnore]
  100. public int Experience
  101. {
  102. get { return experience; }
  103. set
  104. {
  105. experience = value;
  106. while (experience >= ExperienceForNextLevel)
  107. {
  108. if (IsMaximumCharacterLevel)
  109. {
  110. break;
  111. }
  112. experience -= ExperienceForNextLevel;
  113. CharacterLevel++;
  114. }
  115. }
  116. }
  117. /// <summary>
  118. /// Returns the amount of experience necessary to reach the next character level.
  119. /// </summary>
  120. public int ExperienceForNextLevel
  121. {
  122. get
  123. {
  124. int checkIndex = Math.Min(characterLevel,
  125. characterClass.LevelEntries.Count) - 1;
  126. return characterClass.LevelEntries[checkIndex].ExperiencePoints;
  127. }
  128. }
  129. /// <summary>
  130. /// The base statistics of this character, from the character class and level.
  131. /// </summary>
  132. private StatisticsValue baseStatistics = new StatisticsValue();
  133. /// <summary>
  134. /// The base statistics of this character, from the character class and level.
  135. /// </summary>
  136. [ContentSerializerIgnore]
  137. public StatisticsValue BaseStatistics
  138. {
  139. get { return baseStatistics; }
  140. set { baseStatistics = value; }
  141. }
  142. /// <summary>
  143. /// Reset the character's base statistics.
  144. /// </summary>
  145. public void ResetBaseStatistics()
  146. {
  147. if (characterClass == null)
  148. {
  149. baseStatistics = new StatisticsValue();
  150. }
  151. else
  152. {
  153. baseStatistics = characterClass.GetStatisticsForLevel(characterLevel);
  154. }
  155. }
  156. /// <summary>
  157. /// The total statistics for this character.
  158. /// </summary>
  159. [ContentSerializerIgnore]
  160. public StatisticsValue CharacterStatistics
  161. {
  162. get { return baseStatistics + equipmentBuffStatistics; }
  163. }
  164. /// <summary>
  165. /// The equipment currently equipped on this character.
  166. /// </summary>
  167. private List<Equipment> equippedEquipment = new List<Equipment>();
  168. /// <summary>
  169. /// The equipment currently equipped on this character.
  170. /// </summary>
  171. [ContentSerializerIgnore]
  172. public List<Equipment> EquippedEquipment
  173. {
  174. get { return equippedEquipment; }
  175. }
  176. /// <summary>
  177. /// The content names of the equipment initially equipped on the character.
  178. /// </summary>
  179. private List<string> initialEquipmentContentNames = new List<string>();
  180. /// <summary>
  181. /// The content names of the equipment initially equipped on the character.
  182. /// </summary>
  183. public List<string> InitialEquipmentContentNames
  184. {
  185. get { return initialEquipmentContentNames; }
  186. set { initialEquipmentContentNames = value; }
  187. }
  188. /// <summary>
  189. /// Retrieve the currently equipped weapon.
  190. /// </summary>
  191. /// <remarks>There can only be one weapon equipped at the same time.</remarks>
  192. public Weapon GetEquippedWeapon()
  193. {
  194. return equippedEquipment.Find(delegate (Equipment equipment)
  195. { return equipment is Weapon; }) as Weapon;
  196. }
  197. /// <summary>
  198. /// Equip a new weapon.
  199. /// </summary>
  200. /// <returns>True if the weapon was equipped.</returns>
  201. public bool EquipWeapon(Weapon weapon, out Equipment oldEquipment)
  202. {
  203. // check the parameter
  204. if (weapon == null)
  205. {
  206. throw new ArgumentNullException("weapon");
  207. }
  208. // check equipment restrictions
  209. if (!weapon.CheckRestrictions(this))
  210. {
  211. oldEquipment = null;
  212. return false;
  213. }
  214. // unequip any existing weapon
  215. Weapon existingWeapon = GetEquippedWeapon();
  216. if (existingWeapon != null)
  217. {
  218. oldEquipment = existingWeapon;
  219. equippedEquipment.Remove(existingWeapon);
  220. }
  221. else
  222. {
  223. oldEquipment = null;
  224. }
  225. // add the weapon
  226. equippedEquipment.Add(weapon);
  227. // recalculate the statistic changes from equipment
  228. RecalculateEquipmentStatistics();
  229. RecalculateTotalTargetDamageRange();
  230. return true;
  231. }
  232. /// <summary>
  233. /// Remove any equipped weapons.
  234. /// </summary>
  235. public void UnequipWeapon()
  236. {
  237. equippedEquipment.RemoveAll(delegate (Equipment equipment)
  238. { return equipment is Weapon; });
  239. RecalculateEquipmentStatistics();
  240. }
  241. /// <summary>
  242. /// Retrieve the armor equipped in the given slot.
  243. /// </summary>
  244. public Armor GetEquippedArmor(Armor.ArmorSlot slot)
  245. {
  246. return equippedEquipment.Find(delegate (Equipment equipment)
  247. {
  248. Armor armor = equipment as Armor;
  249. return ((armor != null) && (armor.Slot == slot));
  250. }) as Armor;
  251. }
  252. /// <summary>
  253. /// Equip a new piece of armor.
  254. /// </summary>
  255. /// <returns>True if the armor could be equipped.</returns>
  256. public bool EquipArmor(Armor armor, out Equipment oldEquipment)
  257. {
  258. // check the parameter
  259. if (armor == null)
  260. {
  261. throw new ArgumentNullException("armor");
  262. }
  263. // check equipment requirements
  264. if (!armor.CheckRestrictions(this))
  265. {
  266. oldEquipment = null;
  267. return false;
  268. }
  269. // remove any armor equipped in this slot
  270. Armor equippedArmor = GetEquippedArmor(armor.Slot);
  271. if (equippedArmor != null)
  272. {
  273. oldEquipment = equippedArmor;
  274. equippedEquipment.Remove(equippedArmor);
  275. }
  276. else
  277. {
  278. oldEquipment = null;
  279. }
  280. // add the armor
  281. equippedEquipment.Add(armor);
  282. // recalcuate the total armor defense values
  283. RecalculateTotalDefenseRanges();
  284. // recalculate the statistics buffs from equipment
  285. RecalculateEquipmentStatistics();
  286. return true;
  287. }
  288. /// <summary>
  289. /// Unequip any armor in the given slot.
  290. /// </summary>
  291. public void UnequipArmor(Armor.ArmorSlot slot)
  292. {
  293. equippedEquipment.RemoveAll(delegate (Equipment equipment)
  294. {
  295. Armor armor = equipment as Armor;
  296. return ((armor != null) && (armor.Slot == slot));
  297. });
  298. RecalculateEquipmentStatistics();
  299. RecalculateTotalDefenseRanges();
  300. }
  301. /// <summary>
  302. /// Equip a new piece of equipment.
  303. /// </summary>
  304. /// <returns>True if the equipment could be equipped.</returns>
  305. public virtual bool Equip(Equipment equipment)
  306. {
  307. Equipment oldEquipment;
  308. return Equip(equipment, out oldEquipment);
  309. }
  310. /// <summary>
  311. /// Equip a new piece of equipment, specifying any equipment auto-unequipped.
  312. /// </summary>
  313. /// <returns>True if the equipment could be equipped.</returns>
  314. public virtual bool Equip(Equipment equipment, out Equipment oldEquipment)
  315. {
  316. if (equipment == null)
  317. {
  318. throw new ArgumentNullException("equipment");
  319. }
  320. if (equipment is Weapon)
  321. {
  322. return EquipWeapon(equipment as Weapon, out oldEquipment);
  323. }
  324. else if (equipment is Armor)
  325. {
  326. return EquipArmor(equipment as Armor, out oldEquipment);
  327. }
  328. else
  329. {
  330. oldEquipment = null;
  331. }
  332. return false;
  333. }
  334. /// <summary>
  335. /// Unequip a piece of equipment.
  336. /// </summary>
  337. /// <returns>True if the equipment could be unequipped.</returns>
  338. public virtual bool Unequip(Equipment equipment)
  339. {
  340. if (equipment == null)
  341. {
  342. throw new ArgumentNullException("equipment");
  343. }
  344. if (equippedEquipment.Remove(equipment))
  345. {
  346. RecalculateEquipmentStatistics();
  347. RecalculateTotalTargetDamageRange();
  348. RecalculateTotalDefenseRanges();
  349. return true;
  350. }
  351. return false;
  352. }
  353. /// <summary>
  354. /// The total statistics changes (buffs) from all equipped equipment.
  355. /// </summary>
  356. private StatisticsValue equipmentBuffStatistics = new StatisticsValue();
  357. /// <summary>
  358. /// The total statistics changes (buffs) from all equipped equipment.
  359. /// </summary>
  360. [ContentSerializerIgnore]
  361. public StatisticsValue EquipmentBuffStatistics
  362. {
  363. get { return equipmentBuffStatistics; }
  364. set { equipmentBuffStatistics = value; }
  365. }
  366. /// <summary>
  367. /// Recalculate the character's equipment-buff statistics.
  368. /// </summary>
  369. public void RecalculateEquipmentStatistics()
  370. {
  371. // start from scratch
  372. equipmentBuffStatistics = new StatisticsValue();
  373. // add the statistics for each piece of equipped equipment
  374. foreach (Equipment equipment in equippedEquipment)
  375. {
  376. equipmentBuffStatistics += equipment.OwnerBuffStatistics;
  377. }
  378. }
  379. /// <summary>
  380. /// The target damage range for this character, aggregated from all weapons.
  381. /// </summary>
  382. private Int32Range targetDamageRange;
  383. /// <summary>
  384. /// The health damage range for this character, aggregated from all weapons.
  385. /// </summary>
  386. public Int32Range TargetDamageRange
  387. {
  388. get { return targetDamageRange; }
  389. }
  390. /// <summary>
  391. /// Recalculate the character's defense ranges from all of their armor.
  392. /// </summary>
  393. public void RecalculateTotalTargetDamageRange()
  394. {
  395. // set the initial damage range to the physical offense statistic
  396. targetDamageRange = new Int32Range();
  397. // add each weapon's target damage range
  398. foreach (Equipment equipment in equippedEquipment)
  399. {
  400. Weapon weapon = equipment as Weapon;
  401. if (weapon != null)
  402. {
  403. targetDamageRange += weapon.TargetDamageRange;
  404. }
  405. }
  406. }
  407. /// <summary>
  408. /// The health defense range for this character, aggregated from all armor.
  409. /// </summary>
  410. private Int32Range healthDefenseRange;
  411. /// <summary>
  412. /// The health defense range for this character, aggregated from all armor.
  413. /// </summary>
  414. public Int32Range HealthDefenseRange
  415. {
  416. get { return healthDefenseRange; }
  417. }
  418. /// <summary>
  419. /// The magic defense range for this character, aggregated from all armor.
  420. /// </summary>
  421. private Int32Range magicDefenseRange;
  422. /// <summary>
  423. /// The magic defense range for this character, aggregated from all armor.
  424. /// </summary>
  425. public Int32Range MagicDefenseRange
  426. {
  427. get { return magicDefenseRange; }
  428. }
  429. /// <summary>
  430. /// Recalculate the character's defense ranges from all of their armor.
  431. /// </summary>
  432. public void RecalculateTotalDefenseRanges()
  433. {
  434. // set the initial damage ranges based on character statistics
  435. healthDefenseRange = new Int32Range();
  436. magicDefenseRange = new Int32Range();
  437. // add the defense ranges for each piece of equipped armor
  438. foreach (Equipment equipment in equippedEquipment)
  439. {
  440. Armor armor = equipment as Armor;
  441. if (armor != null)
  442. {
  443. healthDefenseRange += armor.OwnerHealthDefenseRange;
  444. magicDefenseRange += armor.OwnerMagicDefenseRange;
  445. }
  446. }
  447. }
  448. /// <summary>
  449. /// The gear in this character's inventory (and not equipped).
  450. /// </summary>
  451. private List<ContentEntry<Gear>> inventory = new List<ContentEntry<Gear>>();
  452. /// <summary>
  453. /// The gear in this character's inventory (and not equipped).
  454. /// </summary>
  455. public List<ContentEntry<Gear>> Inventory
  456. {
  457. get { return inventory; }
  458. set { inventory = value; }
  459. }
  460. /// <summary>
  461. /// The animating sprite for the combat view of this character.
  462. /// </summary>
  463. private AnimatingSprite combatSprite;
  464. /// <summary>
  465. /// The animating sprite for the combat view of this character.
  466. /// </summary>
  467. public AnimatingSprite CombatSprite
  468. {
  469. get { return combatSprite; }
  470. set { combatSprite = value; }
  471. }
  472. /// <summary>
  473. /// Reset the animations for this character.
  474. /// </summary>
  475. public override void ResetAnimation(bool isWalking)
  476. {
  477. base.ResetAnimation(isWalking);
  478. if (combatSprite != null)
  479. {
  480. combatSprite.PlayAnimation("Idle");
  481. }
  482. }
  483. /// <summary>
  484. /// The default animation interval for the combat map sprite.
  485. /// </summary>
  486. private int combatAnimationInterval = 100;
  487. /// <summary>
  488. /// The default animation interval for the combat map sprite.
  489. /// </summary>
  490. [ContentSerializer(Optional = true)]
  491. public int CombatAnimationInterval
  492. {
  493. get { return combatAnimationInterval; }
  494. set { combatAnimationInterval = value; }
  495. }
  496. /// <summary>
  497. /// Add the standard character walk animations to this character.
  498. /// </summary>
  499. private void AddStandardCharacterCombatAnimations()
  500. {
  501. if (combatSprite != null)
  502. {
  503. combatSprite.AddAnimation(new Animation("Idle", 37, 42,
  504. CombatAnimationInterval, true));
  505. combatSprite.AddAnimation(new Animation("Walk", 25, 30,
  506. CombatAnimationInterval, true));
  507. combatSprite.AddAnimation(new Animation("Attack", 1, 6,
  508. CombatAnimationInterval, false));
  509. combatSprite.AddAnimation(new Animation("SpellCast", 31, 36,
  510. CombatAnimationInterval, false));
  511. combatSprite.AddAnimation(new Animation("Defend", 13, 18,
  512. CombatAnimationInterval, false));
  513. combatSprite.AddAnimation(new Animation("Dodge", 13, 18,
  514. CombatAnimationInterval, false));
  515. combatSprite.AddAnimation(new Animation("Hit", 19, 24,
  516. CombatAnimationInterval, false));
  517. combatSprite.AddAnimation(new Animation("Die", 7, 12,
  518. CombatAnimationInterval, false));
  519. }
  520. }
  521. /// <summary>
  522. /// Reads a FightingCharacter object from the content pipeline.
  523. /// </summary>
  524. public class FightingCharacterReader : ContentTypeReader<FightingCharacter>
  525. {
  526. /// <summary>
  527. /// Reads a FightingCharacter object from the content pipeline.
  528. /// </summary>
  529. protected override FightingCharacter Read(ContentReader input,
  530. FightingCharacter existingInstance)
  531. {
  532. FightingCharacter fightingCharacter = existingInstance;
  533. if (fightingCharacter == null)
  534. {
  535. throw new ArgumentNullException("existingInstance");
  536. }
  537. input.ReadRawObject<Character>(fightingCharacter as Character);
  538. fightingCharacter.CharacterClassContentName = input.ReadString();
  539. fightingCharacter.CharacterLevel = input.ReadInt32();
  540. fightingCharacter.InitialEquipmentContentNames.AddRange(
  541. input.ReadObject<List<string>>());
  542. fightingCharacter.Inventory.AddRange(
  543. input.ReadObject<List<ContentEntry<Gear>>>());
  544. fightingCharacter.CombatAnimationInterval = input.ReadInt32();
  545. fightingCharacter.CombatSprite = input.ReadObject<AnimatingSprite>();
  546. fightingCharacter.AddStandardCharacterCombatAnimations();
  547. fightingCharacter.ResetAnimation(false);
  548. // load the character class
  549. fightingCharacter.CharacterClass =
  550. input.ContentManager.Load<CharacterClass>(
  551. System.IO.Path.Combine("CharacterClasses",
  552. fightingCharacter.CharacterClassContentName));
  553. // populate the equipment list
  554. foreach (string gearName in
  555. fightingCharacter.InitialEquipmentContentNames)
  556. {
  557. fightingCharacter.Equip(input.ContentManager.Load<Equipment>(
  558. System.IO.Path.Combine("Gear", gearName)));
  559. }
  560. fightingCharacter.RecalculateEquipmentStatistics();
  561. fightingCharacter.RecalculateTotalTargetDamageRange();
  562. fightingCharacter.RecalculateTotalDefenseRanges();
  563. // populate the inventory based on the content names
  564. foreach (ContentEntry<Gear> inventoryEntry in
  565. fightingCharacter.Inventory)
  566. {
  567. inventoryEntry.Content = input.ContentManager.Load<Gear>(
  568. System.IO.Path.Combine("Gear", inventoryEntry.ContentName));
  569. }
  570. return fightingCharacter;
  571. }
  572. }
  573. }
  574. }