From 9e7dcabc164eacb7024387e060623993b48c60b4 Mon Sep 17 00:00:00 2001 From: nsensfel Date: Wed, 5 Jun 2019 17:52:58 +0200 Subject: [Broken] ... --- src/battle/mechanic/action/btl_action_attack.erl | 655 +++++++++++++++++++++ src/battle/mechanic/action/btl_action_move.erl | 291 +++++++++ .../mechanic/action/btl_action_switch_weapon.erl | 66 +++ .../action/btl_turn_actions_opportunity_attack.erl | 178 ++++++ src/battle/mechanic/btl_attack.erl | 109 +--- .../mechanic/btl_turn_actions_management.erl | 82 +-- .../turn_action/btl_turn_actions_attack.erl | 339 ----------- .../mechanic/turn_action/btl_turn_actions_move.erl | 278 --------- .../btl_turn_actions_opportunity_attack.erl | 178 ------ .../turn_action/btl_turn_actions_switch_weapon.erl | 55 -- src/battle/struct/btl_action.erl | 85 +-- 11 files changed, 1273 insertions(+), 1043 deletions(-) create mode 100644 src/battle/mechanic/action/btl_action_attack.erl create mode 100644 src/battle/mechanic/action/btl_action_move.erl create mode 100644 src/battle/mechanic/action/btl_action_switch_weapon.erl create mode 100644 src/battle/mechanic/action/btl_turn_actions_opportunity_attack.erl delete mode 100644 src/battle/mechanic/turn_action/btl_turn_actions_attack.erl delete mode 100644 src/battle/mechanic/turn_action/btl_turn_actions_move.erl delete mode 100644 src/battle/mechanic/turn_action/btl_turn_actions_opportunity_attack.erl delete mode 100644 src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl 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}. diff --git a/src/battle/mechanic/btl_attack.erl b/src/battle/mechanic/btl_attack.erl index cbc1baa..8a10cc6 100644 --- a/src/battle/mechanic/btl_attack.erl +++ b/src/battle/mechanic/btl_attack.erl @@ -16,109 +16,6 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec roll_precision - ( - shr_statistics:type(), - shr_statistics:type(), - integer() - ) - -> {precision(), integer(), integer()}. -roll_precision -( - AttackerStatistics, - DefenderStatistics, - DefenderLuck -) -> - DefenderDodges = shr_statistics:get_dodges(DefenderStatistics), - AttackerAccuracy = shr_statistics:get_accuracy(AttackerStatistics), - MissChance = max(0, (DefenderDodges - AttackerAccuracy)), - - {Roll, _IsSuccess, PositiveModifier, NegativeModifier} = - shr_roll:percentage_with_luck(MissChance, DefenderLuck), - - { - case Roll of - X when (X =< MissChance) -> misses; - X when (X =< (MissChance * 2)) -> grazes; - _ -> hits - end, - PositiveModifier, - NegativeModifier - }. - --spec roll_critical_hit - ( - shr_statistics:type(), - integer() - ) - -> {boolean(), integer(), integer()}. -roll_critical_hit (AttackerStatistics, AttackerLuck) -> - CriticalHitChance = shr_statistics:get_critical_hits(AttackerStatistics), - {_Roll, IsSuccess, PositiveModifier, NegativeModifier} = - shr_roll:percentage_with_luck(CriticalHitChance, AttackerLuck), - - {IsSuccess, 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 effect_of_attack ( order(), @@ -512,11 +409,7 @@ get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) -> -spec standard ( - non_neg_integer(), - btl_character:type(), - btl_player:type(), - btl_character:type() - btl_player:type() + ) -> { diff --git a/src/battle/mechanic/btl_turn_actions_management.erl b/src/battle/mechanic/btl_turn_actions_management.erl index 0981ab4..7ee173a 100644 --- a/src/battle/mechanic/btl_turn_actions_management.erl +++ b/src/battle/mechanic/btl_turn_actions_management.erl @@ -37,6 +37,25 @@ deactivate_character (Update) -> S1Update. +-spec perform_action + ( + 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()} + ). +perform_action (Action, Character, Update) -> + case btl_action:get_category(Action) of + move -> btl_action_move:handle(Action, Character, Update); + attack -> btl_action_attack:handle(Action, Character, Update); + switch_weapon -> + btl_action_switch_weapon:handle(Action, Character, Update) + end. + -spec handle_actions ( list(btl_action:type()), @@ -44,7 +63,7 @@ deactivate_character (Update) -> ) -> btl_character_turn_update:type(). handle_actions ([], Update) -> Update; -handle_actions ([BattleAction|FutureBattleActions], Update) -> +handle_actions ([BattleAction|FutureBattleActions], S0Update) -> case btl_action:get_actor_index(BattleAction) of -1 -> handle_actions(FutureBattleActions, S0Update); CharacterIX -> @@ -57,56 +76,17 @@ handle_actions ([BattleAction|FutureBattleActions], Update) -> case btl_character:is_alive(Character) of false -> handle_actions(FutureBattleActions, S1Update); true -> - ActionResult = - case btl_action:get_category(BattleAction) of - move -> - btl_event_move:handle - ( - BattleAction, - Character, - S1Update - ); - - attack -> - case - btl_event_attack:handle - ( - BattleAction, - Character, - S1Update - ); - - attack_of_opportunity -> - btl_event_attack_of_opportunity:handle - ( - BattleAction, - Character, - S1Update - ); - end. - ActionResult = - case {MainCharacterIsAlive, btl_action:get_category(BattleAction)} of - {false, _} -> {ok, S0Update}; - {true, move} -> btl_turn_actions_move:handle(BattleAction, S0Update); - {true, switch_weapon} -> - btl_turn_actions_switch_weapon:handle(S0Update); - {true, attack} -> - btl_turn_actions_attack:handle(BattleAction, S0Update); - {true, interrupted_move} -> - btl_turn_actions_move:handle(BattleAction, S0Update); - {true, defend} -> - % TODO: Attack of Opportunity - Update - end, - - case ActionResult of - {ok, NewUpdate} -> handle_actions(FutureBattleActions, NewUpdate); - {events, NewEvents, NewUpdate} -> - handle_actions - ( - (NewEvents ++ FutureBattleActions), - NewUpdate - ) + case perform_action(BattleAction, Character, S1Update) of + {ok, S2Update} -> + handle_actions(FutureBattleActions, S2Update); + {events, NewEvents, S2Update} -> + handle_actions + ( + (NewEvents ++ FutureBattleActions), + S2Update + ) + end + end end. -spec update_timeline diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl b/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl deleted file mode 100644 index 2b032a0..0000000 --- a/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl +++ /dev/null @@ -1,339 +0,0 @@ --module(btl_turn_actions_attack). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export -( - [ - handle/2 - ] -). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --spec handle_attack_sequence - ( - btl_character:type(), - non_neg_integer(), - btl_character:type(), - non_neg_integer(), - integer(), - integer(), - list(btl_attack:step()), - list(btl_attack:type()) - ) - -> - { - list(btl_attack:type()), - non_neg_integer(), - non_neg_integer(), - integer(), - integer() - }. -handle_attack_sequence -( - _Character, - CharacterCurrentHealth, - _TargetCharacter, - TargetCurrentHealth, - AttackerLuck, - DefenderLuck, - AttackSequence, - Result -) -when -( - (CharacterCurrentHealth == 0) - or (TargetCurrentHealth == 0) - or (AttackSequence == []) -) -> - { - lists:reverse(Result), - CharacterCurrentHealth, - TargetCurrentHealth, - AttackerLuck, - DefenderLuck - }; -handle_attack_sequence -( - Character, - AttackerHealth, - TargetCharacter, - DefenderHealth, - AttackerLuck, - DefenderLuck, - [NextAttack | AttackSequence], - Result -) -> - AttackEffect = - btl_attack:get_description_of - ( - NextAttack, - btl_character:get_base_character(Character), - btl_character:get_base_character(TargetCharacter), - AttackerLuck, - DefenderLuck - ), - - { - AttackResult, - NewAttackerHealth, - NewAttackerLuck, - NewDefenderHealth, - NewDefenderLuck - } = - btl_attack:apply_to_healths_and_lucks - ( - AttackEffect, - AttackerHealth, - AttackerLuck, - DefenderHealth, - DefenderLuck - ), - - NextResult = - case AttackResult of - {nothing, _, _} -> Result; - _ -> [AttackResult|Result] - end, - - handle_attack_sequence - ( - Character, - NewAttackerHealth, - TargetCharacter, - NewDefenderHealth, - NewAttackerLuck, - NewDefenderLuck, - AttackSequence, - NextResult - ). - --spec get_attack_sequence - ( - btl_character:type(), - btl_character:type() - ) - -> list(btl_attack:step()). -get_attack_sequence (Character, TargetCharacter) -> - Range = - shr_location:dist - ( - btl_character:get_location(Character), - btl_character:get_location(TargetCharacter) - ), - - AttackingWeapon = - shr_character:get_active_weapon - ( - btl_character:get_base_character(Character) - ), - - DefendingWeapon = - shr_character:get_active_weapon - ( - btl_character:get_base_character(TargetCharacter) - ), - - btl_attack:get_sequence(Range, AttackingWeapon, DefendingWeapon). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% 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), - - AttackingPlayerIX = btl_character:get_player_index(Character), - AttackingPlayer = btl_battle:get_player(AttackingPlayerIX, Battle), - AttackingPlayerLuck = btl_player:get_luck(AttackingPlayer), - - TargetIX = btl_action:get_target_ix(BattleAction), - Map = btl_battle:get_map(Battle), - TargetCharacterRef = btl_battle:get_character(TargetIX, Battle), - TargetCharacter = - btl_character:resolve - ( - shr_tile:get_omnimods - ( - shr_tile:from_id - ( - shr_tile_instance:get_tile_id - ( - shr_map:get_tile_instance - ( - btl_character:get_location(TargetCharacterRef), - Map - ) - ) - ) - ), - TargetCharacterRef - ), - - DefendingPlayerIX = btl_character:get_player_index(TargetCharacter), - DefendingPlayer = btl_battle:get_player(DefendingPlayerIX, Battle), - DefendingPlayerLuck = btl_player:get_luck(DefendingPlayer), - - - true = btl_character:get_is_alive(TargetCharacter), - - - AttackSequence = get_attack_sequence(Character, TargetCharacter), - - { - AttackEffects, - RemainingAttackerHealth, - RemainingDefenderHealth, - NewAttackerLuck, - NewDefenderLuck - } = - handle_attack_sequence - ( - Character, - btl_character:get_current_health(Character), - TargetCharacter, - btl_character:get_current_health(TargetCharacter), - AttackingPlayerLuck, - DefendingPlayerLuck, - 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/turn_action/btl_turn_actions_move.erl b/src/battle/mechanic/turn_action/btl_turn_actions_move.erl deleted file mode 100644 index 70b42c9..0000000 --- a/src/battle/mechanic/turn_action/btl_turn_actions_move.erl +++ /dev/null @@ -1,278 +0,0 @@ --module(btl_turn_actions_move). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export -( - [ - handle/2 - ] -). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% 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 - ( - btl_character_turn_update:type(), - list(shr_direction:type()) - ) - -> - { - non_neg_integer(), - shr_location:type(), - list(shr_direction:type()), - list(shr_map_marker:type()), - btl_character_turn_update:type() - }. -get_path_cost_and_destination (Update, Path) -> - {S0Update, Character} = btl_character_turn_update:get_character(Update), - {S1Update, Battle} = btl_character_turn_update:get_battle(S0Update), - CharacterIX = btl_character_turn_update:get_character_ix(S1Update), - Map = btl_battle:get_map(Battle), - - % FIXME: 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, S1Update}. - --spec get_movement_points - ( - btl_action:type(), - btl_character:type() - ) - -> non_neg_integer(). -get_movement_points (Action, Char) -> - case btl_action:get_category(Action) of - interrupted_move -> btl_action:get_movement_points(Action); - _ -> - shr_statistics:get_movement_points - ( - shr_character:get_statistics - ( - btl_character:get_base_character(Char) - ) - ) - end. - --spec commit_move - ( - btl_character:type(), - btl_character_turn_update:type(), - list(shr_direction:type()), - shr_location:type() - ) - -> btl_character_turn_update:type(). -commit_move (Character, Update, Path, NewLocation) -> - {S0Update, Battle} = btl_character_turn_update:get_battle(Update), - Map = btl_battle:get_map(Battle), - 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), - - S1Update = - btl_character_turn_update:ataxia_set_character - ( - UpdatedCharacter, - CharacterAtaxiaUpdate, - S0Update - ), - - TimelineItem = - btl_turn_result:new_character_moved - ( - btl_character_turn_update:get_character_ix(S1Update), - Path, - NewLocation - ), - - S2Update = btl_character_turn_update:add_to_timeline(TimelineItem, S1Update), - - S2Update. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec handle - ( - btl_action:type(), - btl_character_turn_update:type() - ) - -> - ( - {'ok', btl_character_turn_update:type()} - | {'events', list(btl_action:type()), btl_character_turn_update:type()} - ). -handle (BattleAction, Update) -> - {S0Update, Character} = btl_character_turn_update:get_character(Update), - - Path = btl_action:get_path(BattleAction), - - {PathCost, NewLocation, RemainingPath, Interruptions, S1Update} = - get_path_cost_and_destination(S0Update, Path), - - MovementPoints = get_movement_points(BattleAction, Character), - - true = (MovementPoints >= PathCost), - - S2Update = commit_move(Character, S1Update, Path, NewLocation), - - case RemainingPath of - [] -> {ok, S2Update}; - _ -> - {events, - ( - lists:foldl - ( - fun (Marker, CurrentActions) -> - ( - btl_action:from_map_marker(Character, Marker) - ++ - CurrentActions - ) - end, - [], - Interruptions - ) - ++ - [ - btl_action:new_interrupted_move - ( - RemainingPath, - (MovementPoints - PathCost) - ) - ] - ), - S2Update - } - end. diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_opportunity_attack.erl b/src/battle/mechanic/turn_action/btl_turn_actions_opportunity_attack.erl deleted file mode 100644 index c1dbbdd..0000000 --- a/src/battle/mechanic/turn_action/btl_turn_actions_opportunity_attack.erl +++ /dev/null @@ -1,178 +0,0 @@ --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}. diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl b/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl deleted file mode 100644 index 8e4aeab..0000000 --- a/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl +++ /dev/null @@ -1,55 +0,0 @@ --module(btl_turn_actions_switch_weapon). -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export -( - [ - handle/1 - ] -). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec handle - ( - btl_character_turn_update:type() - ) - -> {'ok', btl_character_turn_update:type()}. -handle (Update) -> - {S0Update, Character} = btl_character_turn_update:get_character(Update), - CharacterIX = btl_character_turn_update:get_character_ix(S0Update), - 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 - ), - - 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_character - ( - UpdatedCharacter, - CharacterAtaxiaUpdate, - S1Update - ), - - {ok, S2Update}. diff --git a/src/battle/struct/btl_action.erl b/src/battle/struct/btl_action.erl index 174a063..50606ac 100644 --- a/src/battle/struct/btl_action.erl +++ b/src/battle/struct/btl_action.erl @@ -8,8 +8,8 @@ move, { actor_ix :: non_neg_integer(), - path :: list(shr_direction:enum()) - movement_points :: non_neg_integer() + path :: list(shr_direction:enum()), + movement_points :: (non_neg_integer() | -1) } ). @@ -38,12 +38,12 @@ | 'attack' | 'nothing' ). + -opaque type() :: ( #move{} | #switch_weapon{} | #attack{} - | #attack_of_opportunity{} ). -export_type([category/0, type/0]). @@ -54,7 +54,7 @@ -export ( [ - from_map_marker/2, + from_map_marker/3, maybe_decode_move/2, maybe_decode_weapon_switch/2, maybe_decode_attack/2, @@ -65,13 +65,14 @@ -export ( [ - new_interrupted_move/2 + new_move/3 ] ). -export ( [ + get_is_opportunistic/1, get_path/1, get_movement_points/1, get_target_index/1, @@ -97,7 +98,14 @@ maybe_decode_move (_CharacterIX, []) -> []; maybe_decode_move (CharacterIX, PathInBinary) -> Path = lists:map(fun shr_direction:decode/1, PathInBinary), - [#move{ actor_ix = CharacterIX, path = Path }]. + [ + #move + { + actor_ix = CharacterIX, + path = Path, + movement_points = -1 + } + ]. -spec maybe_decode_attack ( @@ -107,7 +115,14 @@ maybe_decode_move (CharacterIX, PathInBinary) -> -> list(type()). maybe_decode_attack (_CharacterIX, TargetIX) when (TargetIX < 0) -> []; maybe_decode_attack (CharacterIX, TargetIX) -> - [#attack{ actor_ix = CharacterIX, target_ix = TargetIX }]. + [ + #attack + { + actor_ix = CharacterIX, + target_ix = TargetIX, + is_opportunistic = false + } + ]. -spec maybe_decode_weapon_switch ( @@ -128,31 +143,23 @@ can_follow (move, attack) -> true; can_follow (_, _) -> false. -spec get_path (type()) -> list(shr_direction:type()). -get_path (Action) when is_record(Action, move) -> - Action#move.path; -get_path (Action) when is_record(Action, interrupted_move) -> - Action#interrupted_move.path; +get_path (Action) when is_record(Action, move) -> Action#move.path; get_path (_) -> []. --spec get_movement_points (type()) -> non_neg_integer(). -get_movement_points (Action) when is_record(Action, interrupted_move) -> - Action#interrupted_move.movement_points; -get_movement_points (_) -> 0. +-spec get_movement_points (type()) -> (non_neg_integer() | -1). +get_movement_points (Action) when is_record(Action, move) -> + Action#move.movement_points; +get_movement_points (_) -> -1. --spec get_target_index (type()) -> non_neg_integer(). +-spec get_target_index (type()) -> (non_neg_integer() | -1). get_target_index (Action) when is_record(Action, attack) -> Action#attack.target_ix; -get_target_index (Action) when is_record(Action, attack_of_opportunity) -> - Action#attack_of_opportunity.target_ix; -get_target_index (_) -> - 0. +get_target_index (_) -> -1. -spec get_actor_index (type()) -> (non_neg_integer() | -1). get_actor_index (Action) when is_record(Action, attack) -> Action#attack.actor_ix; -get_actor_index (Action) when is_record(Action, attack_of_opportunity) -> - Action#attack_of_opportunity.actor_ix; get_actor_index (Action) when is_record(Action, move) -> Action#move.actor_ix; get_actor_index (Action) when is_record(Action, switch_weapon) -> @@ -160,37 +167,47 @@ get_actor_index (Action) when is_record(Action, switch_weapon) -> get_actor_index (_) -> -1. --spec new_interrupted_move +-spec get_is_opportunistic (type()) -> boolean(). +get_is_opportunistic (Action) when is_record(Action, attack) -> + Action#attack.is_opportunistic; +get_is_opportunistic (_) -> false. + +-spec new_move ( + non_neg_integer(), list(shr_direction:type()), - non_neg_integer() + (non_neg_integer() | -1) ) -> type(). -new_interrupted_move (Path, MovementPoints) -> - #interrupted_move{ path = Path, movement_points = MovementPoints }. +new_move (ActorIX, Path, MovementPoints) -> + #move + { + actor_ix = ActorIX, + path = Path, + movement_points = MovementPoints + }. -spec get_category (type()) -> category(). get_category (Action) when is_record(Action, attack) -> attack; get_category (Action) when is_record(Action, move) -> move; -get_category (Action) when is_record(Action, switch_weapon) -> switch_weapon; -get_category (Action) when is_record(Action, interrupted_move) -> - interrupted_move; -get_category (Action) when is_record(Action, attack_of_opportunity) -> - attack_of_opportunity. +get_category (Action) when is_record(Action, switch_weapon) -> switch_weapon. -spec from_map_marker ( + non_neg_integer(), btl_character:type(), shr_map_marker:type() ) -> list(type()). -from_map_marker (_Character, Marker) -> +from_map_marker (CharacterIX, _Character, Marker) -> case shr_map_marker:get_category(Marker) of matk -> [ - #attack_of_opportunity + #attack { - target_ix = shr_map_marker:get_character_index(Marker) + target_ix = CharacterIX, + actor_ix = shr_map_marker:get_character_index(Marker), + is_opportunistic = true } ]; -- cgit v1.2.3-70-g09d2