#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
}
}