/// <summary> /// Given an ActionBase and a selection position, gets the characters that /// action will target seperated by AI characters and player characters. /// </summary> /// <param name="action">The action to check the targets for.</param> /// <param name="selectionPosition">A number representing the position the action targets.</param> /// <returns>A struct containing two IReadOnlyList of Characters, one for AI characters and another for player characters.</returns> protected SelectionCharacters GetSelectionTargets(ActionBase action, int selectionPosition) { var myCharacters = new List <Character>(); var playerCharacters = new List <Character>(); // If selection is in left column and something is in the middle or right column on the same row, can't select this position bool leftColumnBlocked = selectionPosition % 3 == 1 && LivingPlayerCharacters.Where( character => (selectionPosition + 1 == character.Position || selectionPosition + 2 == character.Position) && character.CurrentHealth > 0).Any(); // If selection is in middle column and something is in the right column on the same row, can't select this position bool middleColumnBlocked = selectionPosition % 3 == 2 && LivingPlayerCharacters.Where( character => (selectionPosition + 1 == character.Position && character.CurrentHealth > 0)).Any(); if (!action.CanSwitchTargetPosition) { var targets = AITargets.GetModifiedTargets(action); myCharacters.AddRange(AICharacters.Where(character => targets.Contains(character.Position))); playerCharacters.AddRange(LivingPlayerCharacters.Where(character => targets.Contains(character.Position))); return(new SelectionCharacters() { MyCharacters = myCharacters, PlayerCharacters = playerCharacters }); } // If the action can't target through units and the selection is on the player's field else if (!action.CanTargetThroughUnits && selectionPosition <= 9 && (leftColumnBlocked || middleColumnBlocked)) { myCharacters.Clear(); playerCharacters.Clear(); return(new SelectionCharacters() { MyCharacters = myCharacters, PlayerCharacters = playerCharacters }); } else { var modifiedSelection = AITargets.GetModifiedSelection(action, selectionPosition); foreach (var position in modifiedSelection) { if (position >= 10) { Character selectedCharacter = AICharacters.FirstOrDefault(character => character.Position == position); if (selectedCharacter != null) { myCharacters.Add(selectedCharacter); } } else if (position <= 9) { Character selectedCharacter = LivingPlayerCharacters.FirstOrDefault(character => character.Position == position); if (selectedCharacter != null) { playerCharacters.Add(selectedCharacter); } } } return(new SelectionCharacters() { MyCharacters = myCharacters, PlayerCharacters = playerCharacters }); } }
/// <summary> /// Gets all possible actions for the active character and splits them into offensive and defensive actions, /// giving each a priority based on the amount of healing, damage, and/or ai weight provided by the action. /// </summary> /// <returns>A struct providing the action priorities of offensive and defensive actions.</returns> protected virtual ActionPriorities EvaluateActions() { var defensiveActionsPoints = new Dictionary <ActionBase, int>(); var offensiveActionsPoints = new Dictionary <ActionBase, int>(); var defensiveActionPriorities = new Dictionary <ActionBase, int>(); var offensiveActionPriorities = new Dictionary <ActionBase, int>(); var allActions = new List <ActionBase>(); allActions.AddRange(_activeCharacter.Attacks); allActions.AddRange(_activeCharacter.SpellList); allActions.AddRange(_activeCharacter.SkillList); var consumables = _activeCharacter.Inventory .Where(item => item is Consumable) .Select(item => ((Consumable)item).ItemSpell); allActions.AddRange(consumables); // Use average max health as basis for effectiveness of damage and healing int averageMaxHealth = 0; foreach (var character in AICharacters) { averageMaxHealth += character.CurrentMaxHealth; } averageMaxHealth /= AICharacters.Count(); // Calculate total healing and damage and use it to determine if an action is offensive or defensive foreach (var action in allActions) { int damage = DamageCalculator.GetDamageAsInt(_activeCharacter, action); int healing = DamageCalculator.GetHealing(_activeCharacter, action); int percentHealing = DamageCalculator.GetHealingPercentage(_activeCharacter, action); int netDamage = damage - healing; if (netDamage < 0 || percentHealing > 0 || !action.IsOffensive) { int maxPotential = -netDamage; maxPotential += (percentHealing * averageMaxHealth / 100); defensiveActionsPoints[action] = maxPotential; } else { int maxPotential = netDamage; offensiveActionsPoints[action] = maxPotential; } } int medianHealing = MathExtensions.GetMedian(defensiveActionsPoints.Values.ToList()); int medianDamage = MathExtensions.GetMedian(offensiveActionsPoints.Values.ToList()); // Assign action priorities based on how an action performs on par with other actions foreach (var key in defensiveActionsPoints.Keys) { int healing = defensiveActionsPoints[key]; defensiveActionPriorities[key] = key.AiWeight; if (medianHealing > 0) { if (PercentageCalculator.GetPercentage(healing, medianHealing) >= 150) { defensiveActionPriorities[key] += 3; } else if (PercentageCalculator.GetPercentage(healing, medianHealing) >= 120) { defensiveActionPriorities[key] += 2; } } else { defensiveActionPriorities[key] += 1; } } foreach (var key in offensiveActionsPoints.Keys) { int damage = offensiveActionsPoints[key]; offensiveActionPriorities[key] = key.AiWeight; if (medianDamage > 0) { if (PercentageCalculator.GetPercentage(damage, medianDamage) >= 150) { offensiveActionPriorities[key] += 3; } else if (PercentageCalculator.GetPercentage(damage, medianDamage) >= 120) { offensiveActionPriorities[key] += 2; } } else { offensiveActionPriorities[key] += 1; } } return(new ActionPriorities() { DefensiveActionPriorities = defensiveActionPriorities, OffensiveActionPriorities = offensiveActionPriorities }); }