/// <summary> /// Apply the action's item to the given target. /// </summary> /// <returns>True if there was any effect on the target.</returns> 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; }
/// <summary> /// Constructs a new CombatAction object. /// </summary> /// <param name="combatant">The combatant performing the action.</param> public CombatAction(Combatant combatant) { // check the parameter if (combatant == null) { throw new ArgumentNullException("combatant"); } // assign the parameter this.combatant = combatant; Reset(); }
/// <summary> /// Constructs a new SpellCombatAction object. /// </summary> /// <param name="character">The combatant performing the action.</param> public SpellCombatAction(Combatant combatant, Spell spell) : base(combatant) { // check the parameter if (spell == null) { throw new ArgumentNullException("spell"); } // assign the parameter this.spell = spell; this.adjacentTargets = this.spell.AdjacentTargets; }
/// <summary> /// Constructs a new ItemCombatAction object. /// </summary> /// <param name="character">The combatant performing the action.</param> 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; }
/// <summary> /// Set the primary and any secondary targets. /// </summary> /// <param name="primaryTarget">The desired primary target.</param> /// <param name="adjacentTargets"> /// The number of simultaneous, adjacent targets affected by this spell. /// </param> private void SetTargets(Combatant primaryTarget, int adjacentTargets) { // set the primary target this.primaryTargetedCombatant = primaryTarget; // set any secondary targets this.secondaryTargetedCombatants.Clear(); if ((primaryTarget != null) && (adjacentTargets > 0)) { // find out which side is targeted bool isPlayerTarget = primaryTarget is CombatantPlayer; // find the index int primaryTargetIndex = 0; if (isPlayerTarget) { primaryTargetIndex = players.FindIndex( delegate(CombatantPlayer player) { return (player == primaryTarget); }); } else { primaryTargetIndex = monsters.FindIndex( delegate(CombatantMonster monster) { return (monster == primaryTarget); }); } // add the surrounding indices for (int i = 1; i <= adjacentTargets; i++) { int leftIndex = primaryTargetIndex - i; if (leftIndex >= 0) { secondaryTargetedCombatants.Add( isPlayerTarget ? players[leftIndex] as Combatant : monsters[leftIndex] as Combatant); } int rightIndex = primaryTargetIndex + i; if (rightIndex < (isPlayerTarget ? players.Count : monsters.Count)) { secondaryTargetedCombatants.Add( isPlayerTarget ? players[rightIndex] as Combatant : monsters[rightIndex] as Combatant); } } } }
/// <summary> /// Starts a new combat stage. Called right after the stage changes. /// </summary> /// <remarks>The stage never changes into NotStarted.</remarks> protected override void StartStage() { switch (stage) { case CombatActionStage.Preparing: // called from Start() { // play the animations combatant.CombatSprite.PlayAnimation("SpellCast"); spellSpritePosition = Combatant.Position; spell.SpellSprite.PlayAnimation("Creation"); // remove the magic points Combatant.PayCostForSpell(spell); } break; case CombatActionStage.Advancing: { // play the animations spell.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; } } break; case CombatActionStage.Executing: { // play the animation spell.SpellSprite.PlayAnimation("Impact"); // apply the spell effect to the primary target bool damagedAnyone = ApplySpell(Target); // apply the spell to the secondary targets foreach (Combatant targetCombatant in CombatEngine.SecondaryTargetedCombatants) { // skip dead or dying targets if (targetCombatant.IsDeadOrDying) { continue; } // apply the spell damagedAnyone |= ApplySpell(targetCombatant); } // play the impact sound effect if (damagedAnyone) { AudioManager.PlayCue(spell.ImpactCueName); if (spell.Overlay != null) { spell.Overlay.PlayAnimation(0); spell.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"); // make sure that the overlay has stopped spell.Overlay.StopAnimation(); break; } }
/// <summary> /// Constructs a new DefendCombatAction object. /// </summary> /// <param name="character">The character performing the action.</param> public DefendCombatAction(Combatant combatant) : base(combatant) { }
/// <summary> /// Calculate the spell damage done by the given combatant and spell. /// </summary> public static StatisticsValue CalculateSpellDamage(Combatant combatant, Spell spell) { // check the parameters if (combatant == null) { throw new ArgumentNullException("combatant"); } if (spell == null) { throw new ArgumentNullException("spell"); } // get the magical offense from the character's class, gear, and bonuses // -- note that this includes stat buffs int magicalOffense = combatant.Statistics.MagicalOffense; // add the magical offense to the spell StatisticsValue damage = spell.TargetEffectRange.GenerateValue(Session.Random); damage.HealthPoints += (damage.HealthPoints != 0) ? magicalOffense : 0; damage.MagicPoints += (damage.MagicPoints != 0) ? magicalOffense : 0; damage.PhysicalOffense += (damage.PhysicalOffense != 0) ? magicalOffense : 0; damage.PhysicalDefense += (damage.PhysicalDefense != 0) ? magicalOffense : 0; damage.MagicalOffense += (damage.MagicalOffense != 0) ? magicalOffense : 0; damage.MagicalDefense += (damage.MagicalDefense != 0) ? magicalOffense : 0; // add in the spell damage return damage; }
/// <summary> /// Constructs a new MeleeCombatAction object. /// </summary> /// <param name="character">The character performing the action.</param> public MeleeCombatAction(Combatant combatant) : base(combatant) { }
/// <summary> /// Calculate the item damage done by the given combatant and item. /// </summary> 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); }
/// <summary> /// Start the given player's combat turn. /// </summary> private void BeginPlayerTurn(CombatantPlayer player) { // check the parameter if (player == null) { throw new ArgumentNullException("player"); } // set the highlight sprite highlightedCombatant = player; primaryTargetedCombatant = null; secondaryTargetedCombatants.Clear(); Session.Hud.ActionText = "Choose an Action"; }
/// <summary> /// Update the combat engine for this frame. /// </summary> private void UpdateCombatEngine(GameTime gameTime) { // check for the end of combat if (ArePlayersDefeated) { EndCombat(CombatEndingState.Loss); return; } else if (AreMonstersDefeated) { EndCombat(CombatEndingState.Victory); return; } // update the target selections if ((highlightedCombatant != null) && (highlightedCombatant.CombatAction != null)) { SetTargets(highlightedCombatant.CombatAction.Target, highlightedCombatant.CombatAction.AdjacentTargets); } //update active battle timers foreach (CombatantPlayer player in players) { if (player.AC > 5000 && player.readyForQueue) { player.AC = 5000; //add player if hes not already there if (!selectionQueue.Contains(player)) { selectionQueue.Enqueue(player); player.readyForQueue = false; } } else if(!player.IsDeadOrDying) player.AC += gameTime.ElapsedGameTime.Milliseconds; } foreach (CombatantMonster monster in monsters) { if (monster.AC > 4000 && monster.readyForQueue) { monster.AC = 4000; //add player if hes not already there if (!actionQueue.Contains(monster)) { actionQueue.Enqueue(monster); monster.readyForQueue = false; } } else if(!monster.IsDeadOrDying) monster.AC += gameTime.ElapsedGameTime.Milliseconds; } //show action menu if player's turn is ready if (selectionQueue.Count > 0 && !selectionQueueLocked) { if (currentSelector == null) { isPlayersTurn = true; currentSelector = selectionQueue.Peek(); currentSelector.IsTurnTaken = false; BeginPlayerTurn((CombatantPlayer)selectionQueue.Peek()); selectionQueue.Dequeue(); selectionQueueLocked = true; } } //resolve actions bool removeAction = false; //remove dead people in the front of the queue while(actionQueue.Count > 0 && actionQueue.Peek().IsDeadOrDying) actionQueue.Dequeue(); if (actionQueue.Count > 0 ) { //unlock queue after current combatant finishes its action. if(currentCombatant != null) if(currentCombatant.CombatAction == null) actionQueueLocked = false; else if(currentCombatant.CombatAction.Stage == CombatAction.CombatActionStage.NotStarted) actionQueueLocked = false; if (actionQueue.Peek().GetType() == typeof(CombatantPlayer) && !actionQueueLocked) { //execute player action actionQueue.Peek().CombatAction.Start(); currentCombatant = actionQueue.Peek(); removeAction = true; } if (actionQueue.Peek().GetType() == typeof(CombatantMonster) && !actionQueueLocked) { //execute monster action BeginMonsterTurn((CombatantMonster)actionQueue.Peek() ); currentCombatant = actionQueue.Peek(); removeAction = true; } if (removeAction) { actionQueue.Dequeue(); actionQueueLocked = true; } } // update the delay UpdateDelay(gameTime.ElapsedGameTime.Milliseconds); // UpdateDelay might cause combat to end due to a successful escape, // which will set the singleton to null. if (singleton == null) { return; } // update the players foreach (CombatantPlayer player in players) { player.Update(gameTime); } // update the monsters foreach (CombatantMonster monster in monsters) { monster.Update(gameTime); } // check for completion of the highlighted combatant if ((delayType == DelayType.NoDelay) && (highlightedCombatant != null) && highlightedCombatant.IsTurnTaken) { delayType = DelayType.EndCharacterTurn; } // handle any player input HandleInput(); }
/// <summary> /// Handle player input that affects the combat engine. /// </summary> private void HandleInput() { // only accept input during the players' turn // -- exit game, etc. is handled by GameplayScreen if (!IsPlayersTurn || IsPlayersTurnComplete || (highlightedCombatant == null)) { return; } #if DEBUG // cheat key if (InputManager.IsGamePadRightShoulderTriggered() || InputManager.IsKeyTriggered(Keys.W)) { EndCombat(CombatEndingState.Victory); return; } #endif // handle input while choosing an action if (highlightedCombatant.CombatAction != null) { // skip if its turn is over or the action is already going if (highlightedCombatant.IsTurnTaken || (highlightedCombatant.CombatAction.Stage != CombatAction.CombatActionStage.NotStarted)) { return; } // back out of the action if (InputManager.IsActionTriggered(InputManager.Action.Back)) { highlightedCombatant.CombatAction = null; SetTargets(null, 0); return; } // start the action if (InputManager.IsActionTriggered(InputManager.Action.Ok)) { actionQueue.Enqueue(highlightedCombatant); currentSelector = null; highlightedCombatant.IsTurnTaken = true; highlightedCombatant = null; primaryTargetedCombatant = null; secondaryTargetedCombatants.Clear(); isPlayersTurn = false; selectionQueueLocked = false; return; } // go to the next target if (InputManager.IsActionTriggered(InputManager.Action.TargetUp)) { // cycle through monsters or party members if (highlightedCombatant.CombatAction.IsOffensive) { // find the index of the current target int newIndex = monsters.FindIndex( delegate(CombatantMonster monster) { return (primaryTargetedCombatant == monster); }); // find the next living target do { newIndex = (newIndex + 1) % monsters.Count; } while (monsters[newIndex].IsDeadOrDying); // set the new target highlightedCombatant.CombatAction.Target = monsters[newIndex]; } else { // find the index of the current target int newIndex = players.FindIndex( delegate(CombatantPlayer player) { return (primaryTargetedCombatant == player); }); // find the next active, living target do { newIndex = (newIndex + 1) % players.Count; } while (players[newIndex].IsDeadOrDying); // set the new target highlightedCombatant.CombatAction.Target = players[newIndex]; } return; } // go to the previous target else if (InputManager.IsActionTriggered(InputManager.Action.TargetDown)) { // cycle through monsters or party members if (highlightedCombatant.CombatAction.IsOffensive) { // find the index of the current target int newIndex = monsters.FindIndex( delegate(CombatantMonster monster) { return (primaryTargetedCombatant == monster); }); // find the previous active, living target do { newIndex--; while (newIndex < 0) { newIndex += monsters.Count; } } while (monsters[newIndex].IsDeadOrDying); // set the new target highlightedCombatant.CombatAction.Target = monsters[newIndex]; } else { // find the index of the current target int newIndex = players.FindIndex( delegate(CombatantPlayer player) { return (primaryTargetedCombatant == player); }); // find the previous living target do { newIndex--; while (newIndex < 0) { newIndex += players.Count; } } while (players[newIndex].IsDeadOrDying); // set the new target highlightedCombatant.CombatAction.Target = players[newIndex]; } return; } } else // choosing which character will act { // move to the previous living character if (InputManager.IsActionTriggered( InputManager.Action.ActiveCharacterLeft)) { int newHighlightedPlayer = highlightedPlayer; do { newHighlightedPlayer--; while (newHighlightedPlayer < 0) { newHighlightedPlayer += players.Count; } } while (players[newHighlightedPlayer].IsDeadOrDying || players[newHighlightedPlayer].IsTurnTaken); if (newHighlightedPlayer != highlightedPlayer) { highlightedPlayer = newHighlightedPlayer; BeginPlayerTurn(players[highlightedPlayer]); } return; } // move to the next living character else if (InputManager.IsActionTriggered( InputManager.Action.ActiveCharacterRight)) { int newHighlightedPlayer = highlightedPlayer; do { newHighlightedPlayer = (newHighlightedPlayer + 1) % players.Count; } while (players[newHighlightedPlayer].IsDeadOrDying || players[newHighlightedPlayer].IsTurnTaken); if (newHighlightedPlayer != highlightedPlayer) { highlightedPlayer = newHighlightedPlayer; BeginPlayerTurn(players[highlightedPlayer]); } return; } Session.Hud.UpdateActionsMenu(); } }
/// <summary> /// Start the given player's combat turn. /// </summary> private void BeginMonsterTurn(CombatantMonster monster) { // if it's null, find a random living monster who has yet to take their turn if (monster == null) { // don't bother if all monsters have finished if (IsMonstersTurnComplete) { return; } // pick random living monsters who haven't taken their turn do { monster = monsters[Session.Random.Next(monsters.Count)]; } while (monster.IsTurnTaken || monster.IsDeadOrDying); } // set the highlight sprite highlightedCombatant = monster; primaryTargetedCombatant = null; secondaryTargetedCombatants.Clear(); // choose the action immediate monster.CombatAction = monster.ArtificialIntelligence.ChooseAction(); }