CombatEngine.cs 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // CombatEngine.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 System.IO;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Content;
  15. using Microsoft.Xna.Framework.Graphics;
  16. using Microsoft.Xna.Framework.Input;
  17. using RolePlayingGameData;
  18. #endregion
  19. namespace RolePlaying
  20. {
  21. /// <summary>
  22. /// The runtime execution engine for the combat system.
  23. /// </summary>
  24. class CombatEngine
  25. {
  26. #region Singleton
  27. /// <summary>
  28. /// The singleton of the combat engine.
  29. /// </summary>
  30. private static CombatEngine singleton = null;
  31. /// <summary>
  32. /// Check to see if there is a combat going on, and throw an exception if not.
  33. /// </summary>
  34. private static void CheckSingleton()
  35. {
  36. if (singleton == null)
  37. {
  38. throw new InvalidOperationException(
  39. "There is no active combat at this time.");
  40. }
  41. }
  42. #endregion
  43. #region State
  44. /// <summary>
  45. /// If true, the combat engine is active and the user is in combat.
  46. /// </summary>
  47. public static bool IsActive
  48. {
  49. get { return (singleton != null); }
  50. }
  51. /// <summary>
  52. /// If true, it is currently the players' turn.
  53. /// </summary>
  54. private bool isPlayersTurn;
  55. /// <summary>
  56. /// If true, it is currently the players' turn.
  57. /// </summary>
  58. public static bool IsPlayersTurn
  59. {
  60. get
  61. {
  62. CheckSingleton();
  63. return singleton.isPlayersTurn;
  64. }
  65. }
  66. #endregion
  67. #region Rewards
  68. /// <summary>
  69. /// The fixed combat used to generate this fight, if any.
  70. /// </summary>
  71. /// <remarks>
  72. /// Used for rewards. Null means it was a random fight with no special rewards.
  73. /// </remarks>
  74. private MapEntry<FixedCombat> fixedCombatEntry;
  75. /// <summary>
  76. /// The fixed combat used to generate this fight, if any.
  77. /// </summary>
  78. /// <remarks>
  79. /// Used for rewards. Null means it was a random fight with no special rewards.
  80. /// </remarks>
  81. public static MapEntry<FixedCombat> FixedCombatEntry
  82. {
  83. get { return (singleton == null ? null : singleton.fixedCombatEntry); }
  84. }
  85. #endregion
  86. #region Players
  87. /// <summary>
  88. /// The players involved in the current combat.
  89. /// </summary>
  90. private List<CombatantPlayer> players = null;
  91. /// <summary>
  92. /// The players involved in the current combat.
  93. /// </summary>
  94. public static List<CombatantPlayer> Players
  95. {
  96. get
  97. {
  98. CheckSingleton();
  99. return singleton.players;
  100. }
  101. }
  102. private int highlightedPlayer;
  103. /// <summary>
  104. /// The positions of the players on screen.
  105. /// </summary>
  106. private static readonly Vector2[] PlayerPositions = new Vector2[5]
  107. {
  108. new Vector2(850f, 345f),
  109. new Vector2(980f, 260f),
  110. new Vector2(940f, 440f),
  111. new Vector2(1100f, 200f),
  112. new Vector2(1100f, 490f)
  113. };
  114. /// <summary>
  115. /// Start the given player's combat turn.
  116. /// </summary>
  117. private void BeginPlayerTurn(CombatantPlayer player)
  118. {
  119. // check the parameter
  120. if (player == null)
  121. {
  122. throw new ArgumentNullException("player");
  123. }
  124. // set the highlight sprite
  125. highlightedCombatant = player;
  126. primaryTargetedCombatant = null;
  127. secondaryTargetedCombatants.Clear();
  128. Session.Hud.ActionText = "Choose an Action";
  129. }
  130. /// <summary>
  131. /// Begin the players' turn in this combat round.
  132. /// </summary>
  133. private void BeginPlayersTurn()
  134. {
  135. // set the player-turn
  136. isPlayersTurn = true;
  137. // reset each player for the next combat turn
  138. foreach (CombatantPlayer player in players)
  139. {
  140. // reset the animation of living players
  141. if (!player.IsDeadOrDying)
  142. {
  143. player.State = Character.CharacterState.Idle;
  144. }
  145. // reset the turn-taken flag
  146. player.IsTurnTaken = false;
  147. // clear the combat action
  148. player.CombatAction = null;
  149. // advance each player
  150. player.AdvanceRound();
  151. }
  152. // set the action text on the HUD
  153. Session.Hud.ActionText = "Your Party's Turn";
  154. // find the first player who is alive
  155. highlightedPlayer = 0;
  156. CombatantPlayer firstPlayer = players[highlightedPlayer];
  157. while (firstPlayer.IsTurnTaken || firstPlayer.IsDeadOrDying)
  158. {
  159. highlightedPlayer = (highlightedPlayer + 1) % players.Count;
  160. firstPlayer = players[highlightedPlayer];
  161. }
  162. // start the first player's turn
  163. BeginPlayerTurn(firstPlayer);
  164. }
  165. /// <summary>
  166. /// Check for whether all players have taken their turn.
  167. /// </summary>
  168. private bool IsPlayersTurnComplete
  169. {
  170. get
  171. {
  172. return players.TrueForAll(delegate(CombatantPlayer player)
  173. {
  174. return (player.IsTurnTaken || player.IsDeadOrDying);
  175. });
  176. }
  177. }
  178. /// <summary>
  179. /// Check for whether the players have been wiped out and defeated.
  180. /// </summary>
  181. private bool ArePlayersDefeated
  182. {
  183. get
  184. {
  185. return players.TrueForAll(delegate(CombatantPlayer player)
  186. {
  187. return (player.State == Character.CharacterState.Dead);
  188. });
  189. }
  190. }
  191. /// <summary>
  192. /// Retrieves the first living player, if any.
  193. /// </summary>
  194. private CombatantPlayer FirstPlayerTarget
  195. {
  196. get
  197. {
  198. // if there are no living players, then this is moot
  199. if (ArePlayersDefeated)
  200. {
  201. return null;
  202. }
  203. int playerIndex = 0;
  204. while ((playerIndex < players.Count) &&
  205. players[playerIndex].IsDeadOrDying)
  206. {
  207. playerIndex++;
  208. }
  209. return players[playerIndex];
  210. }
  211. }
  212. #endregion
  213. #region Monsters
  214. /// <summary>
  215. /// The monsters involved in the current combat.
  216. /// </summary>
  217. private List<CombatantMonster> monsters = null;
  218. /// <summary>
  219. /// The monsters involved in the current combat.
  220. /// </summary>
  221. public static List<CombatantMonster> Monsters
  222. {
  223. get
  224. {
  225. CheckSingleton();
  226. return singleton.monsters;
  227. }
  228. }
  229. /// <summary>
  230. /// The positions of the monsters on the screen.
  231. /// </summary>
  232. private static readonly Vector2[] MonsterPositions = new Vector2[5]
  233. {
  234. new Vector2(480f, 345f),
  235. new Vector2(345f, 260f),
  236. new Vector2(370f, 440f),
  237. new Vector2(225f, 200f),
  238. new Vector2(225f, 490f)
  239. };
  240. /// <summary>
  241. /// Start the given player's combat turn.
  242. /// </summary>
  243. private void BeginMonsterTurn(CombatantMonster monster)
  244. {
  245. // if it's null, find a random living monster who has yet to take their turn
  246. if (monster == null)
  247. {
  248. // don't bother if all monsters have finished
  249. if (IsMonstersTurnComplete)
  250. {
  251. return;
  252. }
  253. // pick random living monsters who haven't taken their turn
  254. do
  255. {
  256. monster = monsters[Session.Random.Next(monsters.Count)];
  257. }
  258. while (monster.IsTurnTaken || monster.IsDeadOrDying);
  259. }
  260. // set the highlight sprite
  261. highlightedCombatant = monster;
  262. primaryTargetedCombatant = null;
  263. secondaryTargetedCombatants.Clear();
  264. // choose the action immediate
  265. monster.CombatAction = monster.ArtificialIntelligence.ChooseAction();
  266. }
  267. /// <summary>
  268. /// Begin the monsters' turn in this combat round.
  269. /// </summary>
  270. private void BeginMonstersTurn()
  271. {
  272. // set the monster-turn
  273. isPlayersTurn = false;
  274. // reset each monster for the next combat turn
  275. foreach (CombatantMonster monster in monsters)
  276. {
  277. // reset the animations back to idle
  278. monster.Character.State = Character.CharacterState.Idle;
  279. // reset the turn-taken flag
  280. monster.IsTurnTaken = false;
  281. // clear the combat action
  282. monster.CombatAction = null;
  283. // advance the combatants
  284. monster.AdvanceRound();
  285. }
  286. // set the action text on the HUD
  287. Session.Hud.ActionText = "Enemy Party's Turn";
  288. // start a Session.Random monster's turn
  289. BeginMonsterTurn(null);
  290. }
  291. /// <summary>
  292. /// Check for whether all monsters have taken their turn.
  293. /// </summary>
  294. private bool IsMonstersTurnComplete
  295. {
  296. get
  297. {
  298. return monsters.TrueForAll(delegate(CombatantMonster monster)
  299. {
  300. return (monster.IsTurnTaken || monster.IsDeadOrDying);
  301. });
  302. }
  303. }
  304. /// <summary>
  305. /// Check for whether the monsters have been wiped out and defeated.
  306. /// </summary>
  307. private bool AreMonstersDefeated
  308. {
  309. get
  310. {
  311. return monsters.TrueForAll(delegate(CombatantMonster monster)
  312. {
  313. return (monster.State == Character.CharacterState.Dead);
  314. });
  315. }
  316. }
  317. /// <summary>
  318. /// Retrieves the first living monster, if any.
  319. /// </summary>
  320. private CombatantMonster FirstMonsterTarget
  321. {
  322. get
  323. {
  324. // if there are no living monsters, then this is moot
  325. if (AreMonstersDefeated)
  326. {
  327. return null;
  328. }
  329. int monsterIndex = 0;
  330. while ((monsterIndex < monsters.Count) &&
  331. monsters[monsterIndex].IsDeadOrDying)
  332. {
  333. monsterIndex++;
  334. }
  335. return monsters[monsterIndex];
  336. }
  337. }
  338. #endregion
  339. #region Targeting
  340. /// <summary>
  341. /// The currently highlighted player, if any.
  342. /// </summary>
  343. private Combatant highlightedCombatant;
  344. /// <summary>
  345. /// The currently highlighted player, if any.
  346. /// </summary>
  347. public static Combatant HighlightedCombatant
  348. {
  349. get
  350. {
  351. CheckSingleton();
  352. return singleton.highlightedCombatant;
  353. }
  354. }
  355. /// <summary>
  356. /// The current primary target, if any.
  357. /// </summary>
  358. private Combatant primaryTargetedCombatant;
  359. /// <summary>
  360. /// The current primary target, if any.
  361. /// </summary>
  362. public static Combatant PrimaryTargetedCombatant
  363. {
  364. get
  365. {
  366. CheckSingleton();
  367. return singleton.primaryTargetedCombatant;
  368. }
  369. }
  370. /// <summary>
  371. /// The current secondary targets, if any.
  372. /// </summary>
  373. private List<Combatant> secondaryTargetedCombatants = new List<Combatant>();
  374. /// <summary>
  375. /// The current secondary targets, if any.
  376. /// </summary>
  377. public static List<Combatant> SecondaryTargetedCombatants
  378. {
  379. get
  380. {
  381. CheckSingleton();
  382. return singleton.secondaryTargetedCombatants;
  383. }
  384. }
  385. /// <summary>
  386. /// Retrieves the first living enemy, if any.
  387. /// </summary>
  388. public static Combatant FirstEnemyTarget
  389. {
  390. get
  391. {
  392. CheckSingleton();
  393. if (IsPlayersTurn)
  394. {
  395. return singleton.FirstMonsterTarget;
  396. }
  397. else
  398. {
  399. return singleton.FirstPlayerTarget;
  400. }
  401. }
  402. }
  403. /// <summary>
  404. /// Retrieves the first living ally, if any.
  405. /// </summary>
  406. public static Combatant FirstAllyTarget
  407. {
  408. get
  409. {
  410. CheckSingleton();
  411. if (IsPlayersTurn)
  412. {
  413. return singleton.FirstPlayerTarget;
  414. }
  415. else
  416. {
  417. return singleton.FirstMonsterTarget;
  418. }
  419. }
  420. }
  421. /// <summary>
  422. /// Set the primary and any secondary targets.
  423. /// </summary>
  424. /// <param name="primaryTarget">The desired primary target.</param>
  425. /// <param name="adjacentTargets">
  426. /// The number of simultaneous, adjacent targets affected by this spell.
  427. /// </param>
  428. private void SetTargets(Combatant primaryTarget, int adjacentTargets)
  429. {
  430. // set the primary target
  431. this.primaryTargetedCombatant = primaryTarget;
  432. // set any secondary targets
  433. this.secondaryTargetedCombatants.Clear();
  434. if ((primaryTarget != null) && (adjacentTargets > 0))
  435. {
  436. // find out which side is targeted
  437. bool isPlayerTarget = primaryTarget is CombatantPlayer;
  438. // find the index
  439. int primaryTargetIndex = 0;
  440. if (isPlayerTarget)
  441. {
  442. primaryTargetIndex = players.FindIndex(
  443. delegate(CombatantPlayer player)
  444. {
  445. return (player == primaryTarget);
  446. });
  447. }
  448. else
  449. {
  450. primaryTargetIndex = monsters.FindIndex(
  451. delegate(CombatantMonster monster)
  452. {
  453. return (monster == primaryTarget);
  454. });
  455. }
  456. // add the surrounding indices
  457. for (int i = 1; i <= adjacentTargets; i++)
  458. {
  459. int leftIndex = primaryTargetIndex - i;
  460. if (leftIndex >= 0)
  461. {
  462. secondaryTargetedCombatants.Add(
  463. isPlayerTarget ? players[leftIndex] as Combatant :
  464. monsters[leftIndex] as Combatant);
  465. }
  466. int rightIndex = primaryTargetIndex + i;
  467. if (rightIndex < (isPlayerTarget ? players.Count : monsters.Count))
  468. {
  469. secondaryTargetedCombatants.Add(
  470. isPlayerTarget ? players[rightIndex] as Combatant :
  471. monsters[rightIndex] as Combatant);
  472. }
  473. }
  474. }
  475. }
  476. #endregion
  477. #region Damage Sprites
  478. /// <summary>
  479. /// A combat effect sprite, typically used for damage or healing numbers.
  480. /// </summary>
  481. private class CombatEffect
  482. {
  483. #region Position
  484. /// <summary>
  485. /// The starting position of the effect on the screen.
  486. /// </summary>
  487. public Vector2 OriginalPosition;
  488. /// <summary>
  489. /// The current position of the effect on the screen.
  490. /// </summary>
  491. protected Vector2 position;
  492. /// <summary>
  493. /// The current position of the effect on the screen.
  494. /// </summary>
  495. public Vector2 Position
  496. {
  497. get { return position; }
  498. }
  499. #endregion
  500. #region Text
  501. /// <summary>
  502. /// The text that appears on top of the effect.
  503. /// </summary>
  504. protected string text = String.Empty;
  505. /// <summary>
  506. /// The text that appears on top of the effect.
  507. /// </summary>
  508. public string Text
  509. {
  510. get { return text; }
  511. set
  512. {
  513. text = value;
  514. // recalculate the origin
  515. if (String.IsNullOrEmpty(text))
  516. {
  517. textOrigin = Vector2.Zero;
  518. }
  519. else
  520. {
  521. Vector2 textSize = Fonts.DamageFont.MeasureString(text);
  522. textOrigin = new Vector2(
  523. (float)Math.Ceiling(textSize.X / 2f),
  524. (float)Math.Ceiling(textSize.Y / 2f));
  525. }
  526. }
  527. }
  528. /// <summary>
  529. /// The drawing origin of the text on the effect.
  530. /// </summary>
  531. private Vector2 textOrigin = Vector2.Zero;
  532. #endregion
  533. #region Rise Animation
  534. /// <summary>
  535. /// The speed at which the effect rises on the screen.
  536. /// </summary>
  537. const int risePerSecond = 100;
  538. /// <summary>
  539. /// The amount which the effect rises on the screen.
  540. /// </summary>
  541. const int riseMaximum = 80;
  542. /// <summary>
  543. /// The amount which the effect has already risen on the screen.
  544. /// </summary>
  545. public float Rise = 0;
  546. /// <summary>
  547. /// If true, the effect has finished rising.
  548. /// </summary>
  549. private bool isRiseComplete = false;
  550. /// <summary>
  551. /// If true, the effect has finished rising.
  552. /// </summary>
  553. public bool IsRiseComplete
  554. {
  555. get { return isRiseComplete; }
  556. }
  557. #endregion
  558. #region Updating
  559. /// <summary>
  560. /// Updates the combat effect.
  561. /// </summary>
  562. /// <param name="elapsedSeconds">
  563. /// The number of seconds elapsed since the last update.
  564. /// </param>
  565. public virtual void Update(float elapsedSeconds)
  566. {
  567. if (!isRiseComplete)
  568. {
  569. Rise += ((float)risePerSecond * elapsedSeconds);
  570. if (Rise > riseMaximum)
  571. {
  572. Rise = riseMaximum;
  573. isRiseComplete = true;
  574. }
  575. position = new Vector2(
  576. OriginalPosition.X,
  577. OriginalPosition.Y - Rise);
  578. }
  579. }
  580. #endregion
  581. #region Drawing
  582. /// <summary>
  583. /// Draw the combat effect.
  584. /// </summary>
  585. /// <param name="spriteBatch">The SpriteBatch used to draw.</param>
  586. /// <param name="texture">The texture for the effect.</param>
  587. public virtual void Draw(SpriteBatch spriteBatch, Texture2D texture)
  588. {
  589. // check the parameter
  590. if (spriteBatch == null)
  591. {
  592. return;
  593. }
  594. // draw the texture
  595. if (texture != null)
  596. {
  597. spriteBatch.Draw(texture, position, null, Color.White, 0f,
  598. new Vector2(texture.Width / 2, texture.Height / 2), 1f,
  599. SpriteEffects.None, 0.3f * (float)Rise / 200f);
  600. }
  601. // draw the text
  602. if (!String.IsNullOrEmpty(Text))
  603. {
  604. spriteBatch.DrawString(Fonts.DamageFont, text, position,
  605. Color.White, 0f, new Vector2(textOrigin.X, textOrigin.Y), 1f,
  606. SpriteEffects.None, 0.2f * (float)Rise / 200f);
  607. }
  608. }
  609. #endregion
  610. }
  611. #region Damage Effects
  612. /// <summary>
  613. /// The sprite texture for all damage combat effects.
  614. /// </summary>
  615. private Texture2D damageCombatEffectTexture;
  616. /// <summary>
  617. /// All current damage combat effects.
  618. /// </summary>
  619. private List<CombatEffect> damageCombatEffects = new List<CombatEffect>();
  620. /// <summary>
  621. /// Adds a new damage combat effect to the scene.
  622. /// </summary>
  623. /// <param name="position">The position that the effect starts at.</param>
  624. /// <param name="damage">The damage statistics.</param>
  625. public static void AddNewDamageEffects(Vector2 position,
  626. StatisticsValue damage)
  627. {
  628. int startingRise = 0;
  629. CheckSingleton();
  630. if (damage.HealthPoints != 0)
  631. {
  632. CombatEffect combatEffect = new CombatEffect();
  633. combatEffect.OriginalPosition = position;
  634. combatEffect.Text = "HP\n" + damage.HealthPoints.ToString();
  635. combatEffect.Rise = startingRise;
  636. startingRise -= 5;
  637. singleton.damageCombatEffects.Add(combatEffect);
  638. }
  639. if (damage.MagicPoints != 0)
  640. {
  641. CombatEffect combatEffect = new CombatEffect();
  642. combatEffect.OriginalPosition = position;
  643. combatEffect.Text = "MP\n" + damage.MagicPoints.ToString();
  644. combatEffect.Rise = startingRise;
  645. startingRise -= 5;
  646. singleton.damageCombatEffects.Add(combatEffect);
  647. }
  648. if (damage.PhysicalOffense != 0)
  649. {
  650. CombatEffect combatEffect = new CombatEffect();
  651. combatEffect.OriginalPosition = position;
  652. combatEffect.Text = "PO\n" + damage.PhysicalOffense.ToString();
  653. combatEffect.Rise = startingRise;
  654. startingRise -= 5;
  655. singleton.damageCombatEffects.Add(combatEffect);
  656. }
  657. if (damage.PhysicalDefense != 0)
  658. {
  659. CombatEffect combatEffect = new CombatEffect();
  660. combatEffect.OriginalPosition = position;
  661. combatEffect.Text = "PD\n" + damage.PhysicalDefense.ToString();
  662. combatEffect.Rise = startingRise;
  663. startingRise -= 5;
  664. singleton.damageCombatEffects.Add(combatEffect);
  665. }
  666. if (damage.MagicalOffense != 0)
  667. {
  668. CombatEffect combatEffect = new CombatEffect();
  669. combatEffect.OriginalPosition = position;
  670. combatEffect.Text = "MO\n" + damage.MagicalOffense.ToString();
  671. combatEffect.Rise = startingRise;
  672. startingRise -= 5;
  673. singleton.damageCombatEffects.Add(combatEffect);
  674. }
  675. if (damage.MagicalDefense != 0)
  676. {
  677. CombatEffect combatEffect = new CombatEffect();
  678. combatEffect.OriginalPosition = position;
  679. combatEffect.Text = "MD\n" + damage.MagicalDefense.ToString();
  680. combatEffect.Rise = startingRise;
  681. startingRise -= 5;
  682. singleton.damageCombatEffects.Add(combatEffect);
  683. }
  684. }
  685. #endregion
  686. #region Healing Combat Effects
  687. /// <summary>
  688. /// The sprite texture for all healing combat effects.
  689. /// </summary>
  690. private Texture2D healingCombatEffectTexture;
  691. /// <summary>
  692. /// All current healing combat effects.
  693. /// </summary>
  694. private List<CombatEffect> healingCombatEffects = new List<CombatEffect>();
  695. /// <summary>
  696. /// Adds a new healing combat effect to the scene.
  697. /// </summary>
  698. /// <param name="position">The position that the effect starts at.</param>
  699. /// <param name="damage">The healing statistics.</param>
  700. public static void AddNewHealingEffects(Vector2 position,
  701. StatisticsValue healing)
  702. {
  703. int startingRise = 0;
  704. CheckSingleton();
  705. if (healing.HealthPoints != 0)
  706. {
  707. CombatEffect combatEffect = new CombatEffect();
  708. combatEffect.OriginalPosition = position;
  709. combatEffect.Text = "HP\n" + healing.HealthPoints.ToString();
  710. combatEffect.Rise = startingRise;
  711. startingRise -= 5;
  712. singleton.healingCombatEffects.Add(combatEffect);
  713. }
  714. if (healing.MagicPoints != 0)
  715. {
  716. CombatEffect combatEffect = new CombatEffect();
  717. combatEffect.OriginalPosition = position;
  718. combatEffect.Text = "MP\n" + healing.MagicPoints.ToString();
  719. combatEffect.Rise = startingRise;
  720. startingRise -= 5;
  721. singleton.healingCombatEffects.Add(combatEffect);
  722. }
  723. if (healing.PhysicalOffense != 0)
  724. {
  725. CombatEffect combatEffect = new CombatEffect();
  726. combatEffect.OriginalPosition = position;
  727. combatEffect.Text = "PO\n" + healing.PhysicalOffense.ToString();
  728. combatEffect.Rise = startingRise;
  729. startingRise -= 5;
  730. singleton.healingCombatEffects.Add(combatEffect);
  731. }
  732. if (healing.PhysicalDefense != 0)
  733. {
  734. CombatEffect combatEffect = new CombatEffect();
  735. combatEffect.OriginalPosition = position;
  736. combatEffect.Text = "PD\n" + healing.PhysicalDefense.ToString();
  737. combatEffect.Rise = startingRise;
  738. startingRise -= 5;
  739. singleton.healingCombatEffects.Add(combatEffect);
  740. }
  741. if (healing.MagicalOffense != 0)
  742. {
  743. CombatEffect combatEffect = new CombatEffect();
  744. combatEffect.OriginalPosition = position;
  745. combatEffect.Text = "MO\n" + healing.MagicalOffense.ToString();
  746. combatEffect.Rise = startingRise;
  747. startingRise -= 5;
  748. singleton.healingCombatEffects.Add(combatEffect);
  749. }
  750. if (healing.MagicalDefense != 0)
  751. {
  752. CombatEffect combatEffect = new CombatEffect();
  753. combatEffect.OriginalPosition = position;
  754. combatEffect.Text = "MD\n" + healing.MagicalDefense.ToString();
  755. combatEffect.Rise = startingRise;
  756. startingRise -= 5;
  757. singleton.healingCombatEffects.Add(combatEffect);
  758. }
  759. }
  760. #endregion
  761. /// <summary>
  762. /// Load the graphics data for the combat effect sprites.
  763. /// </summary>
  764. private void CreateCombatEffectSprites()
  765. {
  766. ContentManager content = Session.ScreenManager.Game.Content;
  767. damageCombatEffectTexture =
  768. content.Load<Texture2D>(@"Textures\Combat\DamageIcon");
  769. healingCombatEffectTexture =
  770. content.Load<Texture2D>(@"Textures\Combat\HealingIcon");
  771. }
  772. /// <summary>
  773. /// Draw all combat effect sprites.
  774. /// </summary>
  775. private void DrawCombatEffects(GameTime gameTime)
  776. {
  777. float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
  778. SpriteBatch spriteBatch = Session.ScreenManager.SpriteBatch;
  779. // update all effects
  780. foreach (CombatEffect combatEffect in damageCombatEffects)
  781. {
  782. combatEffect.Update(elapsedSeconds);
  783. }
  784. foreach (CombatEffect combatEffect in healingCombatEffects)
  785. {
  786. combatEffect.Update(elapsedSeconds);
  787. }
  788. // draw the damage effects
  789. if (damageCombatEffectTexture != null)
  790. {
  791. foreach (CombatEffect combatEffect in damageCombatEffects)
  792. {
  793. combatEffect.Draw(spriteBatch, damageCombatEffectTexture);
  794. }
  795. }
  796. // draw the healing effects
  797. if (healingCombatEffectTexture != null)
  798. {
  799. foreach (CombatEffect combatEffect in healingCombatEffects)
  800. {
  801. combatEffect.Draw(spriteBatch, healingCombatEffectTexture);
  802. }
  803. }
  804. // remove all complete effects
  805. Predicate<CombatEffect> removeCompleteEffects =
  806. delegate(CombatEffect combatEffect)
  807. {
  808. return combatEffect.IsRiseComplete;
  809. };
  810. damageCombatEffects.RemoveAll(removeCompleteEffects);
  811. healingCombatEffects.RemoveAll(removeCompleteEffects);
  812. }
  813. #endregion
  814. #region Selection Sprites
  815. /// <summary>
  816. /// The animating sprite that draws over the highlighted character.
  817. /// </summary>
  818. private AnimatingSprite highlightForegroundSprite = new AnimatingSprite();
  819. /// <summary>
  820. /// The animating sprite that draws behind the highlighted character.
  821. /// </summary>
  822. private AnimatingSprite highlightBackgroundSprite = new AnimatingSprite();
  823. /// <summary>
  824. /// The animating sprite that draws behind the primary target character.
  825. /// </summary>
  826. private AnimatingSprite primaryTargetSprite = new AnimatingSprite();
  827. /// <summary>
  828. /// The animating sprite that draws behind any secondary target characters.
  829. /// </summary>
  830. private AnimatingSprite secondaryTargetSprite = new AnimatingSprite();
  831. /// <summary>
  832. /// Create the selection sprite objects.
  833. /// </summary>
  834. private void CreateSelectionSprites()
  835. {
  836. ContentManager content = Session.ScreenManager.Game.Content;
  837. Point frameDimensions = new Point(76, 58);
  838. highlightForegroundSprite.FramesPerRow = 6;
  839. highlightForegroundSprite.FrameDimensions = frameDimensions;
  840. highlightForegroundSprite.AddAnimation(
  841. new Animation("Selection", 1, 4, 100, true));
  842. highlightForegroundSprite.PlayAnimation(0);
  843. highlightForegroundSprite.SourceOffset =
  844. new Vector2(frameDimensions.X / 2f, 40f);
  845. highlightForegroundSprite.Texture =
  846. content.Load<Texture2D>(@"Textures\Combat\TilesheetSprangles");
  847. frameDimensions = new Point(102, 54);
  848. highlightBackgroundSprite.FramesPerRow = 4;
  849. highlightBackgroundSprite.FrameDimensions = frameDimensions;
  850. highlightBackgroundSprite.AddAnimation(
  851. new Animation("Selection", 1, 4, 100, true));
  852. highlightBackgroundSprite.PlayAnimation(0);
  853. highlightBackgroundSprite.SourceOffset =
  854. new Vector2(frameDimensions.X / 2f, frameDimensions.Y / 2f);
  855. highlightBackgroundSprite.Texture =
  856. content.Load<Texture2D>(@"Textures\Combat\CharSelectionRing");
  857. primaryTargetSprite.FramesPerRow = 4;
  858. primaryTargetSprite.FrameDimensions = frameDimensions;
  859. primaryTargetSprite.AddAnimation(
  860. new Animation("Selection", 1, 4, 100, true));
  861. primaryTargetSprite.PlayAnimation(0);
  862. primaryTargetSprite.SourceOffset =
  863. new Vector2(frameDimensions.X / 2f, frameDimensions.Y / 2f);
  864. primaryTargetSprite.Texture =
  865. content.Load<Texture2D>(@"Textures\Combat\Target1SelectionRing");
  866. secondaryTargetSprite.FramesPerRow = 4;
  867. secondaryTargetSprite.FrameDimensions = frameDimensions;
  868. secondaryTargetSprite.AddAnimation(
  869. new Animation("Selection", 1, 4, 100, true));
  870. secondaryTargetSprite.PlayAnimation(0);
  871. secondaryTargetSprite.SourceOffset =
  872. new Vector2(frameDimensions.X / 2f, frameDimensions.Y / 2f);
  873. secondaryTargetSprite.Texture =
  874. content.Load<Texture2D>(@"Textures\Combat\Target2SelectionRing");
  875. }
  876. /// <summary>
  877. /// Draw the highlight sprites.
  878. /// </summary>
  879. private void DrawSelectionSprites(GameTime gameTime)
  880. {
  881. SpriteBatch spriteBatch = Session.ScreenManager.SpriteBatch;
  882. Viewport viewport = Session.ScreenManager.GraphicsDevice.Viewport;
  883. // update the animations
  884. float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
  885. highlightForegroundSprite.UpdateAnimation(elapsedSeconds);
  886. highlightBackgroundSprite.UpdateAnimation(elapsedSeconds);
  887. primaryTargetSprite.UpdateAnimation(elapsedSeconds);
  888. secondaryTargetSprite.UpdateAnimation(elapsedSeconds);
  889. // draw the highlighted-player sprite, if any
  890. if (highlightedCombatant != null)
  891. {
  892. highlightBackgroundSprite.Draw(spriteBatch,
  893. highlightedCombatant.Position,
  894. 1f - (highlightedCombatant.Position.Y - 1) / viewport.Height);
  895. highlightForegroundSprite.Draw(spriteBatch,
  896. highlightedCombatant.Position,
  897. 1f - (highlightedCombatant.Position.Y + 1) / viewport.Height);
  898. }
  899. // draw the primary target sprite and name, if any
  900. if (primaryTargetedCombatant != null)
  901. {
  902. primaryTargetSprite.Draw(spriteBatch,
  903. primaryTargetedCombatant.Position,
  904. 1f - (primaryTargetedCombatant.Position.Y - 1) / viewport.Height);
  905. if (primaryTargetedCombatant.Character is Monster)
  906. {
  907. Fonts.DrawCenteredText(spriteBatch, Fonts.DamageFont,
  908. #if DEBUG
  909. primaryTargetedCombatant.Character.Name + "\n" +
  910. primaryTargetedCombatant.Statistics.HealthPoints + "/" +
  911. primaryTargetedCombatant.Character.CharacterStatistics.HealthPoints,
  912. #else
  913. primaryTargetedCombatant.Character.Name,
  914. #endif
  915. primaryTargetedCombatant.Position + new Vector2(0f, 42f),
  916. Color.White);
  917. }
  918. }
  919. // draw the secondary target sprites on live enemies, if any
  920. foreach (Combatant combatant in secondaryTargetedCombatants)
  921. {
  922. if (combatant.IsDeadOrDying)
  923. {
  924. continue;
  925. }
  926. secondaryTargetSprite.Draw(spriteBatch,
  927. combatant.Position,
  928. 1f - (combatant.Position.Y - 1) / viewport.Height);
  929. if (combatant.Character is Monster)
  930. {
  931. Fonts.DrawCenteredText(spriteBatch, Fonts.DamageFont,
  932. #if DEBUG
  933. combatant.Character.Name + "\n" +
  934. combatant.Statistics.HealthPoints + "/" +
  935. combatant.Character.CharacterStatistics.HealthPoints,
  936. #else
  937. combatant.Character.Name,
  938. #endif
  939. combatant.Position + new Vector2(0f, 42f), Color.White);
  940. }
  941. }
  942. }
  943. #endregion
  944. #region Delays
  945. /// <summary>
  946. /// Varieties of delays that are interspersed throughout the combat flow.
  947. /// </summary>
  948. private enum DelayType
  949. {
  950. /// <summary>
  951. /// No delay at this time.
  952. /// </summary>
  953. NoDelay,
  954. /// <summary>
  955. /// Delay at the start of combat.
  956. /// </summary>
  957. StartCombat,
  958. /// <summary>
  959. /// Delay when one side turn's ends before the other side begins.
  960. /// </summary>
  961. EndRound,
  962. /// <summary>
  963. /// Delay at the end of a character's turn before the next one begins.
  964. /// </summary>
  965. EndCharacterTurn,
  966. /// <summary>
  967. /// Delay before a flee is attempted.
  968. /// </summary>
  969. FleeAttempt,
  970. /// <summary>
  971. /// Delay when the party has fled from combat before combat ends.
  972. /// </summary>
  973. FleeSuccessful,
  974. }
  975. /// <summary>
  976. /// The current delay, if any (otherwise NoDelay).
  977. /// </summary>
  978. private DelayType delayType = DelayType.NoDelay;
  979. /// <summary>
  980. /// Returns true if the combat engine is delaying for any reason.
  981. /// </summary>
  982. public static bool IsDelaying
  983. {
  984. get
  985. {
  986. return (singleton == null ? false :
  987. singleton.delayType != DelayType.NoDelay);
  988. }
  989. }
  990. /// <summary>
  991. /// The duration for all kinds of delays, in milliseconds.
  992. /// </summary>
  993. private const int totalDelay = 1000;
  994. /// <summary>
  995. /// The duration of the delay so far.
  996. /// </summary>
  997. private int currentDelay = 0;
  998. /// <summary>
  999. /// Update any delays in the combat system.
  1000. /// </summary>
  1001. /// <remarks>
  1002. /// This function may cause combat to end, setting the singleton to null.
  1003. /// </remarks>
  1004. private void UpdateDelay(int elapsedMilliseconds)
  1005. {
  1006. if (delayType == DelayType.NoDelay)
  1007. {
  1008. return;
  1009. }
  1010. // increment the delay
  1011. currentDelay += elapsedMilliseconds;
  1012. // if the delay is ongoing, then we're done
  1013. if (currentDelay < totalDelay)
  1014. {
  1015. return;
  1016. }
  1017. currentDelay = 0;
  1018. // the delay has ended, so the operation implied by the DelayType happens
  1019. switch (delayType)
  1020. {
  1021. case DelayType.StartCombat:
  1022. // determine who goes first and start combat
  1023. int whoseTurn = Session.Random.Next(2);
  1024. if (whoseTurn == 0)
  1025. {
  1026. BeginPlayersTurn();
  1027. }
  1028. else
  1029. {
  1030. BeginMonstersTurn();
  1031. }
  1032. delayType = DelayType.NoDelay;
  1033. break;
  1034. case DelayType.EndCharacterTurn:
  1035. if (IsPlayersTurn)
  1036. {
  1037. // check to see if the players' turn is complete
  1038. if (IsPlayersTurnComplete)
  1039. {
  1040. delayType = DelayType.EndRound;
  1041. break;
  1042. }
  1043. // find the next player
  1044. int highlightedIndex = players.FindIndex(
  1045. delegate(CombatantPlayer player)
  1046. {
  1047. return (player ==
  1048. highlightedCombatant as CombatantPlayer);
  1049. });
  1050. int nextIndex = (highlightedIndex + 1) % players.Count;
  1051. while (players[nextIndex].IsDeadOrDying ||
  1052. players[nextIndex].IsTurnTaken)
  1053. {
  1054. nextIndex = (nextIndex + 1) % players.Count;
  1055. }
  1056. BeginPlayerTurn(players[nextIndex]);
  1057. }
  1058. else
  1059. {
  1060. // check to see if the monsters' turn is complete
  1061. if (IsMonstersTurnComplete)
  1062. {
  1063. delayType = DelayType.EndRound;
  1064. break;
  1065. }
  1066. // find the next monster
  1067. BeginMonsterTurn(null);
  1068. }
  1069. delayType = DelayType.NoDelay;
  1070. break;
  1071. case DelayType.EndRound:
  1072. // check for turn completion
  1073. if (IsPlayersTurn && IsPlayersTurnComplete)
  1074. {
  1075. BeginMonstersTurn();
  1076. }
  1077. else if (!IsPlayersTurn && IsMonstersTurnComplete)
  1078. {
  1079. BeginPlayersTurn();
  1080. }
  1081. delayType = DelayType.NoDelay;
  1082. break;
  1083. case DelayType.FleeAttempt:
  1084. if (fleeThreshold <= 0)
  1085. {
  1086. delayType = DelayType.EndCharacterTurn;
  1087. Session.Hud.ActionText = "This Fight Cannot Be Escaped...";
  1088. if (highlightedCombatant != null)
  1089. {
  1090. highlightedCombatant.IsTurnTaken = true;
  1091. }
  1092. }
  1093. else if (CalculateFleeAttempt())
  1094. {
  1095. delayType = DelayType.FleeSuccessful;
  1096. Session.Hud.ActionText = "Your Party Has Fled!";
  1097. }
  1098. else
  1099. {
  1100. delayType = DelayType.EndCharacterTurn;
  1101. Session.Hud.ActionText = "Your Party Failed to Escape!";
  1102. if (highlightedCombatant != null)
  1103. {
  1104. highlightedCombatant.IsTurnTaken = true;
  1105. }
  1106. }
  1107. break;
  1108. case DelayType.FleeSuccessful:
  1109. EndCombat(CombatEndingState.Fled);
  1110. delayType = DelayType.NoDelay;
  1111. break;
  1112. }
  1113. }
  1114. #endregion
  1115. #region Starting Combat
  1116. /// <summary>
  1117. /// Generates a list of CombatantPlayer objects from the party members.
  1118. /// </summary>
  1119. private static List<CombatantPlayer> GenerateCombatantsFromParty()
  1120. {
  1121. List<CombatantPlayer> generatedPlayers = new List<CombatantPlayer>();
  1122. foreach (Player player in Session.Party.Players)
  1123. {
  1124. if (generatedPlayers.Count <= PlayerPositions.Length)
  1125. {
  1126. generatedPlayers.Add(new CombatantPlayer(player));
  1127. }
  1128. }
  1129. return generatedPlayers;
  1130. }
  1131. /// <summary>
  1132. /// Start a new combat from the given FixedCombat object.
  1133. /// </summary>
  1134. public static void StartNewCombat(MapEntry<FixedCombat> fixedCombatEntry)
  1135. {
  1136. // check the parameter
  1137. if (fixedCombatEntry == null)
  1138. {
  1139. throw new ArgumentNullException("fixedCombatEntry");
  1140. }
  1141. FixedCombat fixedCombat = fixedCombatEntry.Content;
  1142. if (fixedCombat == null)
  1143. {
  1144. throw new ArgumentException("fixedCombatEntry has no content.");
  1145. }
  1146. // generate the monster combatant list
  1147. List<CombatantMonster> generatedMonsters = new List<CombatantMonster>();
  1148. foreach (ContentEntry<Monster> entry in fixedCombat.Entries)
  1149. {
  1150. for (int i = 0; i < entry.Count; i++)
  1151. {
  1152. generatedMonsters.Add(
  1153. new CombatantMonster(entry.Content));
  1154. }
  1155. }
  1156. // randomize the list of monsters
  1157. List<CombatantMonster> randomizedMonsters = new List<CombatantMonster>();
  1158. while ((generatedMonsters.Count > 0) &&
  1159. (randomizedMonsters.Count <= MonsterPositions.Length))
  1160. {
  1161. int index = Session.Random.Next(generatedMonsters.Count);
  1162. randomizedMonsters.Add(generatedMonsters[index]);
  1163. generatedMonsters.RemoveAt(index);
  1164. }
  1165. // start the combat
  1166. StartNewCombat(GenerateCombatantsFromParty(), randomizedMonsters, 0);
  1167. singleton.fixedCombatEntry = fixedCombatEntry;
  1168. }
  1169. /// <summary>
  1170. /// Start a new combat from the given RandomCombat object.
  1171. /// </summary>
  1172. public static void StartNewCombat(RandomCombat randomCombat)
  1173. {
  1174. // check the parameter
  1175. if (randomCombat == null)
  1176. {
  1177. throw new ArgumentNullException("randomCombat");
  1178. }
  1179. // determine how many monsters will be in the combat
  1180. int monsterCount =
  1181. randomCombat.MonsterCountRange.GenerateValue(Session.Random);
  1182. // determine the total probability
  1183. int totalWeight = 0;
  1184. foreach (WeightedContentEntry<Monster> entry in randomCombat.Entries)
  1185. {
  1186. totalWeight += entry.Weight;
  1187. }
  1188. // generate each monster
  1189. List<CombatantMonster> generatedMonsters = new List<CombatantMonster>();
  1190. for (int i = 0; i < monsterCount; i++)
  1191. {
  1192. int monsterChoice = Session.Random.Next(totalWeight);
  1193. foreach (WeightedContentEntry<Monster> entry in randomCombat.Entries)
  1194. {
  1195. if (monsterChoice < entry.Weight)
  1196. {
  1197. generatedMonsters.Add(
  1198. new CombatantMonster(entry.Content));
  1199. break;
  1200. }
  1201. else
  1202. {
  1203. monsterChoice -= entry.Weight;
  1204. }
  1205. }
  1206. }
  1207. // randomize the list of monsters
  1208. List<CombatantMonster> randomizedMonsters = new List<CombatantMonster>();
  1209. while ((generatedMonsters.Count > 0) &&
  1210. (randomizedMonsters.Count <= MonsterPositions.Length))
  1211. {
  1212. int index = Session.Random.Next(generatedMonsters.Count);
  1213. randomizedMonsters.Add(generatedMonsters[index]);
  1214. generatedMonsters.RemoveAt(index);
  1215. }
  1216. // start the combat
  1217. StartNewCombat(GenerateCombatantsFromParty(), randomizedMonsters,
  1218. randomCombat.FleeProbability);
  1219. }
  1220. /// <summary>
  1221. /// Start a new combat between the party and a group of monsters.
  1222. /// </summary>
  1223. /// <param name="players">The player combatants.</param>
  1224. /// <param name="monsters">The monster combatants.</param>
  1225. /// <param name="fleeThreshold">The odds of success when fleeing.</param>
  1226. private static void StartNewCombat(List<CombatantPlayer> players,
  1227. List<CombatantMonster> monsters, int fleeThreshold)
  1228. {
  1229. // check if we are already in combat
  1230. if (singleton != null)
  1231. {
  1232. throw new InvalidOperationException(
  1233. "There can only be one combat at a time.");
  1234. }
  1235. // create the new CombatEngine object
  1236. singleton = new CombatEngine(players, monsters, fleeThreshold);
  1237. }
  1238. /// <summary>
  1239. /// Construct a new CombatEngine object.
  1240. /// </summary>
  1241. /// <param name="players">The player combatants.</param>
  1242. /// <param name="monsters">The monster combatants.</param>
  1243. /// <param name="fleeThreshold">The odds of success when fleeing.</param>
  1244. private CombatEngine(List<CombatantPlayer> players,
  1245. List<CombatantMonster> monsters, int fleeThreshold)
  1246. {
  1247. // check the parameters
  1248. if ((players == null) || (players.Count <= 0) ||
  1249. (players.Count > PlayerPositions.Length))
  1250. {
  1251. throw new ArgumentException("players");
  1252. }
  1253. if ((monsters == null) || (monsters.Count <= 0) ||
  1254. (monsters.Count > MonsterPositions.Length))
  1255. {
  1256. throw new ArgumentException("monsters");
  1257. }
  1258. // assign the parameters
  1259. this.players = players;
  1260. this.monsters = monsters;
  1261. this.fleeThreshold = fleeThreshold;
  1262. // assign positions
  1263. for (int i = 0; i < players.Count; i++)
  1264. {
  1265. if (i >= PlayerPositions.Length)
  1266. {
  1267. break;
  1268. }
  1269. players[i].Position =
  1270. players[i].OriginalPosition =
  1271. PlayerPositions[i];
  1272. }
  1273. for (int i = 0; i < monsters.Count; i++)
  1274. {
  1275. if (i >= MonsterPositions.Length)
  1276. {
  1277. break;
  1278. }
  1279. monsters[i].Position =
  1280. monsters[i].OriginalPosition =
  1281. MonsterPositions[i];
  1282. }
  1283. // sort the monsters by the y coordinates, descending
  1284. monsters.Sort(delegate(CombatantMonster monster1, CombatantMonster monster2)
  1285. {
  1286. return monster2.OriginalPosition.Y.CompareTo(
  1287. monster1.OriginalPosition.Y);
  1288. });
  1289. // create the selection sprites
  1290. CreateSelectionSprites();
  1291. // create the combat effect sprites
  1292. CreateCombatEffectSprites();
  1293. // start the first combat turn after a delay
  1294. delayType = DelayType.StartCombat;
  1295. // start the combat music
  1296. AudioManager.PushMusic(TileEngine.Map.CombatMusicCueName);
  1297. }
  1298. #endregion
  1299. #region Fleeing Combat
  1300. public static void AttemptFlee()
  1301. {
  1302. CheckSingleton();
  1303. if (!IsPlayersTurn)
  1304. {
  1305. throw new InvalidOperationException("Only the players may flee.");
  1306. }
  1307. singleton.delayType = DelayType.FleeAttempt;
  1308. Session.Hud.ActionText = "Attempting to Escape...";
  1309. }
  1310. /// <summary>
  1311. /// The odds of being able to flee this combat, from 0 to 100.
  1312. /// </summary>
  1313. private int fleeThreshold = 0;
  1314. /// <summary>
  1315. /// Calculate an attempted escape from the combat.
  1316. /// </summary>
  1317. /// <returns>If true, the escape succeeds.</returns>
  1318. private bool CalculateFleeAttempt()
  1319. {
  1320. return (Session.Random.Next(100) < fleeThreshold);
  1321. }
  1322. #endregion
  1323. #region Ending Combat
  1324. /// <summary>
  1325. /// End the combat
  1326. /// </summary>
  1327. /// <param name="combatEndState"></param>
  1328. private void EndCombat(CombatEndingState combatEndingState)
  1329. {
  1330. // go back to the non-combat music
  1331. AudioManager.PopMusic();
  1332. switch (combatEndingState)
  1333. {
  1334. case CombatEndingState.Victory:
  1335. int experienceReward = 0;
  1336. int goldReward = 0;
  1337. List<Gear> gearRewards = new List<Gear>();
  1338. List<string> gearRewardNames = new List<string>();
  1339. // calculate the rewards from the monsters
  1340. foreach (CombatantMonster combatantMonster in monsters)
  1341. {
  1342. Monster monster = combatantMonster.Monster;
  1343. Session.Party.AddMonsterKill(monster);
  1344. experienceReward +=
  1345. monster.CalculateExperienceReward(Session.Random);
  1346. goldReward += monster.CalculateGoldReward(Session.Random);
  1347. gearRewardNames.AddRange(
  1348. monster.CalculateGearDrop(Session.Random));
  1349. }
  1350. foreach (string gearRewardName in gearRewardNames)
  1351. {
  1352. gearRewards.Add(Session.ScreenManager.Game.Content.Load<Gear>(
  1353. Path.Combine(@"Gear", gearRewardName)));
  1354. }
  1355. // add the reward screen
  1356. Session.ScreenManager.AddScreen(new RewardsScreen(
  1357. RewardsScreen.RewardScreenMode.Combat, experienceReward,
  1358. goldReward, gearRewards));
  1359. // remove the fixed combat entry, if this wasn't a random fight
  1360. if (FixedCombatEntry != null)
  1361. {
  1362. Session.RemoveFixedCombat(FixedCombatEntry);
  1363. }
  1364. break;
  1365. case CombatEndingState.Loss: // game over
  1366. ScreenManager screenManager = Session.ScreenManager;
  1367. // end the session
  1368. Session.EndSession();
  1369. // add the game-over screen
  1370. screenManager.AddScreen(new GameOverScreen());
  1371. break;
  1372. case CombatEndingState.Fled:
  1373. break;
  1374. }
  1375. // clear the singleton
  1376. singleton = null;
  1377. }
  1378. /// <summary>
  1379. /// Ensure that there is no combat happening right now.
  1380. /// </summary>
  1381. public static void ClearCombat()
  1382. {
  1383. // clear the singleton
  1384. if (singleton != null)
  1385. {
  1386. singleton = null;
  1387. }
  1388. }
  1389. #endregion
  1390. #region Updating
  1391. /// <summary>
  1392. /// Update the combat engine for this frame.
  1393. /// </summary>
  1394. public static void Update(GameTime gameTime)
  1395. {
  1396. // if there is no active combat, then there's nothing to update
  1397. // -- this will be called every frame, so there should be no exception for
  1398. // calling this method outside of combat
  1399. if (singleton == null)
  1400. {
  1401. return;
  1402. }
  1403. // update the singleton
  1404. singleton.UpdateCombatEngine(gameTime);
  1405. }
  1406. /// <summary>
  1407. /// Update the combat engine for this frame.
  1408. /// </summary>
  1409. private void UpdateCombatEngine(GameTime gameTime)
  1410. {
  1411. // check for the end of combat
  1412. if (ArePlayersDefeated)
  1413. {
  1414. EndCombat(CombatEndingState.Loss);
  1415. return;
  1416. }
  1417. else if (AreMonstersDefeated)
  1418. {
  1419. EndCombat(CombatEndingState.Victory);
  1420. return;
  1421. }
  1422. // update the target selections
  1423. if ((highlightedCombatant != null) &&
  1424. (highlightedCombatant.CombatAction != null))
  1425. {
  1426. SetTargets(highlightedCombatant.CombatAction.Target,
  1427. highlightedCombatant.CombatAction.AdjacentTargets);
  1428. }
  1429. // update the delay
  1430. UpdateDelay(gameTime.ElapsedGameTime.Milliseconds);
  1431. // UpdateDelay might cause combat to end due to a successful escape,
  1432. // which will set the singleton to null.
  1433. if (singleton == null)
  1434. {
  1435. return;
  1436. }
  1437. // update the players
  1438. foreach (CombatantPlayer player in players)
  1439. {
  1440. player.Update(gameTime);
  1441. }
  1442. // update the monsters
  1443. foreach (CombatantMonster monster in monsters)
  1444. {
  1445. monster.Update(gameTime);
  1446. }
  1447. // check for completion of the highlighted combatant
  1448. if ((delayType == DelayType.NoDelay) &&
  1449. (highlightedCombatant != null) && highlightedCombatant.IsTurnTaken)
  1450. {
  1451. delayType = DelayType.EndCharacterTurn;
  1452. }
  1453. // handle any player input
  1454. HandleInput();
  1455. }
  1456. /// <summary>
  1457. /// Handle player input that affects the combat engine.
  1458. /// </summary>
  1459. private void HandleInput()
  1460. {
  1461. // only accept input during the players' turn
  1462. // -- exit game, etc. is handled by GameplayScreen
  1463. if (!IsPlayersTurn || IsPlayersTurnComplete ||
  1464. (highlightedCombatant == null))
  1465. {
  1466. return;
  1467. }
  1468. #if DEBUG
  1469. // cheat key
  1470. if (InputManager.IsGamePadRightShoulderTriggered() ||
  1471. InputManager.IsKeyTriggered(Keys.W))
  1472. {
  1473. EndCombat(CombatEndingState.Victory);
  1474. return;
  1475. }
  1476. #endif
  1477. // handle input while choosing an action
  1478. if (highlightedCombatant.CombatAction != null)
  1479. {
  1480. // skip if its turn is over or the action is already going
  1481. if (highlightedCombatant.IsTurnTaken ||
  1482. (highlightedCombatant.CombatAction.Stage !=
  1483. CombatAction.CombatActionStage.NotStarted))
  1484. {
  1485. return;
  1486. }
  1487. // back out of the action
  1488. if (InputManager.IsActionTriggered(InputManager.Action.Back))
  1489. {
  1490. highlightedCombatant.CombatAction = null;
  1491. SetTargets(null, 0);
  1492. return;
  1493. }
  1494. // start the action
  1495. if (InputManager.IsActionTriggered(InputManager.Action.Ok))
  1496. {
  1497. highlightedCombatant.CombatAction.Start();
  1498. return;
  1499. }
  1500. // go to the next target
  1501. if (InputManager.IsActionTriggered(InputManager.Action.TargetUp))
  1502. {
  1503. // cycle through monsters or party members
  1504. if (highlightedCombatant.CombatAction.IsOffensive)
  1505. {
  1506. // find the index of the current target
  1507. int newIndex = monsters.FindIndex(
  1508. delegate(CombatantMonster monster)
  1509. {
  1510. return (primaryTargetedCombatant == monster);
  1511. });
  1512. // find the next living target
  1513. do
  1514. {
  1515. newIndex = (newIndex + 1) % monsters.Count;
  1516. }
  1517. while (monsters[newIndex].IsDeadOrDying);
  1518. // set the new target
  1519. highlightedCombatant.CombatAction.Target = monsters[newIndex];
  1520. }
  1521. else
  1522. {
  1523. // find the index of the current target
  1524. int newIndex = players.FindIndex(
  1525. delegate(CombatantPlayer player)
  1526. {
  1527. return (primaryTargetedCombatant == player);
  1528. });
  1529. // find the next active, living target
  1530. do
  1531. {
  1532. newIndex = (newIndex + 1) % players.Count;
  1533. }
  1534. while (players[newIndex].IsDeadOrDying);
  1535. // set the new target
  1536. highlightedCombatant.CombatAction.Target = players[newIndex];
  1537. }
  1538. return;
  1539. }
  1540. // go to the previous target
  1541. else if (InputManager.IsActionTriggered(InputManager.Action.TargetDown))
  1542. {
  1543. // cycle through monsters or party members
  1544. if (highlightedCombatant.CombatAction.IsOffensive)
  1545. {
  1546. // find the index of the current target
  1547. int newIndex = monsters.FindIndex(
  1548. delegate(CombatantMonster monster)
  1549. {
  1550. return (primaryTargetedCombatant == monster);
  1551. });
  1552. // find the previous active, living target
  1553. do
  1554. {
  1555. newIndex--;
  1556. while (newIndex < 0)
  1557. {
  1558. newIndex += monsters.Count;
  1559. }
  1560. }
  1561. while (monsters[newIndex].IsDeadOrDying);
  1562. // set the new target
  1563. highlightedCombatant.CombatAction.Target = monsters[newIndex];
  1564. }
  1565. else
  1566. {
  1567. // find the index of the current target
  1568. int newIndex = players.FindIndex(
  1569. delegate(CombatantPlayer player)
  1570. {
  1571. return (primaryTargetedCombatant == player);
  1572. });
  1573. // find the previous living target
  1574. do
  1575. {
  1576. newIndex--;
  1577. while (newIndex < 0)
  1578. {
  1579. newIndex += players.Count;
  1580. }
  1581. }
  1582. while (players[newIndex].IsDeadOrDying);
  1583. // set the new target
  1584. highlightedCombatant.CombatAction.Target = players[newIndex];
  1585. }
  1586. return;
  1587. }
  1588. }
  1589. else // choosing which character will act
  1590. {
  1591. // move to the previous living character
  1592. if (InputManager.IsActionTriggered(
  1593. InputManager.Action.ActiveCharacterLeft))
  1594. {
  1595. int newHighlightedPlayer = highlightedPlayer;
  1596. do
  1597. {
  1598. newHighlightedPlayer--;
  1599. while (newHighlightedPlayer < 0)
  1600. {
  1601. newHighlightedPlayer += players.Count;
  1602. }
  1603. }
  1604. while (players[newHighlightedPlayer].IsDeadOrDying ||
  1605. players[newHighlightedPlayer].IsTurnTaken);
  1606. if (newHighlightedPlayer != highlightedPlayer)
  1607. {
  1608. highlightedPlayer = newHighlightedPlayer;
  1609. BeginPlayerTurn(players[highlightedPlayer]);
  1610. }
  1611. return;
  1612. }
  1613. // move to the next living character
  1614. else if (InputManager.IsActionTriggered(
  1615. InputManager.Action.ActiveCharacterRight))
  1616. {
  1617. int newHighlightedPlayer = highlightedPlayer;
  1618. do
  1619. {
  1620. newHighlightedPlayer =
  1621. (newHighlightedPlayer + 1) % players.Count;
  1622. }
  1623. while (players[newHighlightedPlayer].IsDeadOrDying ||
  1624. players[newHighlightedPlayer].IsTurnTaken);
  1625. if (newHighlightedPlayer != highlightedPlayer)
  1626. {
  1627. highlightedPlayer = newHighlightedPlayer;
  1628. BeginPlayerTurn(players[highlightedPlayer]);
  1629. }
  1630. return;
  1631. }
  1632. Session.Hud.UpdateActionsMenu();
  1633. }
  1634. }
  1635. #endregion
  1636. #region Drawing
  1637. /// <summary>
  1638. /// Draw the combat for this frame.
  1639. /// </summary>
  1640. public static void Draw(GameTime gameTime)
  1641. {
  1642. // if there is no active combat, then there's nothing to draw
  1643. // -- this will be called every frame, so there should be no exception for
  1644. // calling this method outside of combat
  1645. if (singleton == null)
  1646. {
  1647. return;
  1648. }
  1649. // update the singleton
  1650. singleton.DrawCombatEngine(gameTime);
  1651. }
  1652. /// <summary>
  1653. /// Draw the combat for this frame.
  1654. /// </summary>
  1655. private void DrawCombatEngine(GameTime gameTime)
  1656. {
  1657. // draw the players
  1658. foreach (CombatantPlayer player in players)
  1659. {
  1660. player.Draw(gameTime);
  1661. }
  1662. // draw the monsters
  1663. foreach (CombatantMonster monster in monsters)
  1664. {
  1665. monster.Draw(gameTime);
  1666. }
  1667. // draw the selection animations
  1668. DrawSelectionSprites(gameTime);
  1669. // draw the combat effects
  1670. DrawCombatEffects(gameTime);
  1671. }
  1672. #endregion
  1673. }
  1674. }