From 0157365585c1201c53aa29cac84cd977de7434a1 Mon Sep 17 00:00:00 2001 From: Nathanael Sensfelder Date: Tue, 21 May 2019 16:39:12 +0200 Subject: Working on Attacks of Opportunity. --- .../mechanic/btl_turn_actions_management.erl | 55 +++++-- .../turn_action/btl_turn_actions_attack.erl | 4 +- .../mechanic/turn_action/btl_turn_actions_move.erl | 154 +++++++++++++++----- .../turn_action/btl_turn_actions_switch_weapon.erl | 4 +- src/battle/struct/btl_action.erl | 92 +++++++++++- src/shared/struct/map/shr_map_marker.erl | 162 ++++++++++++++++----- src/shared/struct/map/shr_tile_instance.erl | 6 +- 7 files changed, 383 insertions(+), 94 deletions(-) diff --git a/src/battle/mechanic/btl_turn_actions_management.erl b/src/battle/mechanic/btl_turn_actions_management.erl index d33fbad..eefe812 100644 --- a/src/battle/mechanic/btl_turn_actions_management.erl +++ b/src/battle/mechanic/btl_turn_actions_management.erl @@ -37,17 +37,48 @@ deactivate_character (Update) -> S1Update. --spec handle_action -( - btl_action:type(), - btl_character_turn_update:type() -) --> btl_character_turn_update:type(). -handle_action (BattleAction, Update) -> - case btl_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) +-spec main_character_is_alive + ( + btl_character_turn_update:type() + ) + -> {boolean(), btl_character_turn_update:type()}. +main_character_is_alive (Update) -> + {S0Update, MainCharacter} = btl_character_turn_update:get_character(Update), + {btl_character:get_is_alive(MainCharacter), S0Update}. + +-spec handle_actions + ( + list(btl_action:type()), + btl_character_turn_update:type() + ) + -> btl_character_turn_update:type(). +handle_actions ([], Update) -> Update; +handle_actions ([BattleAction|FutureBattleActions], Update) -> + {MainCharacterIsAlive, S0Update} = main_character_is_alive(Update), + + 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 + ) end. -spec update_timeline @@ -98,7 +129,7 @@ update_timeline (Update) -> handle (Update, Request) -> Actions = btl_character_turn_request:get_actions(Request), - S0Update = lists:foldl(fun handle_action/2, Update, Actions), + S0Update = handle_actions(Actions, Update), S1Update = deactivate_character(S0Update), S2Update = update_timeline(S1Update), S3Update = btl_turn_progression:handle(S2Update), diff --git a/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl b/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl index e36c7e3..af4c53b 100644 --- a/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl +++ b/src/battle/mechanic/turn_action/btl_turn_actions_attack.erl @@ -151,7 +151,7 @@ get_attack_sequence (Character, TargetCharacter) -> btl_action:type(), btl_character_turn_update: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), @@ -336,4 +336,4 @@ handle (BattleAction, Update) -> ) end, - S6Update. + {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 index a0cd138..70b42c9 100644 --- a/src/battle/mechanic/turn_action/btl_turn_actions_move.erl +++ b/src/battle/mechanic/turn_action/btl_turn_actions_move.erl @@ -18,16 +18,23 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -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(), non_neg_integer()}. -cross (_Map, _ForbiddenLocations, [], Cost, Location) -> - {Location, Cost}; -cross (Map, ForbiddenLocations, [Step|NextSteps], Cost, Location) -> + -> + { + 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), @@ -43,20 +50,60 @@ cross (Map, ForbiddenLocations, [Step|NextSteps], Cost, Location) -> ForbiddenLocations ), - IsForbidden = false, + 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 + ); - cross(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(), non_neg_integer()}. -cross (Map, ForbiddenLocations, Path, Location) -> - cross(Map, ForbiddenLocations, Path, 0, Location). + -> + { + 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 ( @@ -67,6 +114,8 @@ cross (Map, ForbiddenLocations, Path, Location) -> { 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) -> @@ -75,6 +124,9 @@ get_path_cost_and_destination (Update, Path) -> 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 ( @@ -91,42 +143,35 @@ get_path_cost_and_destination (Update, Path) -> btl_battle:get_characters(Battle) ), - {NewLocation, Cost} = + {NewLocation, RemainingPath, Cost, Interruptions} = cross ( + btl_character:get_player_index(Character), Map, ForbiddenLocations, Path, btl_character:get_location(Character) ), - {Cost, NewLocation, S1Update}. + {Cost, NewLocation, RemainingPath, Interruptions, S1Update}. --spec assert_character_can_move +-spec get_movement_points ( - btl_character:type(), - non_neg_integer() + btl_action:type(), + btl_character:type() ) - -> ('ok' | 'error'). -assert_character_can_move (Char, Cost) -> - CharacterMovementPoints = - shr_statistics:get_movement_points - ( - shr_character:get_statistics + -> 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 ( - btl_character:get_base_character(Char) + shr_character:get_statistics + ( + btl_character:get_base_character(Char) + ) ) - ), - - case (Cost =< CharacterMovementPoints) of - true -> ok; - false -> - io:format - ( - "~n[E] Character trying to move ~p dist with ~p points.~n", - [Cost, CharacterMovementPoints] - ), - error end. -spec commit_move @@ -183,14 +228,51 @@ commit_move (Character, Update, Path, NewLocation) -> btl_action:type(), btl_character_turn_update: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, S1Update} = + {PathCost, NewLocation, RemainingPath, Interruptions, S1Update} = get_path_cost_and_destination(S0Update, Path), - ok = assert_character_can_move(Character, PathCost), + MovementPoints = get_movement_points(BattleAction, Character), + + true = (MovementPoints >= PathCost), + + S2Update = commit_move(Character, S1Update, Path, NewLocation), - 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_switch_weapon.erl b/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl index 24b361a..8e4aeab 100644 --- a/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl +++ b/src/battle/mechanic/turn_action/btl_turn_actions_switch_weapon.erl @@ -24,7 +24,7 @@ ( btl_character_turn_update:type() ) - -> 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), @@ -52,4 +52,4 @@ handle (Update) -> S1Update ), - S2Update. + {ok, S2Update}. diff --git a/src/battle/struct/btl_action.erl b/src/battle/struct/btl_action.erl index 34dd46e..52f41d8 100644 --- a/src/battle/struct/btl_action.erl +++ b/src/battle/struct/btl_action.erl @@ -11,6 +11,15 @@ } ). +-record +( + interrupted_move, + { + path :: list(shr_direction:enum()), + movement_points :: non_neg_integer() + } +). + -record ( switch_weapon, @@ -26,8 +35,32 @@ } ). --type category() :: ('move' | 'switch_weapon' | 'attack' | 'nothing'). --opaque type() :: (#move{} | #switch_weapon{} | #attack{}). +-record +( + defend, + { + target_ix :: non_neg_integer() + } +). + + +-type category() :: + ( + 'move' + | 'interrupted_move' + | 'switch_weapon' + | 'attack' + | 'defend' + | 'nothing' + ). +-opaque type() :: + ( + #move{} + | #interrupted_move{} + | #switch_weapon{} + | #attack{} + | #defend{} + ). -export_type([category/0, type/0]). @@ -37,6 +70,7 @@ -export ( [ + from_map_marker/2, maybe_decode_move/1, maybe_decode_weapon_switch/1, maybe_decode_attack/1, @@ -44,10 +78,18 @@ ] ). +-export +( + [ + new_interrupted_move/2 + ] +). + -export ( [ get_path/1, + get_movement_points/1, get_target_ix/1, get_category/1 ] @@ -86,17 +128,57 @@ 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 (_) -> []. +-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_target_ix (type()) -> non_neg_integer(). get_target_ix (Action) when is_record(Action, attack) -> Action#attack.target_ix; +get_target_ix (Action) when is_record(Action, defend) -> + Action#defend.target_ix; get_target_ix (_) -> - []. + 0. + +-spec new_interrupted_move + ( + list(shr_direction:type()), + non_neg_integer() + ) + -> type(). +new_interrupted_move (Path, MovementPoints) -> + #interrupted_move{ 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, switch_weapon) -> switch_weapon; +get_category (Action) when is_record(Action, interrupted_move) -> + interrupted_move; +get_category (Action) when is_record(Action, defend) -> + defend. + +-spec from_map_marker + ( + btl_character:type(), + shr_map_marker:type() + ) + -> list(type()). +from_map_marker (_Character, Marker) -> + case shr_map_marker:get_category(Marker) of + matk -> + [ + #defend + { + target_ix = shr_map_marker:get_character_index(Marker) + } + ]; + + _ -> [] + end. diff --git a/src/shared/struct/map/shr_map_marker.erl b/src/shared/struct/map/shr_map_marker.erl index 2403ae7..84cad36 100644 --- a/src/shared/struct/map/shr_map_marker.erl +++ b/src/shared/struct/map/shr_map_marker.erl @@ -1,5 +1,15 @@ -module(shr_map_marker). +-define(OWNER_IX_FIELD, <<"oix">>). +-define(LOCATIONS_FIELD, <<"l">>). +-define(DATA_FIELD, <<"d">>). + +-define(DATA_TYPE_FIELD, <<"t">>). +-define(MATK_TYPE_VALUE, <<"matk">>). +-define(SPAWN_TYPE_VALUE, <<"spawn">>). + +-define(MATK_CHARACTER_IX_FIELD, <<"cix">>). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -15,17 +25,37 @@ ( spawn_mrk, { - player_ix :: non_neg_integer() + } +). + +-record +( + marker, + { + owner_ix :: (non_neg_integer() | -1), + data :: (data()), + locations :: list(shr_location:type()) } ). -type name() :: binary(). +-type category() :: (matk | spawn). -opaque melee_attack_zone() :: #matk_mrk{}. -opaque spawn_zone() :: #spawn_mrk{}. --opaque type() :: - {list(shr_location:type()), (melee_attack_zone() | spawn_zone())}. +-opaque data() :: (#matk_mrk{} | #spawn_mrk{}). +-opaque type() :: #marker{}. --export_type([name/0, type/0, melee_attack_zone/0, spawn_zone/0]). +-export_type +( + [ + name/0, + type/0, + category/0, + data/0, + melee_attack_zone/0, + spawn_zone/0 + ] +). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -35,7 +65,17 @@ [ player_can_see/2, get_locations/1, - get_name/1 + get_name/1, + get_owner_index/1, + get_category/1, + interrupts_movement/2 + ] +). + +-export +( + [ + get_character_index/1 ] ). @@ -50,53 +90,103 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec encode_data (data()) -> {list({binary(), any()})}. +encode_data (MarkerData) when is_record(MarkerData, matk_mrk) -> + { + [ + { ?DATA_TYPE_FIELD, ?MATK_TYPE_VALUE }, + { ?MATK_CHARACTER_IX_FIELD, MarkerData#matk_mrk.character_ix } + ] + }; +encode_data (MarkerData) when is_record(MarkerData, spawn_mrk) -> + { + [ + { ?DATA_TYPE_FIELD, ?SPAWN_TYPE_VALUE } + ] + }. + +-spec decode_data (map()) -> data(). +decode_data (Map) -> + case maps:get(?DATA_TYPE_FIELD, Map) of + ?MATK_TYPE_VALUE -> + #matk_mrk + { + character_ix = maps:get(?MATK_CHARACTER_IX_FIELD, Map) + }; + + ?SPAWN_TYPE_VALUE -> #spawn_mrk{}; + + _Other -> + % TODO: error. + #spawn_mrk{} + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec interrupts_movement (non_neg_integer(), type()) -> boolean(). +interrupts_movement (PlayerIX, Marker) -> + ( + (PlayerIX /= Marker#marker.owner_ix) + and is_record(Marker#marker.data, matk_mrk) + ). + -spec get_locations (type()) -> list(shr_location:type()). -get_locations ({L, _}) -> L. +get_locations (Marker) -> Marker#marker.locations. --spec encode (type()) -> {list(any())}. -encode ({L, MarkerData}) when is_record(MarkerData, matk_mrk) -> - { - [ - { <<"t">>, <<"matk">> }, - { <<"cix">>, MarkerData#matk_mrk.character_ix }, - { <<"l">>, lists:map(fun shr_location:encode/1, L) } - ] - }; -encode ({L, MarkerData}) when is_record(MarkerData, spawn_mrk) -> +-spec get_owner_index (type()) -> (non_neg_integer() | -1). +get_owner_index (Marker) -> Marker#marker.owner_ix. + +-spec get_category (type()) -> category(). +get_category (Marker) -> + case Marker#marker.data of + #matk_mrk{} -> matk; + #spawn_mrk{} -> spawn + end. + +-spec encode (type()) -> {list({binary(), any()})}. +encode (Marker) -> { [ - { <<"t">>, <<"spawn">> }, - { <<"pix">>, MarkerData#spawn_mrk.player_ix }, - { <<"l">>, lists:map(fun shr_location:encode/1, L) } + { + ?LOCATIONS_FIELD, + lists:map(fun shr_location:encode/1, Marker#marker.locations) + }, + { ?OWNER_IX_FIELD, Marker#marker.owner_ix }, + { ?DATA_FIELD, encode_data(Marker#marker.data) } ] }. + -spec decode (map()) -> type(). decode (Map) -> - Data = maps:get(<<"d">>, Map), + #marker { - lists:map(fun shr_location:decode/1, maps:get(<<"l">>, Map)), - ( - case maps:get(<<"t">>, Data) of - <<"mtak">> -> #matk_mrk{ character_ix = maps:get(<<"cix">>, Data) }; - <<"spawn">> -> #spawn_mrk{ player_ix = maps:get(<<"pix">>, Data) } - end - ) + locations = maps:get(?LOCATIONS_FIELD, Map), + owner_ix = maps:get(?OWNER_IX_FIELD, Map), + data = decode_data(maps:get(?DATA_FIELD, Map)) }. -spec player_can_see (integer(), type()) -> boolean(). -player_can_see (PlayerIX, _Marker) -> (PlayerIX >= 0). +player_can_see (_PlayerIX, _Marker) -> true. -spec get_name (type()) -> binary(). -get_name ({_Location, MarkerData}) when is_record(MarkerData, matk_mrk) -> - Prefix = <<"matk_c">>, - CharacterIXString = integer_to_binary(MarkerData#matk_mrk.character_ix), - <>; -get_name ({_Location, MarkerData}) when is_record(MarkerData, spawn_mrk) -> - Prefix = <<"spawn_p">>, - PlayerIXString = integer_to_binary(MarkerData#spawn_mrk.player_ix), - <>. +get_name (Marker) -> + case Marker#marker.data of + #matk_mrk{ character_ix = CIX } -> + Prefix = <<"matk_c">>, + CharacterIXString = integer_to_binary(CIX), + <>; + + #spawn_mrk{} -> + Prefix = <<"spawn_p">>, + PlayerIXString = integer_to_binary(Marker#marker.owner_ix), + <> + end. + +-spec get_character_index (type()) -> (non_neg_integer() | -1). +get_character_index (Marker) -> + case Marker#marker.data of + #matk_mrk{ character_ix = IX } -> IX; + _ -> -1 + end. diff --git a/src/shared/struct/map/shr_tile_instance.erl b/src/shared/struct/map/shr_tile_instance.erl index 9f95378..3c3621f 100644 --- a/src/shared/struct/map/shr_tile_instance.erl +++ b/src/shared/struct/map/shr_tile_instance.erl @@ -32,7 +32,8 @@ ( [ get_tile_id/1, - get_variant_id/1 + get_variant_id/1, + get_triggers/1 ] ). @@ -49,6 +50,9 @@ get_tile_id ({TileID, _, _, _}) -> TileID. -spec get_variant_id (type()) -> shr_tile:variant_id(). get_variant_id ({_, VariantID, _, _}) -> VariantID. +-spec get_triggers (type()) -> list(trigger_name()). +get_triggers ({_, _, _, Triggers}) -> Triggers. + -spec decode (map()) -> type(). decode (Map) -> L = maps:get(<<"b">>, Map), -- cgit v1.2.3-70-g09d2