SpellCombatAction.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // SpellCombatAction.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 Microsoft.Xna.Framework;
  12. using RolePlayingGameData;
  13. using Microsoft.Xna.Framework.Graphics;
  14. using Microsoft.Xna.Framework.Audio;
  15. #endregion
  16. namespace RolePlaying
  17. {
  18. /// <summary>
  19. /// A spell-casting combat action, including related data and calculations.
  20. /// </summary>
  21. class SpellCombatAction : CombatAction
  22. {
  23. #region State
  24. /// <summary>
  25. /// Returns true if the action is offensive, targeting the opponents.
  26. /// </summary>
  27. public override bool IsOffensive
  28. {
  29. get { return Spell.IsOffensive; }
  30. }
  31. /// <summary>
  32. /// Returns true if the character can use this action.
  33. /// </summary>
  34. public override bool IsCharacterValidUser
  35. {
  36. get
  37. {
  38. return (Spell.MagicPointCost <= Combatant.Statistics.MagicPoints);
  39. }
  40. }
  41. /// <summary>
  42. /// Returns true if this action requires a target.
  43. /// </summary>
  44. public override bool IsTargetNeeded
  45. {
  46. get { return true; }
  47. }
  48. #endregion
  49. #region Spell
  50. /// <summary>
  51. /// The spell used in this action.
  52. /// </summary>
  53. private Spell spell;
  54. /// <summary>
  55. /// The spell used in this action.
  56. /// </summary>
  57. public Spell Spell
  58. {
  59. get { return spell; }
  60. }
  61. /// <summary>
  62. /// The current position of the spell sprite.
  63. /// </summary>
  64. private Vector2 spellSpritePosition;
  65. /// <summary>
  66. /// Apply the action's spell to the given target.
  67. /// </summary>
  68. /// <returns>True if there was any effect on the target.</returns>
  69. private bool ApplySpell(Combatant spellTarget)
  70. {
  71. StatisticsValue effectStatistics = CalculateSpellDamage(combatant, spell);
  72. if (spell.IsOffensive)
  73. {
  74. // calculate the defense
  75. Int32Range defenseRange = spellTarget.Character.MagicDefenseRange +
  76. spellTarget.Statistics.MagicalDefense;
  77. Int32 defense = defenseRange.GenerateValue(Session.Random);
  78. // subtract the defense
  79. effectStatistics -= new StatisticsValue(defense,
  80. defense, defense, defense, defense, defense);
  81. // make sure that this only contains damage
  82. effectStatistics.ApplyMinimum(new StatisticsValue());
  83. // damage the target
  84. spellTarget.Damage(effectStatistics, spell.TargetDuration);
  85. }
  86. else
  87. {
  88. // make sure that this only contains healing
  89. effectStatistics.ApplyMinimum(new StatisticsValue());
  90. // heal the target
  91. spellTarget.Heal(effectStatistics, spell.TargetDuration);
  92. }
  93. return !effectStatistics.IsZero;
  94. }
  95. #endregion
  96. #region Spell Projectile Data
  97. /// <summary>
  98. /// The speed at which the projectile moves, in units per second.
  99. /// </summary>
  100. private const float projectileSpeed = 600f;
  101. /// <summary>
  102. /// The direction of the projectile.
  103. /// </summary>
  104. private Vector2 projectileDirection;
  105. /// <summary>
  106. /// The distance covered so far by the projectile.
  107. /// </summary>
  108. private float projectileDistanceCovered = 0f;
  109. /// <summary>
  110. /// The total distance between the original combatant position and the target.
  111. /// </summary>
  112. private float totalProjectileDistance;
  113. /// <summary>
  114. /// The sprite effect on the projectile, if any.
  115. /// </summary>
  116. private SpriteEffects projectileSpriteEffect = SpriteEffects.None;
  117. /// <summary>
  118. /// The sound effect cue for the traveling projectile.
  119. /// </summary>
  120. private Cue projectileCue;
  121. #endregion
  122. #region Combat Stage
  123. /// <summary>
  124. /// Starts a new combat stage. Called right after the stage changes.
  125. /// </summary>
  126. /// <remarks>The stage never changes into NotStarted.</remarks>
  127. protected override void StartStage()
  128. {
  129. switch (stage)
  130. {
  131. case CombatActionStage.Preparing: // called from Start()
  132. {
  133. // play the animations
  134. combatant.CombatSprite.PlayAnimation("SpellCast");
  135. spellSpritePosition = Combatant.Position;
  136. spell.SpellSprite.PlayAnimation("Creation");
  137. // remove the magic points
  138. Combatant.PayCostForSpell(spell);
  139. }
  140. break;
  141. case CombatActionStage.Advancing:
  142. {
  143. // play the animations
  144. spell.SpellSprite.PlayAnimation("Traveling");
  145. // calculate the projectile destination
  146. projectileDirection = Target.Position -
  147. Combatant.OriginalPosition;
  148. totalProjectileDistance = projectileDirection.Length();
  149. projectileDirection.Normalize();
  150. projectileDistanceCovered = 0f;
  151. // determine if the projectile is flipped
  152. if (Target.Position.X > Combatant.Position.X)
  153. {
  154. projectileSpriteEffect = SpriteEffects.FlipHorizontally;
  155. }
  156. else
  157. {
  158. projectileSpriteEffect = SpriteEffects.None;
  159. }
  160. // get the projectile's cue and play it
  161. projectileCue = AudioManager.GetCue(spell.TravelingCueName);
  162. if (projectileCue != null)
  163. {
  164. projectileCue.Play();
  165. }
  166. }
  167. break;
  168. case CombatActionStage.Executing:
  169. {
  170. // play the animation
  171. spell.SpellSprite.PlayAnimation("Impact");
  172. // stop the projectile sound effect
  173. if (projectileCue != null)
  174. {
  175. projectileCue.Stop(AudioStopOptions.Immediate);
  176. }
  177. // apply the spell effect to the primary target
  178. bool damagedAnyone = ApplySpell(Target);
  179. // apply the spell to the secondary targets
  180. foreach (Combatant targetCombatant in
  181. CombatEngine.SecondaryTargetedCombatants)
  182. {
  183. // skip dead or dying targets
  184. if (targetCombatant.IsDeadOrDying)
  185. {
  186. continue;
  187. }
  188. // apply the spell
  189. damagedAnyone |= ApplySpell(targetCombatant);
  190. }
  191. // play the impact sound effect
  192. if (damagedAnyone)
  193. {
  194. AudioManager.PlayCue(spell.ImpactCueName);
  195. if (spell.Overlay != null)
  196. {
  197. spell.Overlay.PlayAnimation(0);
  198. spell.Overlay.ResetAnimation();
  199. }
  200. }
  201. }
  202. break;
  203. case CombatActionStage.Returning:
  204. // play the animation
  205. combatant.CombatSprite.PlayAnimation("Idle");
  206. break;
  207. case CombatActionStage.Finishing:
  208. // play the animation
  209. combatant.CombatSprite.PlayAnimation("Idle");
  210. break;
  211. case CombatActionStage.Complete:
  212. // play the animation
  213. combatant.CombatSprite.PlayAnimation("Idle");
  214. // make sure that the overlay has stopped
  215. spell.Overlay.StopAnimation();
  216. break;
  217. }
  218. }
  219. /// <summary>
  220. /// Update the action for the current stage.
  221. /// </summary>
  222. /// <remarks>
  223. /// This function is guaranteed to be called at least once per stage.
  224. /// </remarks>
  225. protected override void UpdateCurrentStage(GameTime gameTime)
  226. {
  227. switch (stage)
  228. {
  229. case CombatActionStage.Advancing:
  230. if (projectileDistanceCovered < totalProjectileDistance)
  231. {
  232. projectileDistanceCovered += projectileSpeed *
  233. (float)gameTime.ElapsedGameTime.TotalSeconds;
  234. }
  235. spellSpritePosition = combatant.OriginalPosition +
  236. projectileDirection * projectileDistanceCovered;
  237. break;
  238. }
  239. }
  240. /// <summary>
  241. /// Returns true if the combat action is ready to proceed to the next stage.
  242. /// </summary>
  243. protected override bool IsReadyForNextStage
  244. {
  245. get
  246. {
  247. switch (stage)
  248. {
  249. case CombatActionStage.Preparing: // ready to advance?
  250. return (combatant.CombatSprite.IsPlaybackComplete &&
  251. spell.SpellSprite.IsPlaybackComplete);
  252. case CombatActionStage.Advancing: // ready to execute?
  253. if (spell.SpellSprite.IsPlaybackComplete ||
  254. (projectileDistanceCovered >= totalProjectileDistance))
  255. {
  256. projectileDistanceCovered = totalProjectileDistance;
  257. return true;
  258. }
  259. return false;
  260. case CombatActionStage.Executing: // ready to return?
  261. return spell.SpellSprite.IsPlaybackComplete;
  262. }
  263. // fall through to the base behavior
  264. return base.IsReadyForNextStage;
  265. }
  266. }
  267. #endregion
  268. #region Heuristic
  269. /// <summary>
  270. /// The heuristic used to compare actions of this type to similar ones.
  271. /// </summary>
  272. public override int Heuristic
  273. {
  274. get
  275. {
  276. return (Combatant.Statistics.MagicalOffense +
  277. Spell.TargetEffectRange.HealthPointsRange.Average);
  278. }
  279. }
  280. #endregion
  281. #region Initialization
  282. /// <summary>
  283. /// Constructs a new SpellCombatAction object.
  284. /// </summary>
  285. /// <param name="character">The combatant performing the action.</param>
  286. public SpellCombatAction(Combatant combatant, Spell spell)
  287. : base(combatant)
  288. {
  289. // check the parameter
  290. if (spell == null)
  291. {
  292. throw new ArgumentNullException("spell");
  293. }
  294. // assign the parameter
  295. this.spell = spell;
  296. this.adjacentTargets = this.spell.AdjacentTargets;
  297. }
  298. /// <summary>
  299. /// Start executing the combat action.
  300. /// </summary>
  301. public override void Start()
  302. {
  303. // play the creation sound effect
  304. AudioManager.PlayCue(spell.CreatingCueName);
  305. base.Start();
  306. }
  307. #endregion
  308. #region Updating
  309. /// <summary>
  310. /// Updates the action over time.
  311. /// </summary>
  312. public override void Update(GameTime gameTime)
  313. {
  314. // update the animations
  315. float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
  316. spell.SpellSprite.UpdateAnimation(elapsedSeconds);
  317. if (spell.Overlay != null)
  318. {
  319. spell.Overlay.UpdateAnimation(elapsedSeconds);
  320. if (!spell.Overlay.IsPlaybackComplete &&
  321. Target.CombatSprite.IsPlaybackComplete)
  322. {
  323. spell.Overlay.StopAnimation();
  324. }
  325. }
  326. base.Update(gameTime);
  327. }
  328. #endregion
  329. #region Drawing
  330. /// <summary>
  331. /// Draw any elements of the action that are independent of the character.
  332. /// </summary>
  333. public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
  334. {
  335. // draw the spell projectile
  336. if (!spell.SpellSprite.IsPlaybackComplete)
  337. {
  338. if (stage == CombatActionStage.Advancing)
  339. {
  340. spell.SpellSprite.Draw(spriteBatch, spellSpritePosition, 0f,
  341. projectileSpriteEffect);
  342. }
  343. else
  344. {
  345. spell.SpellSprite.Draw(spriteBatch, spellSpritePosition, 0f);
  346. }
  347. }
  348. // draw the spell overlay
  349. if (!spell.Overlay.IsPlaybackComplete)
  350. {
  351. spell.Overlay.Draw(spriteBatch, Target.Position, 0f);
  352. }
  353. base.Draw(gameTime, spriteBatch);
  354. }
  355. #endregion
  356. #region Static Calculation Methods
  357. /// <summary>
  358. /// Calculate the spell damage done by the given combatant and spell.
  359. /// </summary>
  360. public static StatisticsValue CalculateSpellDamage(Combatant combatant,
  361. Spell spell)
  362. {
  363. // check the parameters
  364. if (combatant == null)
  365. {
  366. throw new ArgumentNullException("combatant");
  367. }
  368. if (spell == null)
  369. {
  370. throw new ArgumentNullException("spell");
  371. }
  372. // get the magical offense from the character's class, gear, and bonuses
  373. // -- note that this includes stat buffs
  374. int magicalOffense = combatant.Statistics.MagicalOffense;
  375. // add the magical offense to the spell
  376. StatisticsValue damage =
  377. spell.TargetEffectRange.GenerateValue(Session.Random);
  378. damage.HealthPoints += (damage.HealthPoints != 0) ? magicalOffense : 0;
  379. damage.MagicPoints += (damage.MagicPoints != 0) ? magicalOffense : 0;
  380. damage.PhysicalOffense += (damage.PhysicalOffense != 0) ? magicalOffense : 0;
  381. damage.PhysicalDefense += (damage.PhysicalDefense != 0) ? magicalOffense : 0;
  382. damage.MagicalOffense += (damage.MagicalOffense != 0) ? magicalOffense : 0;
  383. damage.MagicalDefense += (damage.MagicalDefense != 0) ? magicalOffense : 0;
  384. // add in the spell damage
  385. return damage;
  386. }
  387. #endregion
  388. }
  389. }