| summaryrefslogtreecommitdiff | 
diff options
Diffstat (limited to 'src/battle/mechanic/action/btl_action_attack.erl')
| -rw-r--r-- | src/battle/mechanic/action/btl_action_attack.erl | 655 | 
1 files changed, 655 insertions, 0 deletions
| diff --git a/src/battle/mechanic/action/btl_action_attack.erl b/src/battle/mechanic/action/btl_action_attack.erl new file mode 100644 index 0000000..1241735 --- /dev/null +++ b/src/battle/mechanic/action/btl_action_attack.erl @@ -0,0 +1,655 @@ +-module(btl_turn_actions_attack). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle/3 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec roll_precision_modifier +   ( +      shr_statistics:type(), +      shr_statistics:type(), +      integer() +   ) +   -> {float(), integer(), integer()}. +roll_precision_modifier (Statistics, TargetStatistics, TargetLuck) -> +   TargetDodges = shr_statistics:get_dodges(TargetStatistics), +   Accuracy = shr_statistics:get_accuracy(Statistics), +   MissChance = max(0, (TargetDodges - Accuracy)), + +   {Roll, _IsSuccess, PositiveModifier, NegativeModifier} = +      shr_roll:percentage_with_luck(MissChance, TargetLuck), + +   { +      case Roll of +         X when (X =< MissChance) -> 0.0; +         X when (X =< (MissChance * 2)) -> 0.5; +         _ -> 1.0 +      end, +      PositiveModifier, +      NegativeModifier +   }. + +-spec roll_critical_hit_modifier +   ( +      shr_statistics:type(), +      integer() +   ) +   -> {boolean(), integer(), integer()}. +roll_critical_hit_modifier (Statistics, Luck) -> +   CriticalHitChance = shr_statistics:get_critical_hits(Statistics), +   {_Roll, IsSuccess, PositiveModifier, NegativeModifier} = +      shr_roll:percentage_with_luck(CriticalHitChance, Luck), + +   { +      case IsSuccess of +         true -> 2.0; % [TODO][FUTURE]: variable critical multiplier? +         false -> 1.0 +      end, +      PositiveModifier, +      NegativeModifier +   }. + +-spec roll_parry +   ( +      shr_statistics:type(), +      integer() +   ) +   -> {boolean(), integer(), integer()}. +roll_parry (DefenderStatistics, DefenderLuck) -> +   DefenderParryChance = shr_statistics:get_parries(DefenderStatistics), +   {_Roll, IsSuccess, PositiveModifier, NegativeModifier} = +      shr_roll:percentage_with_luck(DefenderParryChance, DefenderLuck), + +   {IsSuccess, PositiveModifier, NegativeModifier}. + +-spec get_damage +   ( +      precision(), +      boolean(), +      float(), +      shr_omnimods:type(), +      shr_omnimods:type() +   ) +   -> non_neg_integer(). +get_damage +( +   Precision, +   IsCritical, +   StartingDamageMultiplier, +   AttackerOmnimods, +   DefenderOmnimods +) -> +   ActualDamageMultiplier = +      ( +         StartingDamageMultiplier +         * +         ( +            case Precision of +               misses -> 0; +               grazes -> 0.5; +               hits -> 1 +            end +         ) +         * +         ( +            case IsCritical of +               true -> 2; +               _ -> 1 +            end +         ) +      ), + +   ActualDamage = +      shr_omnimods:get_attack_damage +      ( +         ActualDamageMultiplier, +         AttackerOmnimods, +         DefenderOmnimods +      ), + +   ActualDamage. + +-spec get_character_abilities +   ( +      btl_action:type(), +      btl_character:type(), +      btl_character:type() +   ) +   -> {boolean(), boolean(), boolean()}. +get_character_abilities (Action, Character, TargetCharacter) -> +   CharacterWeapon = +      shr_character:get_active_weapon +      ( +         btl_character:get_base_character(Character) +      ), + +   TargetCharacterWeapon = +      shr_character:get_active_weapon +      ( +         btl_character:get_base_character(TargetCharacter) +      ), + +   DefenseRange = shr_weapon:get_minimum_range(CharacterWeapon), +   AttackRange =  shr_weapon:get_maximum_range(CharacterWeapon), +   TargetDefenseRange = shr_weapon:get_minimum_range(TargetCharacterWeapon), +   TargetAttackRange =  shr_weapon:get_maximum_range(TargetCharacterWeapon), + +   IsNotOpportunistic = btl_action:get_is_opportunistic(Action), + +   AttackRange = +      shr_location:dist +      ( +         btl_character:get_location(Character), +         btl_character:get_location(TargetCharacter) +      ), + +   { +      (DefenseRange == 0), +      ( +         IsNotOpportunistic +         and (TargetDefenseRange == 0) +         and (TargetAttackRange =< AttackRange) +      ), +      ( +         IsNotOpportunistic +         and (TargetAttackRange =< AttackRange) +      ) +   }. + +-spec effect_of_attack +   ( +      btl_attack:category(), +      non_neg_integer(), +      non_neg_integer(), +      btl_character:type(), +      btl_character:type(), +      integer(), +      integer(), +      boolean(), +      btl_character_turn_update:type() +   ) +   -> +   { +      btl_character:type(), +      btl_character:type(), +      integer(), +      integer(), +      btl_character_turn_update:type(), +      btl_attack:type() +   }. +effect_of_attack +( +   Category, +   CharacterIX, +   TargetCharacterIX, +   S0Character, +   S0TargetCharacter, +   S0Luck, +   S0TargetLuck, +   TargetCanParry, +   S0Update +) -> +   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +   %%%% Roll parry to see if the roles have to be swapped. %%%%%%%%%%%%%%%%%%%%% + +   {ParryIsSuccessful, ParryPositiveLuckMod, ParryNegativeLuckMod} = +      case TargetCanParry of +         true -> +            TargetStatistics = +               shr_character:get_statistics +               ( +                  btl_character:get_base_character(TargetCharacter) +               ), +            roll_parry(TargetStatistics, S0TargetLuck); + +         false -> {false, 0, 0} +      end, + +   { AttackerIX, DefenderIX, Attacker, Defender, AttackerLuck, DefenderLuck } = +      case ParryIsSuccessful of +         true -> +            { +               TargetCharacterIX, +               CharacterIX, +               TargetCharacter, +               Character, +               TargetLuck, +               Luck +            }; + +         false -> +            { +               CharacterIX, +               TargetCharacterIX, +               Character, +               TargetCharacter, +               Luck, +               TargetLuck +            } +      end, + +   AttackerStatistics = + +-spec handle_attack_sequence +   ( +      list({btl_attack:category(), boolean()}), +      non_neg_integer(), +      non_neg_integer(), +      btl_character:type(), +      btl_character:type(), +      integer(), +      integer(), +      list(btl_attack:type()), +      btl_character_turn_update:type() +   ) +   -> +   { +      btl_character:type(), +      btl_character:type(), +      integer(), +      integer(), +      list(btl_attack:type()), +      btl_character_turn_update:type() +   }. +handle_attack_sequence +( +   [], +   _CharacterIX, +   _TargetCharacterIX, +   Character, +   TargetCharacter, +   PlayerLuck, +   TargetPlayerLuck, +   Results, +   Update +) +-> +   { +      Character, +      TargetCharacter, +      PlayerLuck, +      TargetPlayerLuck, +      lists:reverse(Results), +      Update +   }; +handle_attack_sequence +( +   [{first, TargetCanParry}|NextAttacks], +   CharacterIX, +   TargetCharacterIX, +   S0Character, +   S0TargetCharacter, +   S0PlayerLuck, +   S0TargetPlayerLuck, +   Results, +   S0Update +) +-> +   { +      S1Character, +      S1TargetCharacter, +      S1PlayerLuck, +      S1TargetPlayerLuck, +      S1Update, +      Result +   } = +      effect_of_attack +      ( +         first, +         CharacterIX, +         TargetCharacterIX, +         S0Character, +         S0TargetCharacter, +         S0PlayerLuck, +         S0TargetPlayerLuck, +         TargetCanParry, +         S0Update +      ), + +   handle_attack_sequence +   ( +      NextAttacks, +      CharacterIX, +      TargetCharacterIX, +      S1Character, +      S1TargetCharacter, +      S1PlayerLuck, +      S1TargetPlayerLuck, +      [Result|Results], +      S1Update +   ); +handle_attack_sequence +( +   [{counter, CanParry}|NextAttacks], +   CharacterIX, +   TargetCharacterIX, +   S0Character, +   S0TargetCharacter, +   S0PlayerLuck, +   S0TargetPlayerLuck, +   Results, +   S0Update +) +-> +   { +      S1TargetCharacter, +      S1Character, +      S2TargetPlayerLuck, +      S2PlayerLuck, +      S1Update, +      Result +   } = +      effect_of_attack +      ( +         counter, +         TargetCharacterIX, +         CharacterIX, +         S0TargetCharacter, +         S0Character, +         S1TargetPlayerLuck, +         S1PlayerLuck, +         CanParry, +         S0Update +      ), + +   handle_attack_sequence +   ( +      NextAttacks, +      CharacterIX, +      TargetCharacterIX, +      S1Character, +      S1TargetCharacter, +      S2PlayerLuck, +      S2TargetPlayerLuck, +      [Result|Results], +      S1Update +   ); +handle_attack_sequence +( +   [{second, TargetCanParry}|NextAttacks], +   CharacterIX, +   TargetCharacterIX, +   S0Character, +   S0TargetCharacter, +   S0PlayerLuck, +   S0TargetPlayerLuck, +   Results, +   S0Update +) +-> +   Statistics = shr_character:get_statistics(S0Character), +   DoubleAttackChance = shr_statistics:get_double_hits(Statistics), +   {_Roll, IsSuccessful, PositiveModifier, NegativeModifier} = +      shr_roll:percentage_with_luck(DoubleAttackChance, S0PlayerLuck), + +   S1PlayerLuck = (S0PlayerLuck + PositiveModifier), +   S1TargetPlayerLuck = (S0TargetPlayerLuck + NegativeModifier), + +   case IsSuccessful of +      false -> +         handle_attack_sequence +         ( +            NextAttacks, +            CharacterIX, +            TargetCharacterIX, +            S0Character, +            S0TargetCharacter, +            S1PlayerLuck, +            S1TargetPlayerLuck, +            Results, +            S0Update +         ); + +      true -> +         { +            S1Character, +            S1TargetCharacter, +            S2PlayerLuck, +            S2TargetPlayerLuck, +            S1Update, +            Result +         } = +            effect_of_attack +            ( +               second, +               CharacterIX, +               TargetCharacterIX, +               S0Character, +               S0TargetCharacter, +               S1PlayerLuck, +               S1TargetPlayerLuck, +               TargetCanParry, +               S0Update +            ), + +         handle_attack_sequence +         ( +            CharacterIX, +            TargetCharacterIX, +            NextAttacks, +            S1Character, +            S1TargetCharacter, +            S2PlayerLuck, +            S2TargetPlayerLuck, +            [Result|Results], +            S1Update +         ) +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle +   ( +      btl_action:type(), +      btl_character:type(), +      btl_character_turn_update:type() +   ) +   -> {ok, btl_character_turn_update:type()}. +handle (Action, S0Character, S0Update) -> +   S0Battle = btl_character_turn_update:get_battle(S0Update), +   CharacterIX = btl_action:get_actor_index(Action), + +   PlayerIX = btl_character:get_player_index(S0Character), +   Player = btl_battle:get_player(PlayerIX, S0Battle), +   S0PlayerLuck = btl_player:get_luck(Player), + +   TargetCharacterIX = btl_action:get_target_index(Action), +   {S0TargetCharacter, S1Battle} = +      btl_battle:get_resolved_character(TargetCharacterIX, S0Battle), + +   TargetPlayerIX = btl_character:get_player_index(TargetCharacter), +   TargetPlayer = btl_battle:get_player(TargetPlayerIX, S1Battle), +   TargetPlayerLuck = btl_player:get_luck(TargetPlayer), + +   {CanParry, TargetCanParry, TargetCanCounter} = +      get_character_abilities(Action, S0Character, S0TargetCharacter), + +   { +      S1Character, +      S1TargetCharacter, +      S1PlayerLuck, +      S1TargetPlayerLuck, +      Results, +      S1Update +   } = +      handle_attack_sequence +      ( +         case TargetCanCounter of +            true -> +               [ +                  {first, TargetCanParry}, +                  {counter, CanParry}, +                  {second, TargetCanParry} +               ]; + +            false -> +               [ +                  {first, TargetCanParry}, +                  {second, TargetCanParry} +               ] +         end, +         S1Character, +         S1TargetCharacter, +         S1PlayerLuck, +         S1TargetPlayerLuck, +         Results, +         S1Update +      ), + +   { +      AttackEffects, +      RemainingAttackerHealth, +      RemainingDefenderHealth, +      NewAttackerLuck, +      NewDefenderLuck +   } = +      handle_attack_sequence +      ( +         Character, +         btl_character:get_current_health(Character), +         TargetCharacter, +         btl_character:get_current_health(TargetCharacter), +         PlayerLuck, +         TargetPlayerLuck, +         AttackSequence, +         [] +      ), + +   S0NewAttackerLuck = +      case {(NewAttackerLuck =< -2), (NewAttackerLuck >= 2)}  of +         {true, _} -> (NewAttackerLuck + 2); +         {_, true} -> (NewAttackerLuck - 2); +         _ -> 0 +      end, + +   S0NewDefenderLuck = +      case {(NewDefenderLuck =< -2), (NewDefenderLuck >= 2)}  of +         {true, _} -> (NewDefenderLuck + 2); +         {_, true} -> (NewDefenderLuck - 2); +         _ -> 0 +      end, + +   {UpdatedAttackingPlayer, AttackingPlayerAtaxiaUpdate} = +      btl_player:ataxia_set_luck(S0NewAttackerLuck, AttackingPlayer), + +   {UpdatedDefendingPlayer, DefendingPlayerAtaxiaUpdate} = +      btl_player:ataxia_set_luck(S0NewDefenderLuck, DefendingPlayer), + +   {UpdatedCharacter, CharacterAtaxiaUpdate} = +      btl_character:ataxia_set_current_health +      ( +         RemainingAttackerHealth, +         Character +      ), + +   {UpdatedTargetCharacterRef, TargetCharacterRefAtaxiaUpdate} = +      btl_character:ataxia_set_current_health +      ( +         RemainingDefenderHealth, +         TargetCharacterRef +      ), + +   {S0Battle, BattleAtaxiaUpdate0} = +      btl_battle:ataxia_set_player +      ( +         AttackingPlayerIX, +         UpdatedAttackingPlayer, +         AttackingPlayerAtaxiaUpdate, +         Battle +      ), + +   {S1Battle, BattleAtaxiaUpdate1} = +      btl_battle:ataxia_set_player +      ( +         DefendingPlayerIX, +         UpdatedDefendingPlayer, +         DefendingPlayerAtaxiaUpdate, +         S0Battle +      ), + +   {S2Battle, BattleAtaxiaUpdate2} = +      btl_battle:ataxia_set_character +      ( +         TargetIX, +         UpdatedTargetCharacterRef, +         TargetCharacterRefAtaxiaUpdate, +         S1Battle +      ), + +   % Potential danger ahead: we're going to update both the 'character' and +   % 'battle' members of a btl_character_turn_update. +   % 'S1Update' is sure to have both up to date (as it's the result of 'get' +   % requests for both) and there is no risk of the 'battle' update influencing +   % 'character', making what follows safe. + +   S2Update = +      btl_character_turn_update:ataxia_set_battle +      ( +         S2Battle, +         false, +         ataxic:optimize +         ( +            ataxic:sequence +            ( +               [ +                  BattleAtaxiaUpdate0, +                  BattleAtaxiaUpdate1, +                  BattleAtaxiaUpdate2 +               ] +            ) +         ), +         S1Update +      ), + +   S3Update = +      btl_character_turn_update:ataxia_set_character +      ( +         UpdatedCharacter, +         CharacterAtaxiaUpdate, +         S2Update +      ), + +   TimelineItem = +      btl_turn_result:new_character_attacked +      ( +         btl_character_turn_update:get_character_ix(S3Update), +         TargetIX, +         AttackEffects, +         S0NewAttackerLuck, +         S0NewDefenderLuck +      ), + +   S4Update = btl_character_turn_update:add_to_timeline(TimelineItem, S3Update), + +   S5Update = +      case (RemainingAttackerHealth > 0) of +         true -> S4Update; +         false -> +            btl_victory_progression:handle_character_loss(Character, S4Update) +      end, + +   S6Update = +      case (RemainingDefenderHealth > 0) of +         true -> S5Update; +         false -> +            btl_victory_progression:handle_character_loss +            ( +               TargetCharacterRef, +               S5Update +            ) +      end, + +   {ok, S6Update}. | 


