FightingCharacter.cs 21 KB

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