| summaryrefslogtreecommitdiff | 
diff options
| author | nsensfel <SpamShield0@noot-noot.org> | 2019-06-05 17:52:58 +0200 | 
|---|---|---|
| committer | nsensfel <SpamShield0@noot-noot.org> | 2019-06-05 17:52:58 +0200 | 
| commit | 9e7dcabc164eacb7024387e060623993b48c60b4 (patch) | |
| tree | 50df2a6143eb2eca18b2588383630c81bb11c1b6 /src/battle/mechanic/action | |
| parent | 1afb69a11b0e291c7bfd6c24bdd8e55742e61889 (diff) | |
[Broken] ...
Diffstat (limited to 'src/battle/mechanic/action')
| -rw-r--r-- | src/battle/mechanic/action/btl_action_attack.erl | 655 | ||||
| -rw-r--r-- | src/battle/mechanic/action/btl_action_move.erl | 291 | ||||
| -rw-r--r-- | src/battle/mechanic/action/btl_action_switch_weapon.erl | 66 | ||||
| -rw-r--r-- | src/battle/mechanic/action/btl_turn_actions_opportunity_attack.erl | 178 | 
4 files changed, 1190 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}. diff --git a/src/battle/mechanic/action/btl_action_move.erl b/src/battle/mechanic/action/btl_action_move.erl new file mode 100644 index 0000000..a32a40f --- /dev/null +++ b/src/battle/mechanic/action/btl_action_move.erl @@ -0,0 +1,291 @@ +-module(btl_action_move). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle/3 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec cross +   ( +      non_neg_integer(), +      shr_map:type(), +      list(shr_location:type()), +      list(shr_direction:enum()), +      non_neg_integer(), +      shr_location:type() +   ) +   -> +   { +      shr_location:type(), +      list(shr_direction:type()), +      non_neg_integer(), +      list(shr_map_marker:type()) +   }. +cross (_PlayerIX, _Map, _ForbiddenLocations, [], Cost, Location) -> +   {Location, [], Cost, []}; +cross (PlayerIX, Map, ForbiddenLocations, [Step|NextSteps], Cost, Location) -> +   NextLocation = shr_location:apply_direction(Step, Location), +   NextTileInstance = shr_map:get_tile_instance(NextLocation, Map), +   NextTileClassID = shr_tile_instance:get_tile_id(NextTileInstance), +   NextTile = shr_tile:from_id(NextTileClassID), +   NextCost = (Cost + shr_tile:get_cost(NextTile)), +   IsForbidden = +      lists:foldl +      ( +         fun (ForbiddenLocation, Prev) -> +            (Prev or (NextLocation == ForbiddenLocation)) +         end, +         false, +         ForbiddenLocations +      ), + +   false = IsForbidden, + +   Interruptions = +      list:foldl +      ( +         fun (MarkerName, CurrentInterruptions) -> +            case shr_map:get_marker(MarkerName, Map) of +               {ok, Marker} -> +                  case shr_map_marker:interrupts_movement(PlayerIX, Marker) of +                     true -> [Marker|CurrentInterruptions]; +                     _ -> CurrentInterruptions +                  end; + +               error -> +                  %% TODO: Error. +                  CurrentInterruptions +            end +         end, +         [], +         shr_tile_instance:get_triggers(NextTileInstance) +      ), + +   case Interruptions of +      [] -> +         cross +         ( +            PlayerIX, +            Map, +            ForbiddenLocations, +            NextSteps, +            NextCost, +            NextLocation +         ); + +      _ -> {NextLocation, NextSteps, NextCost, Interruptions} +   end. + +-spec cross +   ( +      non_neg_integer(), +      shr_map:type(), +      list(shr_location:type()), +      list(shr_direction:enum()), +      shr_location:type() +   ) +   -> +   { +      shr_location:type(), +      list(shr_direction:type()), +      non_neg_integer(), +      list(shr_map_marker:type()) +   }. +cross (PlayerIX, Map, ForbiddenLocations, Path, Location) -> +   cross(PlayerIX, Map, ForbiddenLocations, Path, 0, Location). + +-spec get_path_cost_and_destination +   ( +      non_neg_integer(), +      btl_character:type(), +      btl_character_turn_update:type(), +      list(shr_direction:type()) +   ) +   -> +   { +      non_neg_integer(), +      shr_location:type(), +      list(shr_direction:type()), +      list(shr_map_marker:type()) +   }. +get_path_cost_and_destination (CharacterIX, Character, Update, Path) -> +   Battle = btl_character_turn_update:get_battle(Update), +   Map = btl_battle:get_map(Battle), + +   % [TODO][OPTIMIZATION] Redundant calculations. +   % This is recalculated at every move action, despite there be no need +   % to: The client will not allow the character to go somewhere that would +   % only be freed because of an event. +   ForbiddenLocations = +      orddict:fold +      ( +         fun (IX, Char, Prev) -> +            IsAlive = btl_character:get_is_alive(Char), +            if +               (IX == CharacterIX) -> Prev; +               (not IsAlive) -> Prev; +               true -> +                  ordsets:add_element(btl_character:get_location(Char), Prev) +            end +         end, +         ordsets:new(), +         btl_battle:get_characters(Battle) +      ), + +   {NewLocation, RemainingPath, Cost, Interruptions} = +      cross +      ( +         btl_character:get_player_index(Character), +         Map, +         ForbiddenLocations, +         Path, +         btl_character:get_location(Character) +      ), + +   {Cost, NewLocation, RemainingPath, Interruptions}. + +-spec get_movement_points +   ( +      btl_action:type(), +      btl_character:type() +   ) +   -> non_neg_integer(). +get_movement_points (Action, Character) -> +   case btl_action:get_movement_points(Action) of +      -1 -> +         shr_statistics:get_movement_points +         ( +            shr_character:get_statistics +            ( +               btl_character:get_base_character(Character) +            ) +         ); + +      Other -> Other +   end. + +-spec commit_move +   ( +      non_neg_integer(), +      btl_character:type(), +      btl_character_turn_update:type(), +      list(shr_direction:type()), +      shr_location:type() +   ) +   -> btl_character_turn_update:type(). +commit_move (CharacterIX, Character, S0Update, Path, NewLocation) -> +   S0Battle = btl_character_turn_update:get_battle(S0Update), + +   Map = btl_battle:get_map(S0Battle), + +   TileOmnimods = +      shr_tile:get_omnimods +      ( +         shr_tile:from_id +         ( +            shr_tile_instance:get_tile_id +            ( +               shr_map:get_tile_instance(NewLocation, Map) +            ) +         ) +      ), + +   {UpdatedCharacter, CharacterAtaxiaUpdate} = +      btl_character:ataxia_set_location(NewLocation, TileOmnimods, Character), + +   {UpdatedBattle, BattleAtaxiaUpdate} = +      btl_battle:ataxia_set_character +      ( +         CharacterIX, +         UpdatedCharacter, +         CharacterAtaxiaUpdate, +         S0Battle +      ), + +   TimelineItem = +      btl_turn_result:new_character_moved(CharacterIX, Path, NewLocation), + +   S1Update = btl_character_turn_update:add_to_timeline(TimelineItem, S0Update), +   S2Update = +      btl_character_turn_update:ataxia_set_battle +      ( +         UpdatedBattle, +         BattleAtaxiaUpdate, +         S1Update +      ), + +   S2Update. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle +   ( +      btl_action:type(), +      btl_character:type(), +      btl_character_turn_update:type() +   ) +   -> +   ( +      {'ok', btl_character_turn_update:type()} +      | {'events', list(btl_action:type()), btl_character_turn_update:type()} +   ). +handle (Action, Character, S0Update) -> +   Path = btl_action:get_path(Action), +   CharacterIX = btl_action:get_actor_index(Action), + +   {PathCost, NewLocation, RemainingPath, Interruptions} = +      get_path_cost_and_destination(CharacterIX, Character, S0Update, Path), + +   MovementPoints = get_movement_points(Action, Character), + +   true = (MovementPoints >= PathCost), + +   S1Update = commit_move(CharacterIX, Character, S0Update, Path, NewLocation), + +   case RemainingPath of +      [] -> {ok, S1Update}; +      _ -> +         {events, +            ( +               lists:foldl +               ( +                  fun (Marker, CurrentActions) -> +                     ( +                        btl_action:from_map_marker +                        ( +                           CharacterIX, +                           Character, +                           Marker +                        ) +                        ++ +                        CurrentActions +                     ) +                  end, +                  [], +                  Interruptions +               ) +               ++ +               [ +                  btl_action:new_move +                  ( +                     CharacterIX, +                     RemainingPath, +                     (MovementPoints - PathCost) +                  ) +               ] +            ), +            S1Update +         } +   end. diff --git a/src/battle/mechanic/action/btl_action_switch_weapon.erl b/src/battle/mechanic/action/btl_action_switch_weapon.erl new file mode 100644 index 0000000..cf1a31a --- /dev/null +++ b/src/battle/mechanic/action/btl_action_switch_weapon.erl @@ -0,0 +1,66 @@ +-module(btl_turn_actions_switch_weapon). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle/3 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle +   ( +      btl_action:type(), +      btl_character:type(), +      btl_character_turn_update:type() +   ) +   -> {'ok', btl_character_turn_update:type()}. +handle (Action, Character, S0Update) -> +   CharacterIX = btl_action:get_actor_index(Action), + +   BaseCharacter = btl_character:get_base_character(Character), + +   {UpdatedBaseCharacter, BaseCharacterAtaxiaUpdate} = +      shr_character:ataxia_switch_weapons(BaseCharacter), + +   {UpdatedCharacter, CharacterAtaxiaUpdate} = +      btl_character:ataxia_set_base_character +      ( +         UpdatedBaseCharacter, +         BaseCharacterAtaxiaUpdate, +         Character +      ), + +   {UpdatedBattle, BattleAtaxiaUpdate} = +      btl_battle:ataxia_set_character +      ( +         CharacterIX, +         UpdatedCharacter, +         CharacterAtaxiaUpdate, +         btl_character_turn_update:get_battle(S0Update) +      ), + +   TimelineItem = btl_turn_result:new_character_switched_weapons(CharacterIX), + +   S1Update = btl_character_turn_update:add_to_timeline(TimelineItem, S0Update), +   S2Update = +      btl_character_turn_update:ataxia_set_battle +      ( +         UpdatedBattle, +         BattleAtaxiaUpdate, +         S1Update +      ), + +   {ok, S2Update}. diff --git a/src/battle/mechanic/action/btl_turn_actions_opportunity_attack.erl b/src/battle/mechanic/action/btl_turn_actions_opportunity_attack.erl new file mode 100644 index 0000000..c1dbbdd --- /dev/null +++ b/src/battle/mechanic/action/btl_turn_actions_opportunity_attack.erl @@ -0,0 +1,178 @@ +-module(btl_turn_actions_opportunity_attack). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle/2 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle +   ( +      btl_action:type(), +      btl_character_turn_update:type() +   ) +   -> {ok, btl_character_turn_update:type()}. +handle (BattleAction, Update) -> +   {S0Update, Battle} = btl_character_turn_update:get_battle(Update), +   {S1Update, Character} = btl_character_turn_update:get_character(S0Update), + +   DefendingPlayerIX = btl_character:get_player_index(Character), +   DefendingPlayer = btl_battle:get_player(DefendingPlayerIX, Battle), +   DefendingPlayerLuck = btl_player:get_luck(DefendingPlayer), + +   AttackerIX = btl_action:get_target_ix(BattleAction), +   AttackerRef = btl_battle:get_character(AttackerIX, Battle), +   Attacker = btl_battle:resolve_character(AttackerRef, Battle), + +   AttackingPlayerIX = btl_character:get_player_index(Attacker), +   AttackingPlayer = btl_battle:get_player(AttackingPlayerIX, Battle), +   AttackingPlayerLuck = btl_player:get_luck(AttackingPlayer), + +   Attack = btl_attack:attack_of_opportunity(), + +   AttackEffect = +      btl_attack:get_description_of +      ( +         Attack, +         btl_character:get_base_character(Character), +         btl_character:get_base_character(Attacker), +         AttackingPlayerLuck, +         DefendingPlayerLuck +      ), + +   { +      AttackResult, +      NewAttackerHealth, +      S0NewAttackerLuck, +      NewDefenderHealth, +      S0NewDefenderLuck +   } = +      btl_attack:apply_to_healths_and_lucks +      ( +         AttackEffect, +         btl_character:get_current_health(Attacker), +         AttackingPlayerLuck, +         btl_character:get_current_health(Character), +         DefendingPlayerLuck +      ), + +   S1NewAttackerLuck = +      case {(S0NewAttackerLuck =< -2), (S0NewAttackerLuck >= 2)}  of +         {true, _} -> (S0NewAttackerLuck + 2); +         {_, true} -> (S0NewAttackerLuck - 2); +         _ -> 0 +      end, + +   S1NewDefenderLuck = +      case {(S0NewDefenderLuck =< -2), (S0NewDefenderLuck >= 2)}  of +         {true, _} -> (S0NewDefenderLuck + 2); +         {_, true} -> (S0NewDefenderLuck - 2); +         _ -> 0 +      end, + +   {UpdatedAttackingPlayer, AttackingPlayerAtaxiaUpdate} = +      btl_player:ataxia_set_luck(S1NewAttackerLuck, AttackingPlayer), + +   {UpdatedDefendingPlayer, DefendingPlayerAtaxiaUpdate} = +      btl_player:ataxia_set_luck(S1NewDefenderLuck, DefendingPlayer), + +   {UpdatedCharacter, CharacterAtaxiaUpdate} = +      btl_character:ataxia_set_current_health(NewDefenderHealth, Character), + +   {UpdatedAttackerRef, AttackerRefAtaxiaUpdate} = +      btl_character:ataxia_set_current_health(NewAttackerHealth, AttackerRef), + +   {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 +      ( +         AttackerIX, +         UpdatedAttackerRef, +         AttackerRefAtaxiaUpdate, +         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 +      ( +         AttackerIX, +         btl_character_turn_update:get_character_ix(S3Update), +         AttackResult, +         S0NewAttackerLuck, +         S0NewDefenderLuck +      ), + +   S4Update = btl_character_turn_update:add_to_timeline(TimelineItem, S3Update), + +   S5Update = +      case (NewDefenderHealth > 0) of +         true -> S4Update; +         false -> +            btl_victory_progression:handle_character_loss(Character, S4Update) +      end, + +   {ok, S5Update}.  | 


