2
0

FightingCharacter.cs 23 KB

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