#region File Description //----------------------------------------------------------------------------- // ItemCombatAction.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using Microsoft.Xna.Framework; using RolePlayingGameData; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Audio; #endregion namespace RolePlaying { /// /// A item-casting combat action, including related data and calculations. /// class ItemCombatAction : CombatAction { #region State /// /// Returns true if the action is offensive, targeting the opponents. /// public override bool IsOffensive { get { return Item.IsOffensive; } } /// /// Returns true if the character can use this action. /// public override bool IsCharacterValidUser { get { return true; } } /// /// Returns true if this action requires a target. /// public override bool IsTargetNeeded { get { return true; } } #endregion #region Item /// /// The item used in this action. /// private Item item; /// /// The item used in this action. /// public Item Item { get { return item; } } /// /// The current position of the item sprite. /// private Vector2 itemSpritePosition; /// /// Apply the action's item to the given target. /// /// True if there was any effect on the target. private bool ApplyItem(Combatant itemTarget) { StatisticsValue effectStatistics = CalculateItemDamage(combatant, item); if (item.IsOffensive) { // calculate the defense Int32Range defenseRange = itemTarget.Character.MagicDefenseRange + itemTarget.Statistics.MagicalDefense; Int32 defense = defenseRange.GenerateValue(Session.Random); // subtract the defense effectStatistics -= new StatisticsValue(defense, defense, defense, defense, defense, defense); // make sure that this only contains damage effectStatistics.ApplyMinimum(new StatisticsValue()); // damage the target itemTarget.Damage(effectStatistics, item.TargetDuration); } else { // make sure taht this only contains healing effectStatistics.ApplyMinimum(new StatisticsValue()); // heal the target itemTarget.Heal(effectStatistics, item.TargetDuration); } return !effectStatistics.IsZero; } #endregion #region Item Projectile Data /// /// The speed at which the projectile moves, in units per second. /// private const float projectileSpeed = 300f; /// /// The direction of the projectile. /// private Vector2 projectileDirection; /// /// The distance covered so far by the projectile. /// private float projectileDistanceCovered = 0f; /// /// The total distance between the original combatant position and the target. /// private float totalProjectileDistance; /// /// The sprite effect on the projectile, if any. /// private SpriteEffects projectileSpriteEffect = SpriteEffects.None; /// /// The sound effect cue for the traveling projectile. /// private Cue projectileCue; #endregion #region Combat Stage /// /// Starts a new combat stage. Called right after the stage changes. /// /// The stage never changes into NotStarted. protected override void StartStage() { switch (stage) { case CombatActionStage.Preparing: // called from Start() { // play the animations combatant.CombatSprite.PlayAnimation("ItemCast"); itemSpritePosition = Combatant.Position; item.SpellSprite.PlayAnimation("Creation"); Session.Party.RemoveFromInventory(item, 1); } break; case CombatActionStage.Advancing: { // play the animations item.SpellSprite.PlayAnimation("Traveling"); // calculate the projectile destination projectileDirection = Target.Position - Combatant.OriginalPosition; totalProjectileDistance = projectileDirection.Length(); projectileDirection.Normalize(); projectileDistanceCovered = 0f; // determine if the projectile is flipped if (Target.Position.X > Combatant.Position.X) { projectileSpriteEffect = SpriteEffects.FlipHorizontally; } else { projectileSpriteEffect = SpriteEffects.None; } // get the projectile's cue and play it projectileCue = AudioManager.GetCue(item.TravelingCueName); if (projectileCue != null) { projectileCue.Play(); } } break; case CombatActionStage.Executing: // play the animation item.SpellSprite.PlayAnimation("Impact"); // stop the projectile sound effect if (projectileCue != null) { projectileCue.Stop(AudioStopOptions.Immediate); } // apply the item effect to the primary target bool damagedAnyone = ApplyItem(Target); // apply the item effect to the secondary targets foreach (Combatant targetCombatant in CombatEngine.SecondaryTargetedCombatants) { // skip any dead or dying combatants if (targetCombatant.IsDeadOrDying) { continue; } // apply the effect damagedAnyone |= ApplyItem(targetCombatant); } // play the impact sound effect if (damagedAnyone) { AudioManager.PlayCue(item.ImpactCueName); if (item.Overlay != null) { item.Overlay.PlayAnimation(0); item.Overlay.ResetAnimation(); } } break; case CombatActionStage.Returning: // play the animation combatant.CombatSprite.PlayAnimation("Idle"); break; case CombatActionStage.Finishing: // play the animation combatant.CombatSprite.PlayAnimation("Idle"); break; case CombatActionStage.Complete: // play the animation combatant.CombatSprite.PlayAnimation("Idle"); break; } } /// /// Update the action for the current stage. /// /// /// This function is guaranteed to be called at least once per stage. /// protected override void UpdateCurrentStage(GameTime gameTime) { switch (stage) { case CombatActionStage.Advancing: if (projectileDistanceCovered < totalProjectileDistance) { projectileDistanceCovered += projectileSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; } itemSpritePosition = combatant.OriginalPosition + projectileDirection * projectileDistanceCovered; break; } } /// /// Returns true if the combat action is ready to proceed to the next stage. /// protected override bool IsReadyForNextStage { get { switch (stage) { case CombatActionStage.Preparing: // ready to advance? return (combatant.CombatSprite.IsPlaybackComplete && item.SpellSprite.IsPlaybackComplete); case CombatActionStage.Advancing: // ready to execute? if (item.SpellSprite.IsPlaybackComplete || (projectileDistanceCovered >= totalProjectileDistance)) { projectileDistanceCovered = totalProjectileDistance; return true; } return false; case CombatActionStage.Executing: // ready to return? return item.SpellSprite.IsPlaybackComplete; } // fall through to the base behavior return base.IsReadyForNextStage; } } #endregion #region Heuristic /// /// The heuristic used to compare actions of this type to similar ones. /// public override int Heuristic { get { return Item.TargetEffectRange.HealthPointsRange.Average; } } #endregion #region Initialization /// /// Constructs a new ItemCombatAction object. /// /// The combatant performing the action. public ItemCombatAction(Combatant combatant, Item item) : base(combatant) { // check the parameter if (item == null) { throw new ArgumentNullException("item"); } if ((item.Usage & Item.ItemUsage.Combat) == 0) { throw new ArgumentException("Combat items must have Combat usage."); } // assign the parameter this.item = item; this.adjacentTargets = this.item.AdjacentTargets; } /// /// Start executing the combat action. /// public override void Start() { // play the creation sound effect AudioManager.PlayCue(item.UsingCueName); base.Start(); } #endregion #region Updating /// /// Updates the action over time. /// public override void Update(GameTime gameTime) { // update the animations float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds; item.SpellSprite.UpdateAnimation(elapsedSeconds); if (item.Overlay != null) { item.Overlay.UpdateAnimation(elapsedSeconds); if (!item.Overlay.IsPlaybackComplete && Target.CombatSprite.IsPlaybackComplete) { item.Overlay.StopAnimation(); } } base.Update(gameTime); } #endregion #region Drawing /// /// Draw any elements of the action that are independent of the character. /// public override void Draw(GameTime gameTime, SpriteBatch spriteBatch) { // draw the item projectile if (!item.SpellSprite.IsPlaybackComplete) { if (stage == CombatActionStage.Advancing) { item.SpellSprite.Draw(spriteBatch, itemSpritePosition, 0f, projectileSpriteEffect); } else { item.SpellSprite.Draw(spriteBatch, itemSpritePosition, 0f); } } // draw the item overlay if (!item.Overlay.IsPlaybackComplete) { item.Overlay.Draw(spriteBatch, Target.Position, 0f); } base.Draw(gameTime, spriteBatch); } #endregion #region Static Calculation Methods /// /// Calculate the item damage done by the given combatant and item. /// public static StatisticsValue CalculateItemDamage(Combatant combatant, Item item) { // check the parameters if (item == null) { throw new ArgumentNullException("item"); } // generate a new effect value - no stats are involved for items return item.TargetEffectRange.GenerateValue(Session.Random); } #endregion } }