Ship.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Ship.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 Microsoft.Xna.Framework.Graphics;
  13. using Microsoft.Xna.Framework.Input;
  14. using Microsoft.Xna.Framework.Net;
  15. using Microsoft.Xna.Framework.Content;
  16. #endregion
  17. namespace NetRumble
  18. {
  19. /// <summary>
  20. /// The ship, which is the primary playing-piece in the game.
  21. /// </summary>
  22. public class Ship : GameplayObject
  23. {
  24. #region Constants
  25. /// <summary>
  26. /// The full speed possible for the ship.
  27. /// </summary>
  28. const float fullSpeed = 320f;
  29. /// <summary>
  30. /// The amount of drag applied to velocity per second,
  31. /// as a percentage of velocity.
  32. /// </summary>
  33. const float dragPerSecond = 0.9f;
  34. /// <summary>
  35. /// The amount that the right-stick must be pressed to fire, squared so that
  36. /// we can use LengthSquared instead of Length, which has a square-root in it.
  37. /// </summary>
  38. const float fireThresholdSquared = 0.25f;
  39. /// <summary>
  40. /// The number of radians that the ship can turn in a second at full left-stick.
  41. /// </summary>
  42. const float rotationRadiansPerSecond = 6f;
  43. /// <summary>
  44. /// The maximum length of the velocity vector on a ship.
  45. /// </summary>
  46. const float velocityMaximum = 320f;
  47. /// <summary>
  48. /// The maximum strength of the shield.
  49. /// </summary>
  50. const float shieldMaximum = 100f;
  51. /// <summary>
  52. /// The maximum opacity for the shield, when it's fully recharged.
  53. /// </summary>
  54. const float shieldAlphaMaximum = 150f;
  55. /// <summary>
  56. /// How much the shield recharges per second.
  57. /// </summary>
  58. const float shieldRechargePerSecond = 50f;
  59. /// <summary>
  60. /// The duration of the shield-recharge timer when the ship is hit.
  61. /// </summary>
  62. const float shieldRechargeTimerMaximum = 2.5f;
  63. /// <summary>
  64. /// The base scale for the shield, compared to the size of the ship.
  65. /// </summary>
  66. const float shieldScaleBase = 1.2f;
  67. /// <summary>
  68. /// The amplitude of the shield pulse
  69. /// </summary>
  70. const float shieldPulseAmplitude = 0.15f;
  71. /// <summary>
  72. /// The rate of the shield pulse.
  73. /// </summary>
  74. const float shieldPulseRate = 0.2f;
  75. /// <summary>
  76. /// The maximum value of the "safe" timer.
  77. /// </summary>
  78. const float safeTimerMaximum = 4f;
  79. /// <summary>
  80. /// The maximum amount of life that a ship can have.
  81. /// </summary>
  82. const float lifeMaximum = 25f;
  83. /// <summary>
  84. /// The value of the spawn timer set when the ship dies.
  85. /// </summary>
  86. const float respawnTimerOnDeath = 5f;
  87. /// <summary>
  88. /// The number of variations in textures for ships.
  89. /// </summary>
  90. const int variations = 4;
  91. #endregion
  92. #region Static Graphics Data
  93. /// <summary>
  94. /// The primary ship textures.
  95. /// </summary>
  96. private static Texture2D[] primaryTextures = new Texture2D[variations];
  97. /// <summary>
  98. /// The overlay ship textures, which get tinted.
  99. /// </summary>
  100. private static Texture2D[] overlayTextures = new Texture2D[variations];
  101. /// <summary>
  102. /// The ship shield texture.
  103. /// </summary>
  104. private static Texture2D shieldTexture;
  105. /// <summary>
  106. /// The colors used for each ship.
  107. /// </summary>
  108. public static readonly Color[] ShipColors =
  109. {
  110. Color.Lime, Color.CornflowerBlue, Color.Fuchsia,
  111. Color.Red, Color.LightSeaGreen, Color.LightGray,
  112. Color.Gold, Color.ForestGreen, Color.Beige,
  113. Color.LightPink, Color.Lavender, Color.OrangeRed,
  114. Color.Plum, Color.Tan, Color.YellowGreen,
  115. Color.Azure, Color.Aqua, Color.Salmon
  116. };
  117. /// <summary>
  118. /// The particle-effect manager which recieves the effects from ships.
  119. /// </summary>
  120. public static ParticleEffectManager ParticleEffectManager;
  121. #endregion
  122. #region Gameplay Data
  123. /// <summary>
  124. /// The score for this ship.
  125. /// </summary>
  126. private int score = 0;
  127. public int Score
  128. {
  129. get { return score; }
  130. set { score = value; }
  131. }
  132. /// <summary>
  133. /// The amount of damage that the ship can take before exploding.
  134. /// </summary>
  135. private float life;
  136. public float Life
  137. {
  138. get { return life; }
  139. set { life = value; }
  140. }
  141. /// <summary>
  142. /// The ship's primary weapon.
  143. /// </summary>
  144. private Weapon weapon;
  145. public Weapon Weapon
  146. {
  147. get { return weapon; }
  148. set
  149. {
  150. if (value == null)
  151. {
  152. throw new ArgumentNullException("value");
  153. }
  154. weapon = value;
  155. }
  156. }
  157. /// <summary>
  158. /// The ship's mine-laying weapon.
  159. /// </summary>
  160. private MineWeapon mineWeapon;
  161. /// <summary>
  162. /// All of the projectiles fired by this ship.
  163. /// </summary>
  164. private BatchRemovalCollection<Projectile> projectiles =
  165. new BatchRemovalCollection<Projectile>();
  166. public BatchRemovalCollection<Projectile> Projectiles
  167. {
  168. get { return projectiles; }
  169. }
  170. /// <summary>
  171. /// The strength of the shield.
  172. /// </summary>
  173. private float shield;
  174. public float Shield
  175. {
  176. get { return shield; }
  177. set { shield = value; }
  178. }
  179. /// <summary>
  180. /// Timer for how long until the shield starts to recharge.
  181. /// </summary>
  182. private float shieldRechargeTimer;
  183. /// <summary>
  184. /// Timer for how long the player is safe after spawning.
  185. /// </summary>
  186. private float safeTimer;
  187. public bool Safe
  188. {
  189. get { return (safeTimer > 0f); }
  190. set
  191. {
  192. if (value)
  193. {
  194. safeTimer = safeTimerMaximum;
  195. }
  196. else
  197. {
  198. safeTimer = 0f;
  199. }
  200. }
  201. }
  202. /// <summary>
  203. /// The tint of the ship.
  204. /// </summary>
  205. private Color color = Color.White;
  206. public Color Color
  207. {
  208. get { return color; }
  209. set { color = value; }
  210. }
  211. /// <summary>
  212. /// The amount of time left before respawning the ship.
  213. /// </summary>
  214. private float respawnTimer;
  215. public float RespawnTimer
  216. {
  217. get { return respawnTimer; }
  218. set { respawnTimer = value; }
  219. }
  220. /// <summary>
  221. /// The last object to damage the ship.
  222. /// </summary>
  223. private GameplayObject lastDamagedBy = null;
  224. public GameplayObject LastDamagedBy
  225. {
  226. get { return lastDamagedBy; }
  227. }
  228. #endregion
  229. #region Input Data
  230. /// <summary>
  231. /// The current player input for the ship.
  232. /// </summary>
  233. private ShipInput shipInput;
  234. public ShipInput ShipInput
  235. {
  236. get { return shipInput; }
  237. set { shipInput = value; }
  238. }
  239. #endregion
  240. #region Graphics Data
  241. /// <summary>
  242. /// The variation of this ship.
  243. /// </summary>
  244. private int variation = 0;
  245. public int Variation
  246. {
  247. get { return variation; }
  248. set
  249. {
  250. if ((value < 0) || (value >= variations))
  251. {
  252. throw new ArgumentOutOfRangeException("value");
  253. }
  254. variation = value;
  255. }
  256. }
  257. /// <summary>
  258. /// The time accumulator for the shield pulse.
  259. /// </summary>
  260. private float shieldPulseTime = 0f;
  261. #endregion
  262. #region Initialization Methods
  263. /// <summary>
  264. /// Construct a new ship.
  265. /// </summary>
  266. public Ship()
  267. : base()
  268. {
  269. // set the collision data
  270. this.radius = 24f;
  271. this.mass = 32f;
  272. }
  273. /// <summary>
  274. /// Initialize a ship to its default gameplay states.
  275. /// </summary>
  276. public override void Initialize()
  277. {
  278. if (!active)
  279. {
  280. // set the initial gameplay data values
  281. shipInput = ShipInput.Empty;
  282. rotation = 0f;
  283. velocity = Vector2.Zero;
  284. life = lifeMaximum;
  285. shield = shieldMaximum;
  286. shieldRechargeTimer = 0f;
  287. safeTimer = safeTimerMaximum;
  288. weapon = new LaserWeapon(this);
  289. mineWeapon = new MineWeapon(this);
  290. // play the player-spawn sound
  291. AudioManager.PlaySoundEffect("player_spawn");
  292. // add the ship-spawn particle effect
  293. if (ParticleEffectManager != null)
  294. {
  295. ParticleEffectManager.SpawnEffect(ParticleEffectType.ShipSpawn,
  296. this);
  297. }
  298. // clear out the projectiles list
  299. projectiles.Clear();
  300. }
  301. base.Initialize();
  302. }
  303. #endregion
  304. #region Updating Methods
  305. /// <summary>
  306. /// Update the ship.
  307. /// </summary>
  308. /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
  309. public override void Update(float elapsedTime)
  310. {
  311. // calculate the current forward vector
  312. Vector2 forward = new Vector2((float)Math.Sin(Rotation),
  313. -(float)Math.Cos(Rotation));
  314. Vector2 right = new Vector2(-forward.Y, forward.X);
  315. // calculate the new forward vector with the left stick
  316. shipInput.LeftStick.Y *= -1f;
  317. if (shipInput.LeftStick.LengthSquared() > 0f)
  318. {
  319. // change the direction
  320. Vector2 wantedForward = Vector2.Normalize(shipInput.LeftStick);
  321. float angleDiff = (float)Math.Acos(
  322. Vector2.Dot(wantedForward, forward));
  323. float facing = (Vector2.Dot(wantedForward, right) > 0f) ?
  324. 1f : -1f;
  325. if (angleDiff > 0.001f)
  326. {
  327. Rotation += Math.Min(angleDiff, facing * elapsedTime *
  328. rotationRadiansPerSecond);
  329. }
  330. // add velocity
  331. Velocity += shipInput.LeftStick * (elapsedTime * fullSpeed);
  332. if (Velocity.Length() > velocityMaximum)
  333. {
  334. Velocity = Vector2.Normalize(Velocity) *
  335. velocityMaximum;
  336. }
  337. }
  338. shipInput.LeftStick = Vector2.Zero;
  339. // apply drag to the velocity
  340. Velocity -= Velocity * (elapsedTime * dragPerSecond);
  341. if (Velocity.LengthSquared() <= 0f)
  342. {
  343. Velocity = Vector2.Zero;
  344. }
  345. // check for firing with the right stick
  346. shipInput.RightStick.Y *= -1f;
  347. if (shipInput.RightStick.LengthSquared() > fireThresholdSquared)
  348. {
  349. weapon.Fire(Vector2.Normalize(shipInput.RightStick));
  350. }
  351. shipInput.RightStick = Vector2.Zero;
  352. // check for laying mines
  353. if (shipInput.MineFired)
  354. {
  355. // fire behind the ship
  356. mineWeapon.Fire(-forward);
  357. }
  358. shipInput.MineFired = false;
  359. // recharge the shields
  360. if (shieldRechargeTimer > 0f)
  361. {
  362. shieldRechargeTimer = Math.Max(shieldRechargeTimer - elapsedTime,
  363. 0f);
  364. }
  365. if (shieldRechargeTimer <= 0f)
  366. {
  367. if (shield < shieldMaximum)
  368. {
  369. shield = Math.Min(shieldMaximum,
  370. shield + shieldRechargePerSecond * elapsedTime);
  371. }
  372. }
  373. // update the radius based on the shield
  374. radius = (shield > 0f) ? 24f : 20f;
  375. // update the weapons
  376. if (weapon != null)
  377. {
  378. weapon.Update(elapsedTime);
  379. }
  380. if (mineWeapon != null)
  381. {
  382. mineWeapon.Update(elapsedTime);
  383. }
  384. // decrement the safe timer
  385. if (safeTimer > 0f)
  386. {
  387. safeTimer = Math.Max(safeTimer - elapsedTime, 0f);
  388. }
  389. // update the projectiles
  390. foreach (Projectile projectile in projectiles)
  391. {
  392. if (projectile.Active)
  393. {
  394. projectile.Update(elapsedTime);
  395. }
  396. else
  397. {
  398. projectiles.QueuePendingRemoval(projectile);
  399. }
  400. }
  401. projectiles.ApplyPendingRemovals();
  402. base.Update(elapsedTime);
  403. }
  404. #endregion
  405. #region Drawing Methods
  406. /// <summary>
  407. /// Draw the ship.
  408. /// </summary>
  409. /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
  410. /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
  411. public void Draw(float elapsedTime, SpriteBatch spriteBatch)
  412. {
  413. // draw the uniform section of the ship
  414. base.Draw(elapsedTime, spriteBatch, primaryTextures[variation],
  415. null, Color.White);
  416. // draw the tinted section of the ship
  417. base.Draw(elapsedTime, spriteBatch, overlayTextures[variation],
  418. null, color);
  419. if (shield > 0)
  420. {
  421. // calculate the current shield radius
  422. float oldRadius = radius;
  423. shieldPulseTime += elapsedTime;
  424. radius *= shieldScaleBase + shieldPulseAmplitude *
  425. (float)Math.Sin(shieldPulseTime / shieldPulseRate);
  426. // draw the shield
  427. base.Draw(elapsedTime, spriteBatch, shieldTexture, null,
  428. new Color(color.R, color.G, color.B,
  429. (byte)Math.Floor(shieldAlphaMaximum * shield / shieldMaximum)));
  430. // return to the old radius
  431. radius = oldRadius;
  432. }
  433. // draw the projectiles
  434. foreach (Projectile projectile in projectiles)
  435. {
  436. projectile.Draw(elapsedTime, spriteBatch);
  437. }
  438. }
  439. #endregion
  440. #region Interaction Methods
  441. /// <summary>
  442. /// Damage this ship by the amount provided.
  443. /// </summary>
  444. /// <remarks>
  445. /// This function is provided in lieu of a Life mutation property to allow
  446. /// classes of objects to restrict which kinds of objects may damage them,
  447. /// and under what circumstances they may be damaged.
  448. /// </remarks>
  449. /// <param name="source">The GameplayObject responsible for the damage.</param>
  450. /// <param name="damageAmount">The amount of damage.</param>
  451. /// <returns>If true, this object was damaged.</returns>
  452. public override bool Damage(GameplayObject source, float damageAmount)
  453. {
  454. // if the safe timer hasn't yet gone off, then the ship can't be hurt
  455. if ((safeTimer > 0f) || (damageAmount <= 0f))
  456. {
  457. return false;
  458. }
  459. // once you're hit, the shield-recharge timer starts over
  460. shieldRechargeTimer = 2.5f;
  461. // damage the shield first, then life
  462. if (shield <= 0f)
  463. {
  464. life -= damageAmount;
  465. }
  466. else
  467. {
  468. shield -= damageAmount;
  469. if (shield < 0f)
  470. {
  471. // shield has the overflow value as a negative value, just add it
  472. life += shield;
  473. shield = 0f;
  474. }
  475. }
  476. Projectile sourceAsProjectile = source as Projectile;
  477. if (sourceAsProjectile != null)
  478. {
  479. lastDamagedBy = sourceAsProjectile.Owner;
  480. }
  481. else
  482. {
  483. lastDamagedBy = source;
  484. }
  485. return true;
  486. }
  487. /// <summary>
  488. /// Kills this ship, in response to the given GameplayObject.
  489. /// </summary>
  490. /// <param name="source">The GameplayObject responsible for the kill.</param>
  491. /// <param name="cleanupOnly">
  492. /// If true, the object dies without any further effects.
  493. /// </param>
  494. public override void Die(GameplayObject source, bool cleanupOnly)
  495. {
  496. if (active)
  497. {
  498. if (!cleanupOnly)
  499. {
  500. // update the score
  501. Ship ship = source as Ship;
  502. if (ship == null)
  503. {
  504. Projectile projectile = source as Projectile;
  505. if (projectile != null)
  506. {
  507. ship = projectile.Owner;
  508. }
  509. }
  510. if (ship != null)
  511. {
  512. if (ship == this)
  513. {
  514. // reduce the score, since i blew myself up
  515. ship.Score--;
  516. }
  517. else
  518. {
  519. // add score to the ship who shot this object
  520. ship.Score++;
  521. }
  522. }
  523. else
  524. {
  525. // if it wasn't a ship, then this object loses score
  526. this.Score--;
  527. }
  528. // play the player-death sound
  529. AudioManager.PlaySoundEffect("explosion_shockwave");
  530. AudioManager.PlaySoundEffect("explosion_large");
  531. // display the ship explosion
  532. if (ParticleEffectManager != null)
  533. {
  534. ParticleEffectManager.SpawnEffect(
  535. ParticleEffectType.ShipExplosion, Position);
  536. }
  537. }
  538. // clear out the projectiles list
  539. foreach (Projectile projectile in projectiles)
  540. {
  541. projectile.Die(null, true);
  542. }
  543. projectiles.Clear();
  544. // set the respawn timer
  545. respawnTimer = respawnTimerOnDeath;
  546. }
  547. base.Die(source, cleanupOnly);
  548. }
  549. #endregion
  550. #region Static Graphics Methods
  551. /// <summary>
  552. /// Load all of the static graphics content for this class.
  553. /// </summary>
  554. /// <param name="contentManager">The content manager to load with.</param>
  555. public static void LoadContent(ContentManager contentManager)
  556. {
  557. // safety-check the parameters
  558. if (contentManager == null)
  559. {
  560. throw new ArgumentNullException("contentManager");
  561. }
  562. // load each ship's texture
  563. for (int i = 0; i < variations; i++)
  564. {
  565. primaryTextures[i] = contentManager.Load<Texture2D>(
  566. "Textures/ship" + i.ToString());
  567. overlayTextures[i] = contentManager.Load<Texture2D>(
  568. "Textures/ship" + i.ToString() + "Overlay");
  569. }
  570. // load the shield texture
  571. shieldTexture = contentManager.Load<Texture2D>("Textures/shipShields");
  572. }
  573. /// <summary>
  574. /// Unload all of the static graphics content for this class.
  575. /// </summary>
  576. public static void UnloadContent()
  577. {
  578. for (int i = 0; i < variations; i++)
  579. {
  580. primaryTextures[i] = overlayTextures[i] = null;
  581. }
  582. shieldTexture = null;
  583. }
  584. /// <summary>
  585. /// Determines if a color index is unique.
  586. /// </summary>
  587. /// <param name="networkGamer">The gamer with the color index.</param>
  588. /// <param name="networkSession">The session the player belongs to.</param>
  589. /// <returns>If true, the gamer has a unique color ID.</returns>
  590. public static bool HasUniqueColorIndex(NetworkGamer networkGamer,
  591. NetworkSession networkSession)
  592. {
  593. // safety-check the parameters
  594. if (networkGamer == null)
  595. {
  596. throw new ArgumentNullException("networkGamer");
  597. }
  598. if (networkSession == null)
  599. {
  600. throw new ArgumentNullException("networkSession");
  601. }
  602. PlayerData playerData = networkGamer.Tag as PlayerData;
  603. if (playerData == null)
  604. {
  605. throw new ArgumentNullException("networkGamer.Tag as PlayerData");
  606. }
  607. // search for a match
  608. foreach (NetworkGamer gamer in networkSession.AllGamers)
  609. {
  610. if (gamer == networkGamer)
  611. {
  612. continue;
  613. }
  614. PlayerData gamerData = gamer.Tag as PlayerData;
  615. if ((gamerData != null) &&
  616. (gamerData.ShipColor == playerData.ShipColor))
  617. {
  618. return false;
  619. }
  620. }
  621. return true;
  622. }
  623. /// <summary>
  624. /// Find the next unique color index among the players.
  625. /// </summary>
  626. /// <param name="currentColorIndex">The current color index.</param>
  627. /// <param name="networkSession">The network session.</param>
  628. /// <returns>The next unique color index.</returns>
  629. public static byte GetNextUniqueColorIndex(byte currentColorIndex,
  630. NetworkSession networkSession)
  631. {
  632. // safety-check the parameters
  633. if ((currentColorIndex < 0) || (currentColorIndex >= ShipColors.Length))
  634. {
  635. throw new ArgumentOutOfRangeException("currentColorIndex");
  636. }
  637. if (networkSession == null)
  638. {
  639. throw new ArgumentNullException("networkSession");
  640. }
  641. if (networkSession.AllGamers.Count > ShipColors.Length)
  642. {
  643. throw new InvalidOperationException(
  644. "There are more gamers than there are colors.");
  645. }
  646. // if there are as many gamers as there are colors, then we can't change
  647. if (networkSession.AllGamers.Count == ShipColors.Length)
  648. {
  649. return currentColorIndex;
  650. }
  651. bool colorFound;
  652. byte newColorIndex = currentColorIndex;
  653. do
  654. {
  655. newColorIndex++;
  656. if (newColorIndex >= ShipColors.Length)
  657. {
  658. newColorIndex = 0;
  659. }
  660. colorFound = false;
  661. foreach (NetworkGamer networkGamer in networkSession.AllGamers)
  662. {
  663. PlayerData playerData = networkGamer.Tag as PlayerData;
  664. if ((playerData != null) && (playerData.ShipColor == newColorIndex))
  665. {
  666. colorFound = true;
  667. break;
  668. }
  669. }
  670. }
  671. while (colorFound && (newColorIndex != currentColorIndex));
  672. return newColorIndex;
  673. }
  674. /// <summary>
  675. /// Find the previous unique color index among the players.
  676. /// </summary>
  677. /// <param name="currentColorIndex">The current color index.</param>
  678. /// <param name="networkSession">The network session..</param>
  679. /// <returns>The previous unique color index.</returns>
  680. public static byte GetPreviousUniqueColorIndex(byte currentColorIndex,
  681. NetworkSession networkSession)
  682. {
  683. // safety-check the parameters
  684. if ((currentColorIndex < 0) || (currentColorIndex >= ShipColors.Length))
  685. {
  686. throw new ArgumentOutOfRangeException("currentColorIndex");
  687. }
  688. if (networkSession == null)
  689. {
  690. throw new ArgumentNullException("networkSession");
  691. }
  692. if (networkSession.AllGamers.Count > ShipColors.Length)
  693. {
  694. throw new InvalidOperationException(
  695. "There are more gamers than there are colors.");
  696. }
  697. // if there are as many gamers as there are colors, then we can't change
  698. if (networkSession.AllGamers.Count == ShipColors.Length)
  699. {
  700. return currentColorIndex;
  701. }
  702. bool colorFound;
  703. byte newColorIndex = currentColorIndex;
  704. do
  705. {
  706. if (newColorIndex == 0)
  707. {
  708. newColorIndex = (byte)(ShipColors.Length - 1);
  709. }
  710. else
  711. {
  712. newColorIndex--;
  713. }
  714. colorFound = false;
  715. foreach (NetworkGamer networkGamer in networkSession.AllGamers)
  716. {
  717. PlayerData playerData = networkGamer.Tag as PlayerData;
  718. if ((playerData != null) && (playerData.ShipColor == newColorIndex))
  719. {
  720. colorFound = true;
  721. break;
  722. }
  723. }
  724. }
  725. while (colorFound && (newColorIndex != currentColorIndex));
  726. return newColorIndex;
  727. }
  728. /// <summary>
  729. /// The number of variations in ships.
  730. /// </summary>
  731. public static int Variations
  732. {
  733. get { return variations; }
  734. }
  735. #endregion
  736. }
  737. }