summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/battle/mechanic')
-rw-r--r--src/battle/mechanic/btl_next_turn.erl184
-rw-r--r--src/battle/mechanic/btl_turn_actions.erl83
-rw-r--r--src/battle/mechanic/btl_victory.erl208
-rw-r--r--src/battle/mechanic/turn_action/btl_turn_actions_attack.erl225
-rw-r--r--src/battle/mechanic/turn_action/btl_turn_actions_move.erl186
-rw-r--r--src/battle/mechanic/turn_action/btl_turn_actions_stats_change.erl85
-rw-r--r--src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl68
7 files changed, 1039 insertions, 0 deletions
diff --git a/src/battle/mechanic/btl_next_turn.erl b/src/battle/mechanic/btl_next_turn.erl
new file mode 100644
index 0000000..886916d
--- /dev/null
+++ b/src/battle/mechanic/btl_next_turn.erl
@@ -0,0 +1,184 @@
+-module(btl_next_turn).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ update_if_needed/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec set_player_turn_to_next (btl_battle:type())
+ -> {btl_battle:type(), ataxic:basic()}.
+set_player_turn_to_next (Battle) ->
+ Players = btl_battle:get_players(Battle),
+ CurrentPlayerTurn = btl_battle:get_current_player_turn(Battle),
+
+ NextPlayerTurn = btl_player_turn:next(Players, CurrentPlayerTurn),
+
+ UpdatedBattle = btl_battle:set_current_player_turn(NextPlayerTurn, Battle),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_current_player_turn_field(),
+ ataxic:constant(NextPlayerTurn)
+ ),
+
+ {UpdatedBattle, DBQuery}.
+
+-spec reset_next_player_timeline (btl_battle:type())
+ -> {btl_battle:type(), btl_player:type(), ataxic:basic()}.
+reset_next_player_timeline (Battle) ->
+ NextPlayerTurn = btl_battle:get_current_player_turn(Battle),
+ NextPlayerIX = btl_player_turn:get_player_ix(NextPlayerTurn),
+ NextPlayer = btl_battle:get_player(NextPlayerIX, Battle),
+
+ UpdatedNextPlayer = btl_player:reset_timeline(NextPlayer),
+ UpdatedBattle =
+ btl_battle:set_player(NextPlayerIX, UpdatedNextPlayer, Battle),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_players_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ NextPlayerIX,
+ ataxic:update_field
+ (
+ btl_player:get_timeline_field(),
+ ataxic:constant([])
+ )
+ )
+ ),
+
+ {UpdatedBattle, UpdatedNextPlayer, DBQuery}.
+
+
+-spec activate_next_players_characters (btl_battle:type(), btl_player:type())
+ -> {btl_battle:type(), ataxic:basic()}.
+activate_next_players_characters (Battle, NextPlayer) ->
+ NextPlayerIX = btl_player:get_index(NextPlayer),
+ Characters = btl_battle:get_characters(Battle),
+
+ {UpdatedCharacters, AtaxicUpdates} =
+ orddict:fold
+ (
+ fun (IX, Character, {Prev, Updates}) ->
+ case (btl_character:get_player_index(Character) == NextPlayerIX) of
+ true ->
+ {
+ orddict:store
+ (
+ IX,
+ btl_character:set_is_active(true, Character),
+ Prev
+ ),
+ [
+ ataxic_sugar:update_orddict_element
+ (
+ IX,
+ ataxic:update_field
+ (
+ btl_character:get_is_active_field(),
+ ataxic:constant(true)
+ )
+ )|Updates
+ ]
+ };
+
+ false -> {Prev, Updates}
+ end
+ end,
+ {Characters, []},
+ Characters
+ ),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic:sequence(AtaxicUpdates)
+ ),
+
+ UpdatedBattle = btl_battle:set_characters(UpdatedCharacters, Battle),
+
+ {UpdatedBattle, DBQuery}.
+
+-spec update
+ (
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+update (Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Battle = btl_character_turn_data:get_battle(Data),
+
+ {S0Battle, DBQuery0} = set_player_turn_to_next(Battle),
+ {S1Battle, NextPlayer, DBQuery1} = reset_next_player_timeline(S0Battle),
+ {S2Battle, DBQuery2} =
+ activate_next_players_characters(S1Battle, NextPlayer),
+
+ S0Data = btl_character_turn_data:set_battle(S2Battle, Data),
+ S0Update =
+ btl_character_turn_update:add_to_timeline
+ (
+ btl_turn_result:new_player_turn_started
+ (
+ btl_player:get_index(NextPlayer)
+ ),
+ DBQuery0,
+ Update
+ ),
+
+ S1Update = btl_character_turn_update:set_data(S0Data, S0Update),
+
+ S2Update =
+ lists:foldl
+ (
+ fun btl_character_turn_update:add_to_db/2,
+ S1Update,
+ [DBQuery1,DBQuery2]
+ ),
+
+ S2Update.
+
+-spec requires_update (btl_character_turn_update:type()) -> boolean().
+requires_update (Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Battle = btl_character_turn_data:get_battle(Data),
+ Characters = btl_battle:get_characters(Battle),
+
+ (not
+ (lists:any
+ (
+ fun ({_IX, Char}) ->
+ btl_character:get_is_active(Char)
+ end,
+ orddict:to_list(Characters)
+ )
+ )
+ ).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec update_if_needed
+ (
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+update_if_needed (Update) ->
+ case requires_update(Update) of
+ true -> update(Update);
+ _ -> Update
+ end.
diff --git a/src/battle/mechanic/btl_turn_actions.erl b/src/battle/mechanic/btl_turn_actions.erl
new file mode 100644
index 0000000..d4a81fc
--- /dev/null
+++ b/src/battle/mechanic/btl_turn_actions.erl
@@ -0,0 +1,83 @@
+-module(btl_turn_actions).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ apply_requested_actions/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Move elsewhere
+
+%%%% TODO: move this elsewhere
+-spec finalize_character
+ (
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+finalize_character (Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Character = btl_character_turn_data:get_character(Data),
+
+ DisabledCharacter = btl_character:set_is_active(false, Character),
+ UpdatedData = btl_character_turn_data:set_character(DisabledCharacter, Data),
+ FinalizedData = btl_character_turn_data:clean_battle(UpdatedData),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ btl_character_turn_data:get_character_ix(Data),
+ ataxic:update_field
+ (
+ btl_character:get_is_active_field(),
+ ataxic:constant(false)
+ )
+ )
+ ),
+
+ S0Update = btl_character_turn_update:set_data(FinalizedData, Update),
+ S1Update = btl_character_turn_update:add_to_db(DBQuery, S0Update),
+
+ S1Update.
+
+-spec handle
+(
+ btl_battle_action:type(),
+ btl_character_turn_update:type()
+)
+-> btl_character_turn_update:type().
+handle (BattleAction, Update) ->
+ case btl_battle_action:get_category(BattleAction) of
+ move -> btl_turn_actions_move:handle(BattleAction, Update);
+ switch_weapon -> btl_turn_actions_switch_weapon:handle(Update);
+ attack -> btl_turn_actions_attack:handle(BattleAction, Update)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec apply_requested_actions
+ (
+ btl_character_turn_data:type(),
+ btl_character_turn_request:type()
+ )
+ -> btl_character_turn_update:type().
+apply_requested_actions (Data, Request) ->
+ Actions = btl_character_turn_request:get_actions(Request),
+
+ EmptyUpdate = btl_character_turn_update:new(Data),
+ PostActionsUpdate = lists:foldl(fun handle/2, EmptyUpdate, Actions),
+
+ finalize_character(PostActionsUpdate).
diff --git a/src/battle/mechanic/btl_victory.erl b/src/battle/mechanic/btl_victory.erl
new file mode 100644
index 0000000..089af81
--- /dev/null
+++ b/src/battle/mechanic/btl_victory.erl
@@ -0,0 +1,208 @@
+-module(btl_victory).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle_character_lost_health/3
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec mark_players_characters_as_defeated
+ (
+ non_neg_integer(),
+ orddict:orddict(non_neg_integer(), btl_character:type())
+ )
+ ->
+ {
+ orddict:orddict(non_neg_integer(), btl_character:type()),
+ list(ataxic:basic())
+ }.
+mark_players_characters_as_defeated (PlayerIX, Characters) ->
+ orddict:fold
+ (
+ fun (IX, Character, {Dict, Updates}) ->
+ case (btl_character:get_player_index(Character) == PlayerIX) of
+ false -> {Dict, Updates};
+ true ->
+ {
+ orddict:store
+ (
+ IX,
+ btl_character:set_is_defeated(true, Character),
+ Dict
+ ),
+ [
+ ataxic_sugar:update_orddict_element
+ (
+ IX,
+ ataxic:update_field
+ (
+ btl_character:get_is_defeated_field(),
+ ataxic:constant(true)
+ )
+ )
+ ]
+ }
+ end
+ end,
+ {Characters, []},
+ Characters
+ ).
+
+-spec handle_player_defeat
+ (
+ non_neg_integer(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle_player_defeat (PlayerIX, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Battle = btl_character_turn_data:get_battle(Data),
+ Characters = btl_battle:get_characters(Battle),
+
+ %% FIXME [FUNCTION: battle][MEDIUM]: The controlled character might slip
+ %% through.
+ {UpdatedCharacters, AtaxicUpdates} =
+ mark_players_characters_as_defeated(PlayerIX, Characters),
+
+ S0Battle = btl_battle:set_characters(UpdatedCharacters, Battle),
+ S1Battle =
+ btl_battle:set_player
+ (
+ PlayerIX,
+ btl_player:set_is_active
+ (
+ false,
+ btl_battle:get_player(PlayerIX, S0Battle)
+ ),
+ S0Battle
+ ),
+
+ UpdatedData = btl_character_turn_data:set_battle(S1Battle, Data),
+ S0Update = btl_character_turn_update:set_data(UpdatedData, Update),
+
+ DBQuery =
+ ataxic:sequence
+ (
+ [
+ ataxic:update_field
+ (
+ btl_battle:get_players_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ PlayerIX,
+ ataxic:update_field
+ (
+ btl_player:get_is_active_field(),
+ ataxic:constant(false)
+ )
+ )
+ ),
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic:sequence(AtaxicUpdates)
+ )
+ ]
+ ),
+
+ S1Update =
+ btl_character_turn_update:add_to_timeline
+ (
+ btl_turn_result:new_player_lost(PlayerIX),
+ DBQuery,
+ S0Update
+ ),
+
+ S1Update.
+
+
+-spec actually_handle_character_lost_health
+ (
+ non_neg_integer(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+actually_handle_character_lost_health (CharIX, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Battle = btl_character_turn_data:get_battle(Data),
+ Character = btl_battle:get_character(CharIX, Battle),
+ Characters = btl_battle:get_characters(Battle),
+ CharacterPlayerIX = btl_character:get_player_index(Character),
+
+ case btl_character:get_rank(Character) of
+ optional ->
+ %% Let's not assume there is a commander
+ StillHasAliveChar =
+ lists:any
+ (
+ fun ({IX, Char}) ->
+ (
+ (CharacterPlayerIX == btl_character:get_player_index(Char))
+ and (IX /= CharIX)
+ and btl_character:get_is_alive(Char)
+ )
+ end,
+ orddict:to_list(Characters)
+ ),
+
+ case StillHasAliveChar of
+ true -> Update;
+ _ -> handle_player_defeat(CharacterPlayerIX, Update)
+ end;
+
+ commander -> handle_player_defeat(CharacterPlayerIX, Update);
+
+ target ->
+ StillHasAliveChar =
+ lists:any
+ (
+ fun ({IX, Char}) ->
+ (
+ (CharacterPlayerIX == btl_character:get_player_index(Char))
+ and (IX /= CharIX)
+ and btl_character:get_is_alive(Char)
+ )
+ end,
+ orddict:to_list(Characters)
+ ),
+
+ case StillHasAliveChar of
+ true -> Update;
+ _ -> handle_player_defeat(CharacterPlayerIX, Update)
+ end
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle_character_lost_health
+ (
+ non_neg_integer(),
+ integer(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle_character_lost_health (_, Health, Update) when (Health > 0) -> Update;
+handle_character_lost_health (CharIX, _Health, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ S1Data = btl_character_turn_data:clean_battle(Data),
+ S1Update = btl_character_turn_update:set_data(S1Data, Update),
+
+ S2Update = actually_handle_character_lost_health(CharIX, S1Update),
+
+ S2Data = btl_character_turn_update:get_data(S2Update),
+ S3Data = btl_character_turn_data:refreshr_character(S2Data),
+ S3Update = btl_character_turn_update:set_data(S3Data, S2Update),
+
+ S3Update.
diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl b/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl
new file mode 100644
index 0000000..52dd3fb
--- /dev/null
+++ b/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl
@@ -0,0 +1,225 @@
+-module(btl_turn_actions_attack).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle_attack_sequence
+ (
+ btl_character_current_data:type(),
+ non_neg_integer(),
+ btl_character_current_data:type(),
+ non_neg_integer(),
+ list(btl_attack:step())
+ )
+ -> {list(btl_attack:type()), non_neg_integer(), non_neg_integer()}.
+handle_attack_sequence
+(
+ CharacterCurrentData,
+ CharacterCurrentHealth,
+ TargetCurrentData,
+ TargetCurrentHealth,
+ AttackSequence
+) ->
+ AttackPlannedEffects =
+ lists:map
+ (
+ fun (AttackStep) ->
+ btl_attack:get_description_of
+ (
+ AttackStep,
+ CharacterCurrentData,
+ TargetCurrentData
+ )
+ end,
+ AttackSequence
+ ),
+
+ lists:foldl
+ (
+ fun
+ (
+ AttackEffectCandidate,
+ {AttackValidEffects, AttackerHealth, DefenderHealth}
+ ) ->
+ {AttackResult, NewAttackerHealth, NewDefenderHealth} =
+ btl_attack:apply_to_healths
+ (
+ AttackEffectCandidate,
+ AttackerHealth,
+ DefenderHealth
+ ),
+ case AttackResult of
+ nothing -> {AttackValidEffects, AttackerHealth, DefenderHealth};
+ _ ->
+ {
+ (AttackValidEffects ++ [AttackResult]),
+ NewAttackerHealth,
+ NewDefenderHealth
+ }
+ end
+ end,
+ {
+ [],
+ CharacterCurrentHealth,
+ TargetCurrentHealth
+ },
+ AttackPlannedEffects
+ ).
+
+-spec get_attack_sequence
+ (
+ btl_character:type(),
+ btl_character:type()
+ )
+ -> list(btl_attack:step()).
+get_attack_sequence (Character, TargetCharacter) ->
+ Range =
+ btl_location:dist
+ (
+ btl_character:get_location(Character),
+ btl_character:get_location(TargetCharacter)
+ ),
+
+ {AttackingWeaponID, _} = btl_character:get_weapon_ids(Character),
+ {DefendingWeaponID, _} = btl_character:get_weapon_ids(TargetCharacter),
+
+ AttackingWeapon = shr_weapon:from_id(AttackingWeaponID),
+ DefendingWeapon = shr_weapon:from_id(DefendingWeaponID),
+
+ btl_attack:get_sequence(Range, AttackingWeapon, DefendingWeapon).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle
+ (
+ btl_battle_action:type(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle (BattleAction, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Battle = btl_character_turn_data:get_battle(Data),
+ Character = btl_character_turn_data:get_character(Data),
+ CharacterIX = btl_character_turn_data:get_character_ix(Data),
+ CharacterCurrentData =
+ btl_character_turn_data:get_character_current_data(Data),
+
+ Map = btl_battle:get_map(Battle),
+ TargetIX = btl_battle_action:get_target_ix(BattleAction),
+ TargetCharacter = btl_battle:get_character(TargetIX, Battle),
+ TargetCurrentData = btl_character_current_data:new(TargetCharacter, Map),
+
+ true = btl_character:get_is_alive(TargetCharacter),
+
+ AttackSequence = get_attack_sequence(Character, TargetCharacter),
+
+ {AttackEffects, RemainingAttackerHealth, RemainingDefenderHealth} =
+ handle_attack_sequence
+ (
+ CharacterCurrentData,
+ btl_character:get_current_health(Character),
+ TargetCurrentData,
+ btl_character:get_current_health(TargetCharacter),
+ AttackSequence
+ ),
+
+ UpdatedCharacter =
+ btl_character:set_current_health(RemainingAttackerHealth, Character),
+
+ UpdatedBattle =
+ btl_battle:set_character
+ (
+ TargetIX,
+ btl_character:set_current_health
+ (
+ RemainingDefenderHealth,
+ TargetCharacter
+ ),
+ Battle
+ ),
+
+ S0Data = btl_character_turn_data:set_battle(UpdatedBattle, Data),
+ S1Data = btl_character_turn_data:set_character(UpdatedCharacter, S0Data),
+
+ TimelineItem =
+ btl_turn_result:new_character_attacked
+ (
+ CharacterIX,
+ TargetIX,
+ AttackEffects
+ ),
+
+ DBQuery0 =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ TargetIX,
+ ataxic:update_field
+ (
+ btl_character:get_current_health_field(),
+ ataxic:constant(RemainingDefenderHealth)
+ )
+ )
+ ),
+
+ DBQuery1 =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ CharacterIX,
+ ataxic:update_field
+ (
+ btl_character:get_current_health_field(),
+ ataxic:constant(RemainingAttackerHealth)
+ )
+ )
+ ),
+
+ S0Update =
+ btl_character_turn_update:add_to_timeline
+ (
+ TimelineItem,
+ DBQuery0,
+ Update
+ ),
+
+ S1Update = btl_character_turn_update:add_to_db(DBQuery1, S0Update),
+ S2Update = btl_character_turn_update:set_data(S1Data, S1Update),
+
+ S3Update =
+ btl_victory:handle_character_lost_health
+ (
+ CharacterIX,
+ RemainingAttackerHealth,
+ S2Update
+ ),
+
+ S4Update =
+ btl_victory:handle_character_lost_health
+ (
+ TargetIX,
+ RemainingDefenderHealth,
+ S3Update
+ ),
+
+ S4Update.
diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_move.erl b/src/battle/mechanic/turn_action/btl_turn_actions_move.erl
new file mode 100644
index 0000000..bf023d5
--- /dev/null
+++ b/src/battle/mechanic/turn_action/btl_turn_actions_move.erl
@@ -0,0 +1,186 @@
+-module(btl_turn_actions_move).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec cross
+ (
+ btl_map:type(),
+ list(btl_location:type()),
+ list(btl_direction:enum()),
+ non_neg_integer(),
+ btl_location:type()
+ )
+ -> {btl_location:type(), non_neg_integer()}.
+cross (_Map, _ForbiddenLocations, [], Cost, Location) ->
+ {Location, Cost};
+cross (Map, ForbiddenLocations, [Step|NextSteps], Cost, Location) ->
+ NextLocation = btl_location:apply_direction(Step, Location),
+ NextTileInstance = btl_map:get_tile_instance(NextLocation, Map),
+ NextTileClassID = shr_tile:extract_main_class_id(NextTileInstance),
+ NextTile = shr_tile:from_class_id(NextTileClassID),
+ NextCost = (Cost + shr_tile:get_cost(NextTile)),
+ IsForbidden =
+ lists:foldl
+ (
+ fun (ForbiddenLocation, Prev) ->
+ (Prev or (NextLocation == ForbiddenLocation))
+ end,
+ false,
+ ForbiddenLocations
+ ),
+
+ IsForbidden = false,
+
+ cross(Map, ForbiddenLocations, NextSteps, NextCost, NextLocation).
+
+-spec cross
+ (
+ btl_map:type(),
+ list(btl_location:type()),
+ list(btl_direction:enum()),
+ btl_location:type()
+ )
+ -> {btl_location:type(), non_neg_integer()}.
+cross (Map, ForbiddenLocations, Path, Location) ->
+ cross(Map, ForbiddenLocations, Path, 0, Location).
+
+-spec get_path_cost_and_destination
+ (
+ btl_character_turn_data:type(),
+ list(btl_direction:type())
+ )
+ -> {non_neg_integer(), btl_location:type()}.
+get_path_cost_and_destination (Data, Path) ->
+ Character = btl_character_turn_data:get_character(Data),
+ CharacterIX = btl_character_turn_data:get_character_ix(Data),
+ Battle = btl_character_turn_data:get_battle(Data),
+ Map = btl_battle:get_map(Battle),
+
+ 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, Cost} =
+ cross
+ (
+ Map,
+ ForbiddenLocations,
+ Path,
+ btl_character:get_location(Character)
+ ),
+
+ {Cost, NewLocation}.
+
+-spec assert_character_can_move
+ (
+ btl_character_turn_data:type(),
+ non_neg_integer()
+ )
+ -> 'ok'.
+assert_character_can_move (Data, Cost) ->
+ CharacterData = btl_character_turn_data:get_character_current_data(Data),
+ CharacterStats= btl_character_current_data:get_statistics(CharacterData),
+ CharacterMovementPoints = shr_statistics:get_movement_points(CharacterStats),
+
+ true = (Cost =< CharacterMovementPoints),
+
+ ok.
+
+-spec commit_move
+ (
+ btl_character_current_data:type(),
+ btl_character_turn_update:type(),
+ list(btl_direction:type()),
+ btl_location:type()
+ )
+ -> btl_character_turn_update:type().
+commit_move (PreviousCurrentData, Update, Path, NewLocation) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Character = btl_character_turn_data:get_character(Data),
+ CharacterIX = btl_character_turn_data:get_character_ix(Data),
+
+ UpdatedCharacter = btl_character:set_location(NewLocation, Character),
+ S0Data = btl_character_turn_data:set_character(UpdatedCharacter, Data),
+ S1Data = btl_character_turn_data:refresh_character_current_data(S0Data),
+
+ S0Update = btl_character_turn_update:set_data(S1Data, Update),
+ S1Update =
+ btl_turn_actions_stats_change:handle_max_health_changes
+ (
+ PreviousCurrentData,
+ S0Update
+ ),
+
+ TimelineItem =
+ btl_turn_result:new_character_moved(CharacterIX, Path, NewLocation),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ CharacterIX,
+ ataxic:update_field
+ (
+ btl_character:get_location_field(),
+ ataxic:constant(NewLocation)
+ )
+ )
+ ),
+
+ S2Update =
+ btl_character_turn_update:add_to_timeline
+ (
+ TimelineItem,
+ DBQuery,
+ S1Update
+ ),
+
+ S2Update.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle
+ (
+ btl_battle_action:type(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle (BattleAction, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ CharacterCurrentData =
+ btl_character_turn_data:get_character_current_data(Data),
+ Path = btl_battle_action:get_path(BattleAction),
+
+ {PathCost, NewLocation} = get_path_cost_and_destination(Data, Path),
+ assert_character_can_move(Data, PathCost),
+
+ commit_move(CharacterCurrentData, Update, Path, NewLocation).
diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_stats_change.erl b/src/battle/mechanic/turn_action/btl_turn_actions_stats_change.erl
new file mode 100644
index 0000000..5fe5444
--- /dev/null
+++ b/src/battle/mechanic/turn_action/btl_turn_actions_stats_change.erl
@@ -0,0 +1,85 @@
+-module(btl_turn_actions_stats_change).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle_max_health_changes/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec mod_current_health
+ (
+ non_neg_integer(),
+ non_neg_integer(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+mod_current_health (CurrentMaxHealth, PreviousMaxHealth, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Character = btl_character_turn_data:get_character(Data),
+ CharacterIX = btl_character_turn_data:get_character_ix(Data),
+ PreviousHealth = btl_character:get_current_health(Character),
+
+ PreviousHealthRatio = (PreviousHealth / PreviousMaxHealth),
+ NewHealth =
+ min
+ (
+ CurrentMaxHealth,
+ max(1, round(PreviousHealthRatio * CurrentMaxHealth))
+ ),
+
+ UpdatedCharacter = btl_character:set_current_health(NewHealth, Character),
+ UpdatedData = btl_character_turn_data:set_character(UpdatedCharacter, Data),
+ S0Update = btl_character_turn_update:set_data(UpdatedData, Update),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ CharacterIX,
+ ataxic:update_field
+ (
+ btl_character:get_current_health_field(),
+ ataxic:constant(NewHealth)
+ )
+ )
+ ),
+
+ S1Update = btl_character_turn_update:add_to_db(DBQuery, S0Update),
+
+ S1Update.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle_max_health_changes
+ (
+ btl_character_current_data:type(),
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle_max_health_changes (PreviousData, Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ CurrentData = btl_character_turn_data:get_character_current_data(Data),
+ CurrentStats = btl_character_current_data:get_statistics(CurrentData),
+ PreviousStats = btl_character_current_data:get_statistics(PreviousData),
+
+ CurrentMaxHealth = shr_statistics:get_health(CurrentStats),
+ PreviousMaxHealth = shr_statistics:get_health(PreviousStats),
+
+ case (CurrentMaxHealth == PreviousMaxHealth) of
+ true -> Update;
+ _ -> mod_current_health(CurrentMaxHealth, PreviousMaxHealth, Update)
+ end.
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
new file mode 100644
index 0000000..119dbe7
--- /dev/null
+++ b/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl
@@ -0,0 +1,68 @@
+-module(btl_turn_actions_switch_weapon).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec handle
+ (
+ btl_character_turn_update:type()
+ )
+ -> btl_character_turn_update:type().
+handle (Update) ->
+ Data = btl_character_turn_update:get_data(Update),
+ Character = btl_character_turn_data:get_character(Data),
+ CharacterCurrentData =
+ btl_character_turn_data:get_character_current_data(Data),
+ CharacterIX = btl_character_turn_data:get_character_ix(Data),
+
+ {PrimaryWeaponID, SecondaryWeaponID} = btl_character:get_weapon_ids(Character),
+
+ UpdatedWeaponIDs = {SecondaryWeaponID, PrimaryWeaponID},
+ UpdatedCharacter = btl_character:set_weapon_ids(UpdatedWeaponIDs, Character),
+
+ S0Data = btl_character_turn_data:set_character(UpdatedCharacter, Data),
+ S1Data = btl_character_turn_data:refresh_character_current_data(S0Data),
+
+ S0Update = btl_character_turn_update:set_data(S1Data, Update),
+ S1Update =
+ btl_turn_actions_stats_change:handle_max_health_changes
+ (
+ CharacterCurrentData,
+ S0Update
+ ),
+
+ TimelineItem = btl_turn_result:new_character_switched_weapons(CharacterIX),
+
+ DBQuery =
+ ataxic:update_field
+ (
+ btl_battle:get_characters_field(),
+ ataxic_sugar:update_orddict_element
+ (
+ CharacterIX,
+ ataxic:update_field
+ (
+ btl_character:get_weapons_field(),
+ ataxic:constant(UpdatedWeaponIDs)
+ )
+ )
+ ),
+
+ btl_character_turn_update:add_to_timeline(TimelineItem, DBQuery, S1Update).