| summaryrefslogtreecommitdiff | 
diff options
Diffstat (limited to 'src/battle')
30 files changed, 3768 insertions, 0 deletions
| diff --git a/src/battle/btl_handler.erl b/src/battle/btl_handler.erl new file mode 100644 index 0000000..19b0e20 --- /dev/null +++ b/src/battle/btl_handler.erl @@ -0,0 +1,33 @@ +-module(btl_handler). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([start/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec start (pid()) -> 'ok'. +start (TimedCachesManagerPid) -> +   case sh_database:fetch(battle_db, <<"0">>) of +      {ok, _} -> ok; +      not_found -> +         sh_database:insert +         ( +            battle_db, +            <<"0">>, +            any, +            btl_shim:generate_random_battle() +         ) +   end, +   sh_timed_caches_manager:new_cache(TimedCachesManagerPid, battle_db, none), +   ok. diff --git a/src/battle/btl_shim.erl b/src/battle/btl_shim.erl new file mode 100644 index 0000000..07d5854 --- /dev/null +++ b/src/battle/btl_shim.erl @@ -0,0 +1,183 @@ +-module(btl_shim). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate_random_battle/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec generate_random_characters +   ( +      non_neg_integer(), +      non_neg_integer(), +      non_neg_integer(), +      non_neg_integer(), +      btl_battlemap:type(), +      list(btl_location:type()), +      list(btl_character:type()) +   ) +   -> list(btl_character:type()). +generate_random_characters +( +   0, +   0, +   _CharactersPerPlayer, +   _TotalCharacterCount, +   _Battlemap, +   _ForbiddenLocations, +   Result +) -> +   Result; +generate_random_characters +( +   MaxPlayerIX, +   0, +   CharactersPerPlayer, +   TotalCharacterCount, +   Battlemap, +   ForbiddenLocations, +   Result +) -> +   generate_random_characters +   ( +      (MaxPlayerIX - 1), +      CharactersPerPlayer, +      CharactersPerPlayer, +      TotalCharacterCount, +      Battlemap, +      ForbiddenLocations, +      Result +   ); +generate_random_characters +( +   MaxPlayerIX, +   PlayerCharacterCount, +   CharactersPerPlayer, +   TotalCharacterCount, +   Battlemap, +   ForbiddenLocations, +   Result +) -> +   NewCharacter = +      btl_character:random +      ( +         TotalCharacterCount, +         MaxPlayerIX, +         btl_battlemap:get_width(Battlemap), +         btl_battlemap:get_height(Battlemap), +         ForbiddenLocations +      ), +   Character = +      case MaxPlayerIX of +         0 -> btl_character:set_is_active(true, NewCharacter); +         _ -> NewCharacter +      end, + +   generate_random_characters +   ( +      MaxPlayerIX, +      (PlayerCharacterCount - 1), +      CharactersPerPlayer, +      (TotalCharacterCount + 1), +      Battlemap, +      [btl_character:get_location(Character)|ForbiddenLocations], +      [Character|Result] +   ). +-spec demo_map () -> list(non_neg_integer()). +demo_map () -> +   [ +      2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, +      2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 2, +      2, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 2, +      2, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 2, +      2, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 0, 0, 0, 1, 2, +      2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 6, 6, 6, 6, 9, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 2, +      2, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 2, +      2, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 2, +      2, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 2, +      2, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, +      2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 0, 0, 0, 2, 1, 1, 0, 1, 2, +      2, 0, 0, 0, 0, 1, 0, 2, 0, 1, 0, 1, 0, 0, 0, 4, 3, 3, 3, 3, 5, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 2, +      2, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 4, 3, 3, 3, 3, 5, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 2, +      2, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0,11, 7, 7, 7, 7,10, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 2, +      2, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, +      2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 8, 6, 6, 6, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0, 0, 0, 1, 2, +      2, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 5, 0, 0, 0, 0, 0, 1, 0, 2, +      2, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 5, 0, 0, 1, 1, 0, 2, 0, 2, +      2, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 5, 0, 1, 1, 0, 0, 0, 0, 2, +      2, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 5, 0, 0, 1, 1, 0, 1, 0, 2, +      2, 0, 0, 0, 1, 2, 2, 0, 0, 0, 0, 8, 6, 6,16,15, 7, 7, 7, 7, 7, 7, 7,10, 0, 0, 1, 2, 0, 1, 0, 2, +      2, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 4, 3, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, +      2, 1, 0, 0, 1, 1, 0, 0, 0, 0, 8,16, 3, 3, 3, 5, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 2, +      2, 1, 0, 1, 1, 0, 0, 0, 1, 1, 4, 3, 3, 3, 3, 5, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 2, +      2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3,15, 7, 7,10, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 1, 0, 0, 0, 1, 2, +      2, 0, 0, 1, 0, 1, 0, 1, 0, 1, 4, 3, 5, 8, 6, 6, 9, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, +      2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 4, 3,17,16, 3, 3, 5, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 2, +      2, 0, 0, 1, 0, 0, 1, 1, 0, 1, 4, 3,15, 7, 7, 7,10, 0, 1, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 1, 1, 2, +      2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 4, 3, 5, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 2, +      2, 1, 1, 1, 0, 1, 0, 1, 0, 1, 4, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 2, +      6, 6, 6, 6, 6, 6, 6, 6, 6, 6,16, 3,17, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 +   ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate_random_battle () -> btl_battle:type(). +generate_random_battle () -> +   %BattlemapWidth = 32, % sh_roll:between(16, 32), +   %BattlemapHeight = 32, %sh_roll:between(16, 32), +   %Battlemap = btl_battlemap:random(0, BattlemapWidth, BattlemapHeight), +   Battlemap = btl_battlemap:from_list(0, 32, 32, demo_map()), +   Characters = generate_random_characters(1, 8, 8, 0, Battlemap, [], []), +   PlayersAsList = [btl_player:new(0, 8, <<"0">>), btl_player:new(1, 0, <<"1">>)], + +   {UsedWeaponIDs, UsedArmorIDs} = +      lists:foldl +      ( +         fun (Character, {UWIDs, UAIDs}) -> +            {MWpID, SWpID} = btl_character:get_weapon_ids(Character), +            AID = btl_character:get_armor_id(Character), +            { +               sets:add_element(MWpID, sets:add_element(SWpID, UWIDs)), +               sets:add_element(AID, UAIDs) +            } +         end, +         {sets:new(), sets:new()}, +         Characters +      ), + +   UsedTileIDs = +      array:sparse_foldl +      ( +         fun (_IX, TileClassID, CurrentTileIDs) -> +            sets:add_element +            ( +               btl_tile:class_id_to_type_id(TileClassID), +               CurrentTileIDs +            ) +         end, +         sets:new(), +         btl_battlemap:get_tile_class_ids(Battlemap) +      ), + +   Battle = +      btl_battle:new +      ( +         <<"0">>, +         PlayersAsList, +         Battlemap, +         Characters, +         sets:to_list(UsedWeaponIDs), +         sets:to_list(UsedArmorIDs), +         sets:to_list(UsedTileIDs) +      ), + +   Battle. diff --git a/src/battle/game-logic/btl_movement.erl b/src/battle/game-logic/btl_movement.erl new file mode 100644 index 0000000..87b1806 --- /dev/null +++ b/src/battle/game-logic/btl_movement.erl @@ -0,0 +1,60 @@ +-module(btl_movement). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([cross/4]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec cross +   ( +      btl_battlemap:type(), +      list(btl_location:type()), +      list(btl_direction:enum()), +      non_neg_integer(), +      btl_location:type() +   ) +   -> {btl_location:type(), non_neg_integer()}. +cross (_Battlemap, _ForbiddenLocations, [], Cost, Location) -> +   {Location, Cost}; +cross (Battlemap, ForbiddenLocations, [Step|NextSteps], Cost, Location) -> +   NextLocation = btl_location:apply_direction(Step, Location), +   NextTileClassID = btl_battlemap:get_tile_class_id(NextLocation, Battlemap), +   NextTileID = btl_tile:class_id_to_type_id(NextTileClassID), +   NextTile = btl_tile:from_id(NextTileID), +   NextCost = (Cost + btl_tile:get_cost(NextTile)), +   IsForbidden = +      lists:foldl +      ( +         fun (ForbiddenLocation, Prev) -> +            (Prev or (NextLocation == ForbiddenLocation)) +         end, +         false, +         ForbiddenLocations +      ), + +   IsForbidden = false, + +   cross(Battlemap, ForbiddenLocations, NextSteps, NextCost, NextLocation). + +-spec cross +   ( +      btl_battlemap:type(), +      list(btl_location:type()), +      list(btl_direction:enum()), +      btl_location:type() +   ) +   -> {btl_location:type(), non_neg_integer()}. +cross (Battlemap, ForbiddenLocations, Path, Location) -> +   cross(Battlemap, ForbiddenLocations, Path, 0, Location). diff --git a/src/battle/game-logic/btl_next_turn.erl b/src/battle/game-logic/btl_next_turn.erl new file mode 100644 index 0000000..82eec98 --- /dev/null +++ b/src/battle/game-logic/btl_next_turn.erl @@ -0,0 +1,160 @@ +-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(), sh_db_query:op()}. +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 = +      sh_db_query:set_field +      ( +         btl_battle:get_current_player_turn_field(), +         NextPlayerTurn +      ), + +   {UpdatedBattle, DBQuery}. + +-spec reset_next_player_timeline (btl_battle:type()) +   -> {btl_battle:type(), btl_player:type(), sh_db_query:op()}. +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 = +      sh_db_query:update_indexed +      ( +         btl_battle:get_players_field(), +         NextPlayerIX, +         [ sh_db_query:set_field(btl_player:get_timeline_field(), []) ] +      ), + +   {UpdatedBattle, UpdatedNextPlayer, DBQuery}. + + +-spec activate_next_players_characters (btl_battle:type(), btl_player:type()) +   -> {btl_battle:type(), list(sh_db_query:op())}. +activate_next_players_characters (Battle, NextPlayer) -> +   NextPlayerIX = btl_player:get_index(NextPlayer), +   Characters = btl_battle:get_characters(Battle), + +   {UpdatedCharacters, ModifiedIXs} = +      sh_array_util:mapiff +      ( +         fun (Character) -> +            (btl_character:get_player_index(Character) == NextPlayerIX) +         end, +         fun (Character) -> +            btl_character:set_is_active(true, Character) +         end, +         Characters +      ), + +   DBQueries = +      lists:map +      ( +         fun (IX) -> +            sh_db_query:update_indexed +            ( +               btl_battle:get_characters_field(), +               IX, +               [ +                  sh_db_query:set_field +                  ( +                     btl_character:get_is_active_field(), +                     true +                  ) +               ] +            ) +         end, +         ModifiedIXs +      ), + +   UpdatedBattle = btl_battle:set_characters(UpdatedCharacters, Battle), + +   {UpdatedBattle, DBQueries}. + +-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, DBQueries} = +      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|DBQueries] +      ), + +   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), + +   sh_array_util:none(fun btl_character:get_is_active/1, 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/game-logic/btl_turn_actions.erl b/src/battle/game-logic/btl_turn_actions.erl new file mode 100644 index 0000000..21205ac --- /dev/null +++ b/src/battle/game-logic/btl_turn_actions.erl @@ -0,0 +1,391 @@ +-module(btl_turn_actions). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle/2 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%% SWITCHING WEAPON %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle_switch_weapon +   ( +      btl_character_turn_update:type() +   ) +   -> btl_character_turn_update:type(). +handle_switch_weapon (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), +   CharacterAttributes = btl_character:get_attributes(Character), +   ArmorID = btl_character:get_armor_id(Character), +   {PrimaryWeaponID, SecondaryWeaponID} = btl_character:get_weapon_ids(Character), + +   UpdatedWeaponIDs = {SecondaryWeaponID, PrimaryWeaponID}, +   UpdatedCharacterStatistics = +      sh_statistics:new(CharacterAttributes, UpdatedWeaponIDs, ArmorID), +   UpdatedCharacter = +      btl_character:set_statistics +      ( +         UpdatedCharacterStatistics, +         btl_character:set_weapon_ids(UpdatedWeaponIDs, Character) +      ), + +   TimelineItem = btl_turn_result:new_character_switched_weapons(CharacterIX), + +   DBQuery = +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         CharacterIX, +         [ +            sh_db_query:set_field +            ( +               btl_character:get_weapons_field(), +               UpdatedWeaponIDs +            ), +            sh_db_query:set_field +            ( +               btl_character:get_statistics_field(), +               UpdatedCharacterStatistics +            ) +         ] +      ), + +   UpdatedData = btl_character_turn_data:set_character(UpdatedCharacter, Data), + +   S0Update = btl_character_turn_update:set_data(UpdatedData, Update), + +   btl_character_turn_update:add_to_timeline(TimelineItem, DBQuery, S0Update). + +%%%% MOVING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-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), +   Battlemap = btl_battle:get_battlemap(Battle), + +   ForbiddenLocations = +      array:foldl +      ( +         fun (IX, Char, Prev) -> +            IsAlive = btl_character:get_is_alive(Char), +            if +               (IX == CharacterIX) -> Prev; +               (not IsAlive) -> Prev; +               true -> [btl_character:get_location(Char)|Prev] +            end +         end, +         [], +         btl_battle:get_characters(Battle) +      ), + +   {NewLocation, Cost} = +      btl_movement:cross +      ( +         Battlemap, +         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) -> +   Character = btl_character_turn_data:get_character(Data), +   CharacterStatistics = btl_character:get_statistics(Character), +   CharacterMovementPoints = +      sh_statistics:get_movement_points(CharacterStatistics), + +   true = (Cost =< CharacterMovementPoints), + +   ok. + +-spec commit_move +   ( +      btl_character_turn_update:type(), +      list(btl_direction:type()), +      btl_location:type() +   ) +   -> btl_character_turn_update:type(). +commit_move (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), + +   UpdatedData = btl_character_turn_data:set_character(UpdatedCharacter, Data), + +   TimelineItem = +      btl_turn_result:new_character_moved(CharacterIX, Path, NewLocation), + +   DBQuery = +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         CharacterIX, +         [ +            sh_db_query:set_field +            ( +               btl_character:get_location_field(), +               NewLocation +            ) +         ] +      ), + +   S0Update = +      btl_character_turn_update:add_to_timeline +      ( +         TimelineItem, +         DBQuery, +         Update +      ), + +   btl_character_turn_update:set_data(UpdatedData, S0Update). + +-spec handle_move +   ( +      btl_battle_action:type(), +      btl_character_turn_update:type() +   ) +   -> btl_character_turn_update:type(). +handle_move (BattleAction, Update) -> +   Data = btl_character_turn_update:get_data(Update), +   Path = btl_battle_action:get_path(BattleAction), + +   {PathCost, NewLocation} = get_path_cost_and_destination(Data, Path), +   assert_character_can_move(Data, PathCost), + +   commit_move(Update, Path, NewLocation). + +%%%% ATTACKING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle_attack_sequence +   ( +      btl_character:type(), +      btl_character:type(), +      list(btl_attack:step()) +   ) +   -> {list(btl_attack:type()), non_neg_integer(), non_neg_integer()}. +handle_attack_sequence +( +   Character, +   TargetCharacter, +   AttackSequence +) -> +   AttackPlannedEffects = +      lists:map +      ( +         fun (AttackStep) -> +            btl_attack:get_description_of +            ( +               AttackStep, +               Character, +               TargetCharacter +            ) +         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, +      { +         [], +         btl_character:get_current_health(Character), +         btl_character:get_current_health(TargetCharacter) +      }, +      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 = sh_weapon:from_id(AttackingWeaponID), +   DefendingWeapon = sh_weapon:from_id(DefendingWeaponID), + +   btl_attack:get_sequence(Range, AttackingWeapon, DefendingWeapon). + + +-spec handle_attack +   ( +      btl_battle_action:type(), +      btl_character_turn_update:type() +   ) +   -> btl_character_turn_update:type(). +handle_attack (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), +   TargetIX = btl_battle_action:get_target_ix(BattleAction), +   TargetCharacter = btl_battle:get_character(TargetIX, Battle), + +   true = btl_character:get_is_alive(TargetCharacter), + +   AttackSequence = get_attack_sequence(Character, TargetCharacter), + +   {AttackEffects, RemainingAttackerHealth, RemainingDefenderHealth} = +      handle_attack_sequence(Character, 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 = +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         TargetIX, +         [ +            sh_db_query:set_field +            ( +               btl_character:get_current_health_field(), +               RemainingDefenderHealth +            ) +         ] +      ), + +   DBQuery1 = +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         CharacterIX, +         [ +            sh_db_query:set_field +            ( +               btl_character:get_current_health_field(), +               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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-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 -> handle_move(BattleAction, Update); +      switch_weapon -> handle_switch_weapon(Update); +      attack -> handle_attack(BattleAction, Update) +   end. diff --git a/src/battle/game-logic/btl_victory.erl b/src/battle/game-logic/btl_victory.erl new file mode 100644 index 0000000..eef42ad --- /dev/null +++ b/src/battle/game-logic/btl_victory.erl @@ -0,0 +1,194 @@ +-module(btl_victory). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      handle_character_lost_health/3 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec mark_players_characters_as_defeated +   ( +      non_neg_integer(), +      array:array(btl_character:type()) +   ) -> {array:array(btl_character:type()), list(non_neg_integer())}. +mark_players_characters_as_defeated (PlayerIX, Characters) -> +   sh_array_util:mapiff +   ( +      fun (Character) -> +         (btl_character:get_player_index(Character) == PlayerIX) +      end, +      fun (Character) -> +         btl_character:set_is_defeated(true, Character) +      end, +      Characters +   ). + +-spec add_db_query_to_mark_character_as_defeated +   ( +      non_neg_integer(), +      btl_character_turn_update:type() +   ) +   -> btl_character_turn_update:type(). +add_db_query_to_mark_character_as_defeated (IX, Update) -> +   btl_character_turn_update:add_to_db +   ( +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         IX, +         [ +            sh_db_query:set_field +            ( +               btl_character:get_is_defeated_field(), +               true +            ) +         ] +      ), +      Update +   ). + +-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: The controlled character might slip through. +   {UpdatedCharacters, ModifiedIXs} = +      mark_players_characters_as_defeated(PlayerIX, Characters), + +   S1Update = +      lists:foldl +      ( +         fun add_db_query_to_mark_character_as_defeated/2, +         Update, +         ModifiedIXs +      ), + +   %% TODO: Battle.player[PlayerIX].is_active <- false + +   UpdatedBattle = btl_battle:set_characters(UpdatedCharacters, Battle), +   UpdatedData = btl_character_turn_data:set_battle(UpdatedBattle, Data), +   S2Update = btl_character_turn_update:set_data(UpdatedData, S1Update), + +   DBQuery = +      sh_db_query:update_indexed +      ( +         btl_battle:get_players_field(), +         PlayerIX, +         [ +            sh_db_query:set_field +            ( +               btl_player:get_is_active_field(), +               false +            ) +         ] +      ), + +   S3Update = +      btl_character_turn_update:add_to_timeline +      ( +         btl_turn_result:new_player_lost(PlayerIX), +         DBQuery, +         S2Update +      ), + +   S3Update. + + +-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 = +            sh_array_util:any_indexed +            ( +               fun (IX, Char) -> +                  ( +                     (CharacterPlayerIX == btl_character:get_player_index(Char)) +                     and (IX /= CharIX) +                     and btl_character:get_is_alive(Char) +                  ) +               end, +               Characters +            ), + +         case StillHasAliveChar of +            true -> Update; +            _ -> handle_player_defeat(CharacterPlayerIX, Update) +         end; + +      commander -> handle_player_defeat(CharacterPlayerIX, Update); + +      target -> +         StillHasAliveChar = +            sh_array_util:any_indexed +            ( +               fun (IX, Char) -> +                  ( +                     (CharacterPlayerIX == btl_character:get_player_index(Char)) +                     and (IX /= CharIX) +                     and btl_character:get_is_alive(Char) +                     and (btl_character:get_rank(Char) == target) +                  ) +               end, +               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:refresh_character(S2Data), +   S3Update = btl_character_turn_update:set_data(S3Data, S2Update), + +   S3Update. diff --git a/src/battle/io/btl_security.erl b/src/battle/io/btl_security.erl new file mode 100644 index 0000000..cf6bb9b --- /dev/null +++ b/src/battle/io/btl_security.erl @@ -0,0 +1,33 @@ +-module(btl_security). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      assert_identity/2, +      lock_queries/1, +      unlock_queries/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec assert_identity (any(), any()) -> 'unimplemented'. +assert_identity (_PlayerID, _SessionToken) -> unimplemented. + +-spec lock_queries (any()) -> 'unimplemented'. +lock_queries (_PlayerID) -> unimplemented. + +-spec unlock_queries (any()) -> 'unimplemented'. +unlock_queries (_PlayerID) -> unimplemented. diff --git a/src/battle/query/btl_character_turn.erl b/src/battle/query/btl_character_turn.erl new file mode 100644 index 0000000..c232ab0 --- /dev/null +++ b/src/battle/query/btl_character_turn.erl @@ -0,0 +1,282 @@ +-module(btl_character_turn). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-include("../../../include/yaws_api.hrl"). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([out/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%-spec send_to_database (list(database_diff:type()), character_turn_request:type()) -> 'ok'. + + +%%%% REQUEST DECODING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode_request (binary()) -> btl_character_turn_request:type(). +decode_request (BinaryRequest) -> +   JSONMap = jiffy:decode(BinaryRequest, [return_maps]), + +   btl_character_turn_request:decode(JSONMap). + +%%%% USER AUTHENTICATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec authenticate_user (btl_character_turn_request:type()) -> 'ok'. +authenticate_user (Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   SessionToken = btl_character_turn_request:get_session_token(Request), + +   btl_security:assert_identity(PlayerID, SessionToken), +   btl_security:lock_queries(PlayerID), + +   ok. + +%%%% MAIN LOGIC %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec fetch_data +   ( +      btl_character_turn_request:type() +   ) +   -> btl_character_turn_data:type(). +fetch_data (Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   BattleID = btl_character_turn_request:get_battle_id(Request), +   CharacterIX = btl_character_turn_request:get_character_ix(Request), +   Battle = sh_timed_cache:fetch(battle_db, PlayerID, BattleID), + +   btl_character_turn_data:new(Battle, CharacterIX). + +%%%% ASSERTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec assert_user_is_current_player +   ( +      btl_character_turn_data:type(), +      btl_character_turn_request:type() +   ) -> 'ok'. +assert_user_is_current_player (Data, Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   Battle = btl_character_turn_data:get_battle(Data), +   CurrentPlayerTurn = btl_battle:get_current_player_turn(Battle), +   CurrentPlayerIX = btl_player_turn:get_player_ix(CurrentPlayerTurn), +   CurrentPlayer = btl_battle:get_player(CurrentPlayerIX, Battle), + +   true = (PlayerID == btl_player:get_id(CurrentPlayer)), + +   ok. + +-spec assert_user_owns_played_character +   ( +      btl_character_turn_data:type(), +      btl_character_turn_request:type() +   ) -> 'ok'. +assert_user_owns_played_character (Data, Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   Battle = btl_character_turn_data:get_battle(Data), +   Players = btl_battle:get_players(Battle), +   Character = btl_character_turn_data:get_character(Data), +   CharacterPlayerIX = btl_character:get_player_index(Character), +   CharacterPlayer = array:get(CharacterPlayerIX, Players), +   CharacterPlayerID = btl_player:get_id(CharacterPlayer), + +   true = (PlayerID == CharacterPlayerID), + +   ok. + +-spec assert_character_can_be_played (btl_character_turn_data:type()) -> 'ok'. +assert_character_can_be_played (Data) -> +   Character = btl_character_turn_data:get_character(Data), + +   true = btl_character:get_is_active(Character), + +   ok. + +-spec assert_user_permissions +   ( +      btl_character_turn_data:type(), +      btl_character_turn_request:type() +   ) -> 'ok'. +assert_user_permissions (Data, Request) -> +   assert_user_is_current_player(Data, Request), +   assert_user_owns_played_character(Data, Request), +   assert_character_can_be_played(Data), + +   ok. + +%%%% QUERY LOGIC HANDLING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-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 = +      sh_db_query:update_indexed +      ( +         btl_battle:get_characters_field(), +         btl_character_turn_data:get_character_ix(Data), +         [ sh_db_query:set_field(btl_character:get_is_active_field(), false) ] +      ), + +   S0Update = btl_character_turn_update:set_data(FinalizedData, Update), +   S1Update = btl_character_turn_update:add_to_db(DBQuery, S0Update), + +   S1Update. + +-spec handle_actions +   ( +      btl_character_turn_data:type(), +      btl_character_turn_request:type() +   ) +   -> btl_character_turn_update:type(). +handle_actions (Data, Request) -> +   Actions = btl_character_turn_request:get_actions(Request), + +   EmptyUpdate = btl_character_turn_update:new(Data), +   PostActionsUpdate = +      lists:foldl(fun btl_turn_actions:handle/2, EmptyUpdate, Actions), + +   finalize_character(PostActionsUpdate). + +-spec update_timeline +   ( +      btl_character_turn_update:type() +   ) +   -> btl_character_turn_update:type(). +update_timeline (Update) -> +   NewTimelineElements = btl_character_turn_update:get_timeline(Update), +   Data = btl_character_turn_update:get_data(Update), +   Battle = btl_character_turn_data:get_battle(Data), +   PlayerTurn = btl_battle:get_current_player_turn(Battle), +   PlayerIX = btl_player_turn:get_player_ix(PlayerTurn), +   Player = btl_battle:get_player(PlayerIX, Battle), + +   UpdatedPlayer = btl_player:add_to_timeline(NewTimelineElements, Player), +   UpdatedBattle = btl_battle:set_player(PlayerIX, UpdatedPlayer, Battle), +   UpdatedData = btl_character_turn_data:set_battle(UpdatedBattle, Data), + +   DBQuery = +      sh_db_query:update_indexed +      ( +         btl_battle:get_players_field(), +         PlayerIX, +         [ +            sh_db_query:add_to_field +            ( +               btl_player:get_timeline_field(), +               NewTimelineElements, +               true % We add those to the start of the list +            ) +         ] +      ), + +   S0Update = btl_character_turn_update:set_data(UpdatedData, Update), +   S1Update = btl_character_turn_update:add_to_db(DBQuery, S0Update), + +   S1Update. + + +-spec update_data +   ( +      btl_character_turn_data:type(), +      btl_character_turn_request:type() +   ) +   -> btl_character_turn_update:type(). +update_data (Data, Request) -> +   PostActionsUpdate = handle_actions(Data, Request), +   PostCharacterTurnUpdate = update_timeline(PostActionsUpdate), + +   btl_next_turn:update_if_needed(PostCharacterTurnUpdate). + +%%%% DATABASE UPDATES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec send_to_database +   ( +      btl_character_turn_update:type(), +      btl_character_turn_request:type() +   ) +   -> 'ok'. +send_to_database (Update, Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   BattleID = btl_character_turn_request:get_battle_id(Request), +   Ops = btl_character_turn_update:get_db(Update), +   Query = sh_db_query:new(battle_db, BattleID, {user, PlayerID}, Ops), + +   sh_database:commit(Query), + +   ok. + +-spec send_to_cache +   ( +      btl_character_turn_update:type(), +      btl_character_turn_request:type() +   ) +   -> 'ok'. +send_to_cache (Update, Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), +   BattleID = btl_character_turn_request:get_battle_id(Request), +   Data = btl_character_turn_update:get_data(Update), +   Battle = btl_character_turn_data:get_battle(Data), + +   sh_timed_cache:update(battle_db, PlayerID, BattleID, Battle), + +   ok. + +-spec commit_update +   ( +      btl_character_turn_update:type(), +      btl_character_turn_request:type() +   ) +   -> 'ok'. +commit_update (Update, Request) -> +   send_to_database(Update, Request), +   send_to_cache(Update, Request), + +   ok. + +%%%% USER DISCONNECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec disconnect_user (btl_character_turn_request:type()) -> 'ok'. +disconnect_user (Request) -> +   PlayerID = btl_character_turn_request:get_player_id(Request), + +   btl_security:unlock_queries(PlayerID), + +   ok. + +%%%% REPLY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate_reply (btl_character_turn_update:type()) -> binary(). +generate_reply (Update) -> +   NewTimelineItems = btl_character_turn_update:get_timeline(Update), + +   TurnResultReply = btl_turn_results:generate(NewTimelineItems), + +   jiffy:encode([TurnResultReply]). + +%%%% MAIN LOGIC %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec handle (binary()) -> binary(). +handle (EncodedRequest) -> +   Request = decode_request(EncodedRequest), +   authenticate_user(Request), +   Data = fetch_data(Request), +   assert_user_permissions(Data, Request), +   Update = update_data(Data, Request), +   commit_update(Update, Request), +   disconnect_user(Request), +   generate_reply(Update). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +out(A) -> +   { +      content, +      "application/json; charset=UTF-8", +      handle(A#arg.clidata) +   }. diff --git a/src/battle/query/btl_load_state.erl b/src/battle/query/btl_load_state.erl new file mode 100644 index 0000000..f79e24e --- /dev/null +++ b/src/battle/query/btl_load_state.erl @@ -0,0 +1,159 @@ +-module(btl_load_state). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-include("../../../include/yaws_api.hrl"). + +-record +( +   input, +   { +      player_id :: btl_player:id(), +      session_token :: binary(), +      battle_id :: binary() +   } +). + +-record +( +   query_state, +   { +      battle :: btl_battle:type() +   } +). + +-type input() :: #input{}. +-type query_state() :: #query_state{}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([out/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec parse_input (binary()) -> input(). +parse_input (Req) -> +   JSONReqMap = jiffy:decode(Req, [return_maps]), +   PlayerID = maps:get(<<"pid">>, JSONReqMap), +   SessionToken =  maps:get(<<"stk">>, JSONReqMap), +   BattleID = maps:get(<<"bid">>, JSONReqMap), + +   #input +   { +      player_id = PlayerID, +      session_token = SessionToken, +      battle_id = BattleID +   }. + +-spec fetch_data (input()) -> query_state(). +fetch_data (Input) -> +   PlayerID = Input#input.player_id, +   BattleID = Input#input.battle_id, + +   Battle = sh_timed_cache:fetch(battle_db, PlayerID, BattleID), + +   #query_state +   { +      battle = Battle +   }. + +-spec generate_reply(query_state(), input()) -> binary(). +generate_reply (QueryState, Input) -> +   PlayerID = Input#input.player_id, +   Battle = QueryState#query_state.battle, +   Players = btl_battle:get_players(Battle), + +   PlayerIX = +      sh_array_util:first +      ( +         fun (Player) -> +            (btl_player:get_id(Player) == PlayerID) +         end, +         Players +      ), + +   true = (PlayerIX >= 0), + +   SetTimeline = +      btl_set_timeline:generate +      ( +         btl_battle:get_encoded_last_turns_effects(Battle) +      ), + +   SetMap = btl_set_map:generate(btl_battle:get_battlemap(Battle)), + +   AddCharList = +      array:sparse_to_list +      ( +         array:map +         ( +            fun (IX, Character) -> +               btl_add_char:generate(IX, Character, PlayerIX) +            end, +            btl_battle:get_characters(Battle) +         ) +      ), + +   AddWeaponList = +      lists:map +      ( +         fun (WeaponID) -> +            btl_add_weapon:generate(sh_weapon:from_id(WeaponID)) +         end, +         btl_battle:get_used_weapon_ids(Battle) +      ), + +   AddArmorList = +      lists:map +      ( +         fun (ArmorID) -> +            btl_add_armor:generate(sh_armor:from_id(ArmorID)) +         end, +         btl_battle:get_used_armor_ids(Battle) +      ), + +   AddTileList = +      lists:map +      ( +         fun (TileID) -> +            btl_add_tile:generate(btl_tile:from_id(TileID)) +         end, +         btl_battle:get_used_tile_ids(Battle) +      ), + +   OutputList = +      ( +         AddTileList +         ++ [SetTimeline, SetMap | AddWeaponList] +         ++ AddArmorList +         ++ AddCharList +      ), +   Output = jiffy:encode(OutputList), + +   Output. + +-spec handle (binary()) -> binary(). +handle (Req) -> +   Input = parse_input(Req), +   btl_security:assert_identity +   ( +      Input#input.player_id, +      Input#input.session_token +   ), +   btl_security:lock_queries(Input#input.player_id), +   QueryState = fetch_data(Input), +   btl_security:unlock_queries(Input#input.player_id), +   generate_reply(QueryState, Input). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +out(A) -> +   { +      content, +      "application/json; charset=UTF-8", +      handle(A#arg.clidata) +   }. diff --git a/src/battle/reply/btl_add_armor.erl b/src/battle/reply/btl_add_armor.erl new file mode 100644 index 0000000..2c6a875 --- /dev/null +++ b/src/battle/reply/btl_add_armor.erl @@ -0,0 +1,34 @@ +-module(btl_add_armor). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec encode_category (sh_armor:category()) -> binary(). +encode_category (kinetic) -> <<"k">>; +encode_category (leather) -> <<"l">>; +encode_category (chain) -> <<"c">>; +encode_category (plate) -> <<"p">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (sh_armor:type()) -> {list(any())}. +generate (Armor) -> +   { +      [ +         {<<"msg">>, <<"add_armor">>}, +         {<<"id">>, sh_armor:get_id(Armor)}, +         {<<"nam">>, sh_armor:get_name(Armor)}, +         {<<"ct">>, encode_category(sh_armor:get_category(Armor))}, +         {<<"cf">>, sh_armor:get_coefficient(Armor)} +      ] +   }. diff --git a/src/battle/reply/btl_add_char.erl b/src/battle/reply/btl_add_char.erl new file mode 100644 index 0000000..75b6fcd --- /dev/null +++ b/src/battle/reply/btl_add_char.erl @@ -0,0 +1,83 @@ +-module(btl_add_char). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/3]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec rank_to_string (btl_character:rank()) -> binary(). +rank_to_string (Rank) -> +   case Rank of +      optional -> <<"o">>; +      target -> <<"t">>; +      commander -> <<"c">> +   end. + +-spec attributes_as_json +   ( +      sh_attributes:type() +   ) -> +   {list({binary(), non_neg_integer()})}. +attributes_as_json (Attributes) -> +   { +      [ +         {<<"con">>, sh_attributes:get_constitution(Attributes)}, +         {<<"dex">>, sh_attributes:get_dexterity(Attributes)}, +         {<<"int">>, sh_attributes:get_intelligence(Attributes)}, +         {<<"min">>, sh_attributes:get_mind(Attributes)}, +         {<<"spe">>, sh_attributes:get_speed(Attributes)}, +         {<<"str">>, sh_attributes:get_strength(Attributes)} +      ] +   }. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate +   ( +      non_neg_integer(), +      btl_character:type(), +      non_neg_integer() +   ) +   -> {list(any())}. +generate (IX, Character, PlayerIX) -> +   Attributes = btl_character:get_attributes(Character), +   {ActiveWeapon, SecondaryWeapon} = btl_character:get_weapon_ids(Character), +   CharacterPlayerIX = btl_character:get_player_index(Character), +   Location = btl_character:get_location(Character), + +   { +      [ +         {<<"msg">>, <<"add_char">>}, +         {<<"ix">>, IX}, +         {<<"nam">>, btl_character:get_name(Character)}, +         {<<"rnk">>, rank_to_string(btl_character:get_rank(Character))}, +         {<<"ico">>, btl_character:get_icon(Character)}, +         {<<"prt">>, btl_character:get_portrait(Character)}, +         { +            <<"hea">>, +            btl_character:get_current_health(Character) +         }, +         {<<"lc">>, btl_location:encode(Location)}, +         {<<"pla">>, CharacterPlayerIX}, +         { +            <<"ena">>, +            ( +               btl_character:get_is_active(Character) +               and (CharacterPlayerIX == PlayerIX) +            ) +         }, +         {<<"dea">>, btl_character:get_is_defeated(Character)}, +         {<<"att">>, attributes_as_json(Attributes)}, +         {<<"awp">>, ActiveWeapon}, +         {<<"swp">>, SecondaryWeapon}, +         {<<"ar">>, btl_character:get_armor_id(Character)} +      ] +   }. diff --git a/src/battle/reply/btl_add_tile.erl b/src/battle/reply/btl_add_tile.erl new file mode 100644 index 0000000..04c4ec2 --- /dev/null +++ b/src/battle/reply/btl_add_tile.erl @@ -0,0 +1,30 @@ +-module(btl_add_tile). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (btl_tile:type()) -> {list(any())}. +generate (Tile) -> +   { +      [ +         {<<"msg">>, <<"add_tile">>}, +         {<<"id">>, btl_tile:get_id(Tile)}, +         {<<"nam">>, btl_tile:get_name(Tile)}, +         {<<"ct">>, btl_tile:get_cost(Tile)}, +         {<<"rmi">>, btl_tile:get_range_minimum(Tile)}, +         {<<"rma">>, btl_tile:get_range_maximum(Tile)} +      ] +   }. diff --git a/src/battle/reply/btl_add_weapon.erl b/src/battle/reply/btl_add_weapon.erl new file mode 100644 index 0000000..d4edbef --- /dev/null +++ b/src/battle/reply/btl_add_weapon.erl @@ -0,0 +1,54 @@ +-module(btl_add_weapon). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec encode_range_type (sh_weapon:range_type()) -> binary(). +encode_range_type (melee) -> <<"m">>; +encode_range_type (ranged) -> <<"r">>. + +-spec encode_range_modifier (sh_weapon:range_modifier()) -> binary(). +encode_range_modifier (long) -> <<"l">>; +encode_range_modifier (short) -> <<"s">>. + +-spec encode_damage_type (sh_weapon:damage_type()) -> binary(). +encode_damage_type (slash) -> <<"s">>; +encode_damage_type (pierce) -> <<"p">>; +encode_damage_type (blunt) -> <<"b">>. + +-spec encode_damage_modifier (sh_weapon:damage_modifier()) -> binary(). +encode_damage_modifier (heavy) -> <<"h">>; +encode_damage_modifier (light) -> <<"l">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (sh_weapon:type()) -> {list(any())}. +generate (Weapon) -> +   { +      [ +         {<<"msg">>, <<"add_weapon">>}, +         {<<"id">>, sh_weapon:get_id(Weapon)}, +         {<<"nam">>, sh_weapon:get_name(Weapon)}, +         {<<"rt">>, encode_range_type(sh_weapon:get_range_type(Weapon))}, +         { +            <<"rm">>, +            encode_range_modifier(sh_weapon:get_range_modifier(Weapon)) +         }, +         {<<"dt">>, encode_damage_type(sh_weapon:get_damage_type(Weapon))}, +         { +            <<"dm">>, +            encode_damage_modifier(sh_weapon:get_damage_modifier(Weapon)) +         }, +         {<<"cf">>, sh_weapon:get_coefficient(Weapon)} +      ] +   }. diff --git a/src/battle/reply/btl_set_map.erl b/src/battle/reply/btl_set_map.erl new file mode 100644 index 0000000..37c6331 --- /dev/null +++ b/src/battle/reply/btl_set_map.erl @@ -0,0 +1,31 @@ +-module(btl_set_map). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (btl_battlemap:type()) -> {list(any())}. +generate (Battlemap) -> +   { +      [ +         {<<"msg">>, <<"set_map">>}, +         {<<"w">>, btl_battlemap:get_width(Battlemap)}, +         {<<"h">>, btl_battlemap:get_height(Battlemap)}, +         { +            <<"t">>, +            array:sparse_to_list(btl_battlemap:get_tile_class_ids(Battlemap)) +         } +      ] +   }. diff --git a/src/battle/reply/btl_set_timeline.erl b/src/battle/reply/btl_set_timeline.erl new file mode 100644 index 0000000..e6f571f --- /dev/null +++ b/src/battle/reply/btl_set_timeline.erl @@ -0,0 +1,27 @@ +-module(btl_set_timeline). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (list(any())) -> {list(any())}. +generate (EncodedClientUpdate) -> +   io:format("~nSending timeline:~n~p~n", [EncodedClientUpdate]), +   { +      [ +         {<<"msg">>, <<"set_timeline">>}, +         {<<"cnt">>, EncodedClientUpdate} +      ] +   }. diff --git a/src/battle/reply/btl_turn_results.erl b/src/battle/reply/btl_turn_results.erl new file mode 100644 index 0000000..361cab1 --- /dev/null +++ b/src/battle/reply/btl_turn_results.erl @@ -0,0 +1,27 @@ +-module(btl_turn_results). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (list(any())) -> {list(any())}. +generate (EncodedClientUpdate) -> +   io:format("~nSending turn results:~n~p~n", [EncodedClientUpdate]), +   { +      [ +         {<<"msg">>, <<"turn_results">>}, +         {<<"cnt">>, EncodedClientUpdate} +      ] +   }. diff --git a/src/battle/struct/btl_attack.erl b/src/battle/struct/btl_attack.erl new file mode 100644 index 0000000..aa7659f --- /dev/null +++ b/src/battle/struct/btl_attack.erl @@ -0,0 +1,306 @@ +-module(btl_attack). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type order() :: ('first' | 'second' | 'counter'). +-type precision() :: ('misses' | 'grazes' | 'hits'). + +-record +( +   attack, +   { +      order :: order(), +      precision :: precision(), +      is_critical :: boolean(), +      is_parry :: boolean(), +      damage :: non_neg_integer() +   } +). + +-opaque type() :: #attack{}. +-type maybe_type() :: ('nothing' | type()). +-opaque step() :: {order(), boolean()}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export_type([type/0, maybe_type/0, step/0]). + +-export +( +   [ +      get_sequence/3, +      get_description_of/3, +      apply_to_healths/3 +   ] +). + +-export +( +   [ +      encode/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec roll_precision +   ( +      sh_statistics:type(), +      sh_statistics:type() +   ) +   -> precision(). +roll_precision (AttackerStatistics, DefenderStatistics) -> +   DefenderDodges = sh_statistics:get_dodges(DefenderStatistics), +   AttackerAccuracy = sh_statistics:get_accuracy(AttackerStatistics), +   MissChance = max(0, (DefenderDodges - AttackerAccuracy)), +   case sh_roll:percentage() of +      X when (X =< MissChance) -> misses; +      X when (X =< (MissChance * 2)) -> grazes; +      _ -> hits +   end. + +-spec roll_damage +   ( +      sh_statistics:type(), +      sh_statistics:type() +   ) +   -> {non_neg_integer(), boolean()}. +roll_damage (AttackerStatistics, _DefenderStatistics) -> +   {MinimumDamage, MaximumDamage} = +      sh_statistics:get_damages(AttackerStatistics), +   MaximumRoll = max(1, MaximumDamage - MinimumDamage), +   BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1), +   CriticalHitChance = sh_statistics:get_critical_hits(AttackerStatistics), +   case sh_roll:percentage() of +      X when (X =< CriticalHitChance) -> {(BaseDamage * 2), true}; +      _ -> {BaseDamage, false} +   end. + +-spec roll_parry (sh_statistics:type()) -> boolean(). +roll_parry (DefenderStatistics) -> +   DefenderParryChance = sh_statistics:get_parries(DefenderStatistics), +   (sh_roll:percentage() =< DefenderParryChance). + +-spec effect_of_attack +   ( +      order(), +      btl_character:type(), +      btl_character:type(), +      boolean() +   ) +   -> type(). +effect_of_attack (Order, Attacker, Defender, CanParry) -> +   AttackerStatistics = btl_character:get_statistics(Attacker), +   DefenderStatistics = btl_character:get_statistics(Defender), + +   ParryIsSuccessful = (CanParry and roll_parry(DefenderStatistics)), + +   {ActualAtkStatistics, ActualDefStatistics} = +      case ParryIsSuccessful of +         true -> {DefenderStatistics, AttackerStatistics}; +         false -> {AttackerStatistics, DefenderStatistics} +      end, +   {ActualAttacker, ActualDefender} = +      case ParryIsSuccessful of +         true -> {Defender, Attacker}; +         false -> {Attacker, Defender} +      end, + +   ActualDefArmor = sh_armor:from_id(btl_character:get_armor_id(ActualDefender)), +   {ActualAtkWeaponID, _} = btl_character:get_weapon_ids(ActualAttacker), +   ActualAtkWeaponDmgType = +      sh_weapon:get_damage_type(sh_weapon:from_id(ActualAtkWeaponID)), + +   Precision = roll_precision(ActualAtkStatistics, ActualDefStatistics), +   {Damage, IsCritical} = roll_damage(ActualAtkStatistics, ActualDefStatistics), +   S0Damage = +      case Precision of +         misses -> 0; +         grazes -> trunc(Damage / 2); +         hits -> Damage +      end, +   ArmorResistance = +      sh_armor:get_resistance_to(ActualAtkWeaponDmgType, ActualDefArmor), +   ActualDamage = max(0, (S0Damage - ArmorResistance)), + +   #attack +   { +      order = Order, +      precision = Precision, +      is_critical = IsCritical, +      is_parry = ParryIsSuccessful, +      damage = ActualDamage +   }. + +-spec encode_order (order()) -> binary(). +encode_order (first) -> <<"f">>; +encode_order (counter) -> <<"c">>; +encode_order (second) -> <<"s">>. + +-spec encode_precision (precision()) -> binary(). +encode_precision (hits) -> <<"h">>; +encode_precision (grazes) -> <<"g">>; +encode_precision (misses) -> <<"m">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec get_description_of +   ( +      step(), +      btl_character:type(), +      btl_character:type() +   ) +   -> maybe_type(). +get_description_of ({first, CanParry}, Attacker, Defender) -> +   effect_of_attack(first, Attacker, Defender, CanParry); +get_description_of ({second, CanParry}, Attacker, Defender) -> +   AttackerStatistics = btl_character:get_statistics(Attacker), +   AttackerDoubleAttackChange = +      sh_statistics:get_double_hits(AttackerStatistics), + +   case sh_roll:percentage() of +      X when (X =< AttackerDoubleAttackChange) -> +         effect_of_attack (second, Attacker, Defender, CanParry); + +      _ -> +         nothing +   end; +get_description_of ({counter, CanParry}, Attacker, Defender) -> +   effect_of_attack(counter, Defender, Attacker, CanParry). + +-spec apply_to_healths +   ( +      maybe_type(), +      non_neg_integer(), +      non_neg_integer() +   ) +   -> {maybe_type(), non_neg_integer(), non_neg_integer()}. +apply_to_healths +( +   nothing, +   AttackerHealth, +   DefenderHealth +) -> +   {nothing, AttackerHealth, DefenderHealth}; +apply_to_healths +( +   _Attack, +   AttackerHealth, +   DefenderHealth +) +when +( +   (AttackerHealth =< 0) +   or (DefenderHealth =< 0) +) -> +   {nothing, AttackerHealth, DefenderHealth}; +apply_to_healths +( +   Attack, +   AttackerHealth, +   DefenderHealth +) +when +( +   ( +      (not Attack#attack.is_parry) +      and ((Attack#attack.order == first) or (Attack#attack.order == second)) +   ) +   or +   ( +      Attack#attack.is_parry +      and (Attack#attack.order == counter) +   ) +) -> +   Damage = Attack#attack.damage, + +   { +      Attack, +      AttackerHealth, +      (DefenderHealth - Damage) +   }; +apply_to_healths +( +   Attack, +   AttackerHealth, +   DefenderHealth +) +when +( +   ( +      (not Attack#attack.is_parry) +      and (Attack#attack.order == counter) +   ) +   or +   ( +      Attack#attack.is_parry +      and ((Attack#attack.order == first) or (Attack#attack.order == second)) +   ) +) -> +   Damage = Attack#attack.damage, + +   { +      Attack, +      (AttackerHealth - Damage), +      DefenderHealth +   }. + +-spec get_sequence +   ( +      non_neg_integer(), +      sh_weapon:type(), +      sh_weapon:type() +   ) +   -> list(step()). +get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) -> +   {AttackerDefenseRange, AttackerAttackRange} = +      sh_weapon:get_ranges(AttackerWeapon), +   {DefenderDefenseRange, DefenderAttackRange} = +      sh_weapon:get_ranges(DefenderWeapon), + +   AttackerCanAttack = (AttackRange =< AttackerAttackRange), +   AttackerCanAttack = true, +   AttackerCanDefend = +      (AttackerCanAttack and (AttackRange > AttackerDefenseRange)), +   AttackerCanParry = +      (AttackerCanDefend and sh_weapon:can_parry(AttackerWeapon)), + +   DefenderCanAttack = (AttackRange =< DefenderAttackRange), +   DefenderCanDefend = +      (DefenderCanAttack and (AttackRange > DefenderDefenseRange)), +   DefenderCanParry = +      (DefenderCanDefend and sh_weapon:can_parry(DefenderWeapon)), + +   First = {first, DefenderCanParry}, +   Second = {second, DefenderCanParry}, +   Counter = {counter, AttackerCanParry}, + +   if +      (not DefenderCanDefend) -> +         [First, Second]; + +      true -> +         [First, Counter, Second] +   end. + +-spec encode (type()) -> {list(any())}. +encode (Attack) -> +   Order = Attack#attack.order, +   Precision = Attack#attack.precision, +   IsCritical = Attack#attack.is_critical, +   IsParry = Attack#attack.is_parry, +   Damage = Attack#attack.damage, + +   { +      [ +         {<<"ord">>, encode_order(Order)}, +         {<<"pre">>, encode_precision(Precision)}, +         {<<"cri">>, IsCritical}, +         {<<"par">>, IsParry}, +         {<<"dmg">>, Damage} +      ] +   }. diff --git a/src/battle/struct/btl_battle.erl b/src/battle/struct/btl_battle.erl new file mode 100644 index 0000000..8befc4e --- /dev/null +++ b/src/battle/struct/btl_battle.erl @@ -0,0 +1,216 @@ +-module(btl_battle). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( +   battle, +   { +      id :: id(), +      used_armor_ids :: list(sh_armor:id()), +      used_weapon_ids :: list(sh_weapon:id()), +      used_tile_ids :: list(btl_tile:id()), +      battlemap :: btl_battlemap:type(), +      characters :: array:array(btl_character:type()), +      players :: array:array(btl_player:type()), +      current_player_turn :: btl_player_turn:type() +   } +). + +-opaque type() :: #battle{}. + +-export_type([type/0, id/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( +   [ +      get_id/1, +      get_used_weapon_ids/1, +      get_used_armor_ids/1, +      get_used_tile_ids/1, +      get_battlemap/1, +      get_characters/1, +      get_character/2, +      get_players/1, +      get_player/2, +      get_current_player_turn/1, +      get_encoded_last_turns_effects/1, + +      set_battlemap/2, +      set_characters/2, +      set_character/3, +      set_players/2, +      set_player/3, +      set_current_player_turn/2, + +      get_characters_field/0, +      get_players_field/0, +      get_current_player_turn_field/0 +   ] +). + +-export +( +   [ +      new/7 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +get_all_timelines (Result, CurrentIndex, EndPoint, ArraySize, Players) -> +   Player = array:get(CurrentIndex, Players), +   Timeline = btl_player:get_timeline(Player), +   NextIndex = ((CurrentIndex + 1) rem ArraySize), +   NextResult = (Timeline ++ Result), +   case CurrentIndex of +      EndPoint -> +         NextResult; + +      _ -> +         get_all_timelines(NextResult, NextIndex, EndPoint, ArraySize, Players) +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_id (type()) -> id(). +get_id (Battle) -> Battle#battle.id. + +-spec get_used_weapon_ids (type()) -> list(sh_weapon:id()). +get_used_weapon_ids (Battle) -> Battle#battle.used_weapon_ids. + +-spec get_used_armor_ids (type()) -> list(sh_armor:id()). +get_used_armor_ids (Battle) -> Battle#battle.used_armor_ids. + +-spec get_used_tile_ids (type()) -> list(btl_tile:id()). +get_used_tile_ids (Battle) -> Battle#battle.used_tile_ids. + +-spec get_battlemap (type()) -> btl_battlemap:type(). +get_battlemap (Battle) -> Battle#battle.battlemap. + +-spec get_characters (type()) -> array:array(btl_character:type()). +get_characters (Battle) -> Battle#battle.characters. + +-spec get_character (non_neg_integer(), type()) -> btl_character:type(). +get_character (IX, Battle) -> +   array:get(IX, Battle#battle.characters). + +-spec get_players (type()) -> array:array(btl_player:type()). +get_players (Battle) -> +   Battle#battle.players. + +-spec get_player (non_neg_integer(), type()) -> btl_player:type(). +get_player (IX, Battle) -> +   array:get(IX, Battle#battle.players). + +-spec get_current_player_turn (type()) -> btl_player_turn:type(). +get_current_player_turn (Battle) -> +   Battle#battle.current_player_turn. + +-spec get_encoded_last_turns_effects (type()) -> list(any()). +get_encoded_last_turns_effects (Battle) -> +   CurrentPlayerTurn = Battle#battle.current_player_turn, +   Players = Battle#battle.players, +   CurrentPlayerIX = btl_player_turn:get_player_ix(CurrentPlayerTurn), + +   PlayersCount = array:size(Players), +   StartingPoint = ((CurrentPlayerIX + 1) rem PlayersCount), +   get_all_timelines([], StartingPoint, CurrentPlayerIX, PlayersCount, Players). + +-spec set_battlemap (btl_battlemap:type(), type()) -> type(). +set_battlemap (Battlemap, Battle) -> +   Battle#battle +   { +      battlemap = Battlemap +   }. + +-spec set_characters (array:array(btl_character:type()), type()) -> type(). +set_characters (Characters, Battle) -> +   Battle#battle +   { +      characters = Characters +   }. + +-spec set_character (non_neg_integer(), btl_character:type(), type()) -> type(). +set_character (IX, Character, Battle) -> +   Battle#battle +   { +      characters = +         array:set +         ( +            IX, +            Character, +            Battle#battle.characters +         ) +   }. + +-spec set_players (array:array(btl_player:type()), type()) -> type(). +set_players (Players, Battle) -> +   Battle#battle +   { +      players = Players +   }. + +-spec set_player (non_neg_integer(), btl_player:type(), type()) -> type(). +set_player (IX, Player, Battle) -> +   Battle#battle +   { +      players = +         array:set +         ( +            IX, +            Player, +            Battle#battle.players +         ) +   }. + +-spec set_current_player_turn (btl_player_turn:type(), type()) -> type(). +set_current_player_turn (PlayerTurn, Battle) -> +   Battle#battle +   { +      current_player_turn = PlayerTurn +   }. + +-spec new +   ( +      id(), +      list(btl_player:type()), +      btl_battlemap:type(), +      list(btl_character:type()), +      list(sh_weapon:id()), +      list(sh_armor:id()), +      list(btl_tile:id()) +   ) +   -> type(). +new (ID, PlayersAsList, Battlemap, CharactersAsList, UWIDs, UAIDs, UTIDs) -> +   #battle +   { +      id = ID, +      used_weapon_ids = UWIDs, +      used_armor_ids = UAIDs, +      used_tile_ids = UTIDs, +      battlemap = Battlemap, +      characters = array:from_list(CharactersAsList), +      players = array:from_list(PlayersAsList), +      current_player_turn = btl_player_turn:new(0, 0) +   }. + + +-spec get_characters_field () -> non_neg_integer(). +get_characters_field () -> #battle.characters. + +-spec get_players_field () -> non_neg_integer(). +get_players_field () -> #battle.players. + +-spec get_current_player_turn_field () -> non_neg_integer(). +get_current_player_turn_field () -> #battle.current_player_turn. diff --git a/src/battle/struct/btl_battle_action.erl b/src/battle/struct/btl_battle_action.erl new file mode 100644 index 0000000..307043a --- /dev/null +++ b/src/battle/struct/btl_battle_action.erl @@ -0,0 +1,114 @@ +-module(btl_battle_action). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   move, +   { +      path :: list(btl_direction:enum()) +   } +). + +-record +( +   switch_weapon, +   { +   } +). + +-record +( +   attack, +   { +      target_ix :: non_neg_integer() +   } +). + +-type category() :: ('move' | 'switch_weapon' | 'attack' | 'nothing'). +-opaque type() :: (#move{} | #switch_weapon{} | #attack{}). + +-export_type([category/0, type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      decode/1, +      can_follow/2 +   ] +). + +-export +( +   [ +      get_path/1, +      get_target_ix/1, +      get_category/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode_mov_action (map()) -> type(). +decode_mov_action (JSONMap) -> +   PathInBinary = maps:get(<<"p">>, JSONMap), +   Path = lists:map(fun btl_direction:decode/1, PathInBinary), + +   #move { path = Path }. + +-spec decode_atk_action (map()) -> type(). +decode_atk_action (JSONMap) -> +   TargetIX = maps:get(<<"tix">>, JSONMap), + +   #attack { target_ix = TargetIX }. + +-spec decode_swp_action (map()) -> type(). +decode_swp_action (_JSONMap) -> +   #switch_weapon{}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode (map()) -> type(). +decode (EncodedAction) -> +   JSONActionMap = EncodedAction, %jiffy:decode(EncodedAction, [return_maps]), +   ActionType = maps:get(<<"t">>, JSONActionMap), +   case ActionType of +      <<"mov">> -> decode_mov_action(JSONActionMap); +      <<"atk">> -> decode_atk_action(JSONActionMap); +      <<"swp">> -> decode_swp_action(JSONActionMap) +   end. + +-spec can_follow (category(), category()) -> boolean(). +can_follow (nothing, attack) -> true; +can_follow (nothing, switch_weapon) -> true; +can_follow (nothing, move) -> true; +can_follow (switch_weapon, attack) -> true; +can_follow (move, attack) -> true; +can_follow (_, _) -> false. + +-spec get_path (type()) -> list(btl_direction:type()). +get_path (Action) when is_record(Action, move) -> +   Action#move.path; +get_path (_) -> +   []. + +-spec get_target_ix (type()) -> non_neg_integer(). +get_target_ix (Action) when is_record(Action, attack) -> +   Action#attack.target_ix; +get_target_ix (_) -> +   []. + +-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) -> +   io:format("How'd you get there?~p~n", [Action]), +   true = Action. + diff --git a/src/battle/struct/btl_battlemap.erl b/src/battle/struct/btl_battlemap.erl new file mode 100644 index 0000000..886e2a9 --- /dev/null +++ b/src/battle/struct/btl_battlemap.erl @@ -0,0 +1,100 @@ +-module(btl_battlemap). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( +   battlemap, +   { +      id :: id(), +      width :: integer(), +      height :: integer(), +      tile_class_ids :: array:array(btl_tile:class_id()) +   } +). + +-opaque type() :: #battlemap{}. + +-export_type([type/0, id/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( +   [ +      get_id/1, +      get_width/1, +      get_height/1, +      get_tile_class_ids/1, +      get_tile_class_id/2 +   ] +). + +-export +( +   [ +      from_list/4 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec location_to_array_index +   ( +      non_neg_integer(), +      btl_location:type() +   ) +   -> ('error' | non_neg_integer()). +location_to_array_index (ArrayWidth, {X, Y}) -> +   if +      (X < 0) -> error; +      (Y < 0) -> error; +      (X >= ArrayWidth) -> error; +      true -> ((Y * ArrayWidth) + X) +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_id (type()) -> id(). +get_id (Battlemap) -> Battlemap#battlemap.id. + +-spec get_width (type()) -> integer(). +get_width (Battlemap) -> Battlemap#battlemap.width. + +-spec get_height (type()) -> integer(). +get_height (Battlemap) -> Battlemap#battlemap.height. + +-spec get_tile_class_ids (type()) -> array:array(btl_tile:class_id()). +get_tile_class_ids (Battlemap) -> Battlemap#battlemap.tile_class_ids. + +-spec get_tile_class_id (btl_location:type(), type()) -> btl_tile:class_id(). +get_tile_class_id (Location, Battlemap) -> +   TileIX = location_to_array_index(Battlemap#battlemap.width, Location), +   array:get(TileIX, Battlemap#battlemap.tile_class_ids). + +-spec from_list +   ( +      non_neg_integer(), +      non_neg_integer(), +      non_neg_integer(), +      list(non_neg_integer()) +   ) +   -> type(). +from_list (ID, Width, Height, List) -> +   TileClassIDs = lists:map(fun btl_tile:class_id_from_int/1, List), + +   #battlemap +   { +      id = list_to_binary(integer_to_list(ID)), +      width = Width, +      height = Height, +      tile_class_ids = array:from_list(TileClassIDs) +   }. diff --git a/src/battle/struct/btl_character.erl b/src/battle/struct/btl_character.erl new file mode 100644 index 0000000..841f4ea --- /dev/null +++ b/src/battle/struct/btl_character.erl @@ -0,0 +1,290 @@ +-module(btl_character). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: non_neg_integer(). +-type rank() :: ('optional' | 'target' | 'commander'). + +-record +( +   character, +   { +      id :: id(), +      player_ix :: non_neg_integer(), +      name :: binary(), +      rank :: rank(), +      icon :: binary(), +      portrait :: binary(), +      attributes :: sh_attributes:type(), +      statistics :: sh_statistics:type(), +      weapon_ids :: {sh_weapon:id(), sh_weapon:id()}, +      armor_id :: sh_armor:id(), +      location :: {non_neg_integer(), non_neg_integer()}, +      current_health :: integer(), %% Negative integers let us reverse attacks. +      is_active :: boolean(), +      is_defeated :: boolean() +   } +). + +-opaque type() :: #character{}. + +-export_type([type/0, rank/0, id/0]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( +   [ +      get_id/1, +      get_player_index/1, +      get_name/1, +      get_rank/1, +      get_icon/1, +      get_portrait/1, +      get_attributes/1, +      get_statistics/1, +      get_weapon_ids/1, +      get_armor_id/1, +      get_location/1, +      get_current_health/1, +      get_is_alive/1, +      get_is_active/1, +      get_is_defeated/1, + +      set_rank/2, +      set_weapon_ids/2, +      set_armor_id/2, +      set_statistics/2, +      set_location/2, +      set_current_health/2, +      set_is_active/2, +      set_is_defeated/2, + +      get_rank_field/0, +      get_statistics_field/0, +      get_weapons_field/0, +      get_location_field/0, +      get_current_health_field/0, +      get_is_active_field/0, +      get_is_defeated_field/0 +   ] +). + +-export +( +   [ +      random/5 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec find_random_location +   ( +      non_neg_integer(), +      non_neg_integer(), +      list({non_neg_integer(), non_neg_integer()}) +   ) +   -> {non_neg_integer(), non_neg_integer()}. +find_random_location (BattlemapWidth, BattlemapHeight, ForbiddenLocations) -> +   X = sh_roll:between(0, (BattlemapWidth - 1)), +   Y = sh_roll:between(0, (BattlemapHeight - 1)), + +   IsForbidden = lists:member({X, Y}, ForbiddenLocations), + +   case IsForbidden of +      true -> +         find_random_location +         ( +            BattlemapWidth, +            BattlemapHeight, +            ForbiddenLocations +         ); + +      _ -> {X, Y} +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_id (type()) -> id(). +get_id (Char) -> Char#character.id. + +-spec get_player_index (type()) -> non_neg_integer(). +get_player_index (Char) -> Char#character.player_ix. + +-spec get_name (type()) -> binary(). +get_name (Char) -> Char#character.name. + +-spec get_rank (type()) -> rank(). +get_rank (Char) -> Char#character.rank. + +-spec get_icon (type()) -> binary(). +get_icon (Char) -> Char#character.icon. + +-spec get_portrait (type()) -> binary(). +get_portrait (Char) -> Char#character.portrait. + +-spec get_attributes (type()) -> sh_attributes:type(). +get_attributes (Char) -> Char#character.attributes. + +-spec get_armor_id (type()) -> sh_armor:id(). +get_armor_id (Char) -> Char#character.armor_id. + +-spec get_weapon_ids (type()) -> {sh_weapon:id(), sh_weapon:id()}. +get_weapon_ids (Char) -> Char#character.weapon_ids. + +-spec get_statistics (type()) -> sh_statistics:type(). +get_statistics (Char) -> Char#character.statistics. + +-spec get_location (type()) -> {non_neg_integer(), non_neg_integer()}. +get_location (Char) -> Char#character.location. + +-spec get_current_health (type()) -> integer(). +get_current_health (Char) -> Char#character.current_health. + +-spec get_is_alive (type()) -> boolean(). +get_is_alive (Char) -> +   ( +      (not Char#character.is_defeated) +      and (Char#character.current_health > 0) +   ). + +-spec get_is_active (type()) -> boolean(). +get_is_active (Char) -> +   ( +      (not Char#character.is_defeated) +      and Char#character.is_active +      and get_is_alive(Char) +   ). + +-spec get_is_defeated (type()) -> boolean(). +get_is_defeated (Char) -> Char#character.is_defeated. + +-spec set_rank (rank(), type()) -> type(). +set_rank (Rank, Char) -> +   Char#character +   { +      rank = Rank +   }. + +-spec set_location +   ( +      {non_neg_integer(), non_neg_integer()}, +      type() +   ) +   -> type(). +set_location (Location, Char) -> +   Char#character +   { +      location = Location +   }. + +-spec set_current_health (integer(), type()) -> type(). +set_current_health (Health, Char) -> +   Char#character +   { +      current_health = Health +   }. + +-spec set_is_active (boolean(), type()) -> type(). +set_is_active (Active, Char) -> +   Char#character +   { +      is_active = Active +   }. + +-spec set_is_defeated (boolean(), type()) -> type(). +set_is_defeated (Defeated, Char) -> +   Char#character +   { +      is_defeated = Defeated +   }. + +-spec set_armor_id (sh_armor:id(), type()) -> type(). +set_armor_id (ArmorID, Char) -> +   Char#character +   { +      armor_id = ArmorID +   }. + +-spec set_weapon_ids ({sh_weapon:id(), sh_weapon:id()}, type()) -> type(). +set_weapon_ids (WeaponIDs, Char) -> +   Char#character +   { +      weapon_ids = WeaponIDs +   }. + +-spec set_statistics +   ( +      sh_statistics:type(), +      type() +   ) +   -> type(). +set_statistics (Stats, Char) -> +   Char#character +   { +      statistics = Stats +   }. + +%%%% Utils +-spec random +   ( +      non_neg_integer(), +      non_neg_integer(), +      non_neg_integer(), +      non_neg_integer(), +      list({non_neg_integer(), non_neg_integer()}) +   ) +   -> type(). +random (ID, PlayerIX, BattlemapWidth, BattlemapHeight, ForbiddenLocations) -> +   Location = +      find_random_location(BattlemapWidth, BattlemapHeight, ForbiddenLocations), +   WeaponIDs = {sh_weapon:random_id(), sh_weapon:random_id()}, +   ArmorID = sh_armor:random_id(), +   Attributes = sh_attributes:random(), +   Statistics = sh_statistics:new(Attributes, WeaponIDs, ArmorID), +   IDAsListString = integer_to_list(ID), +   IDAsBinaryString = list_to_binary(IDAsListString), + +   #character +   { +      id = ID, +      player_ix = PlayerIX, +      name = list_to_binary("Char" ++ IDAsListString), +      rank = +         if +            ((ID rem 8) == 0) -> commander; +            ((ID rem 3) == 0) -> target; +            true -> optional +         end, +      icon = IDAsBinaryString, +      portrait = IDAsBinaryString, +      attributes = Attributes, +      weapon_ids = WeaponIDs, +      armor_id = ArmorID, +      statistics = Statistics, +      location = Location, +      current_health = sh_statistics:get_health(Statistics), +      is_active = false, +      is_defeated = false +   }. + +-spec get_rank_field() -> non_neg_integer(). +get_rank_field () -> #character.rank. +-spec get_statistics_field() -> non_neg_integer(). +get_statistics_field () -> #character.statistics. +-spec get_weapons_field() -> non_neg_integer(). +get_weapons_field () -> #character.weapon_ids. +-spec get_location_field() -> non_neg_integer(). +get_location_field () -> #character.location. +-spec get_current_health_field() -> non_neg_integer(). +get_current_health_field () -> #character.current_health. +-spec get_is_active_field() -> non_neg_integer(). +get_is_active_field () -> #character.is_active. +-spec get_is_defeated_field() -> non_neg_integer(). +get_is_defeated_field () -> #character.is_defeated. diff --git a/src/battle/struct/btl_character_turn_data.erl b/src/battle/struct/btl_character_turn_data.erl new file mode 100644 index 0000000..31a4b7d --- /dev/null +++ b/src/battle/struct/btl_character_turn_data.erl @@ -0,0 +1,115 @@ +-module(btl_character_turn_data). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   type, +   { +      dirty :: boolean(), +      battle :: btl_battle:type(), +      character :: btl_character:type(), +      character_ix :: non_neg_integer() +   } +). + +-opaque type() :: #type{}. + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      new/2, + +      get_battle_is_dirty/1, +      get_battle/1, +      get_character/1, +      get_character_ix/1, + +      set_battle/2, +      set_character/2 +   ] +). + +-export +( +   [ +      clean_battle/1, +      refresh_character/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec new (btl_battle:type(), non_neg_integer()) -> type(). +new (Battle, CharacterIX) -> +   Character = btl_battle:get_character(CharacterIX, Battle), + +   #type +   { +      dirty = false, +      battle = Battle, +      character = Character, +      character_ix = CharacterIX +   }. + +-spec get_battle_is_dirty (type()) -> boolean(). +get_battle_is_dirty (Data) -> Data#type.dirty. + +-spec get_battle (type()) -> btl_battle:type(). +get_battle (Data) -> Data#type.battle. + +-spec get_character (type()) -> btl_character:type(). +get_character (Data) -> Data#type.character. + +-spec get_character_ix (type()) -> non_neg_integer(). +get_character_ix (Data) -> Data#type.character_ix. + +-spec set_battle (btl_battle:type(), type()) -> type(). +set_battle (Battle, Data) -> +   Data#type{ battle = Battle }. + +-spec set_character (btl_character:type(), type()) -> type(). +set_character (Character, Data) -> +   Data#type +   { +      dirty = true, +      character = Character +   }. + +-spec clean_battle (type()) -> type(). +clean_battle (Data) -> +   Data#type +   { +      dirty = false, +      battle = +         btl_battle:set_character +         ( +            Data#type.character_ix, +            Data#type.character, +            Data#type.battle +         ) +   }. + +-spec refresh_character (type()) -> type(). +refresh_character (Data) -> +   Data#type +   { +      dirty = false, +      character = +         btl_battle:get_character +         ( +            Data#type.character_ix, +            Data#type.battle +         ) +   }. diff --git a/src/battle/struct/btl_character_turn_request.erl b/src/battle/struct/btl_character_turn_request.erl new file mode 100644 index 0000000..a4f310d --- /dev/null +++ b/src/battle/struct/btl_character_turn_request.erl @@ -0,0 +1,84 @@ +-module(btl_character_turn_request). + +-define(PLAYER_ID_FIELD, <<"pid">>). +-define(SESSION_TOKEN_FIELD, <<"stk">>). +-define(BATTLE_ID_FIELD, <<"bid">>). +-define(CHAR_IX_FIELD, <<"cix">>). +-define(ACTIONS_FIELD, <<"act">>). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   type, +   { +      player_id :: btl_player:id(), +      session_token :: binary(), +      battle_id :: binary(), +      character_ix :: non_neg_integer(), +      actions :: list(btl_battle_action:type()) +   } +). + +-opaque type() :: #type{}. + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      decode/1 +   ] +). + +-export +( +   [ +      get_player_id/1, +      get_session_token/1, +      get_battle_id/1, +      get_character_ix/1, +      get_actions/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode (map()) -> type(). +decode (Map) -> +   CharacterIX = maps:get(?CHAR_IX_FIELD, Map), +   EncodedActions = maps:get(?ACTIONS_FIELD, Map), +   Actions = lists:map(fun btl_battle_action:decode/1, EncodedActions), + +   #type +   { +      player_id = maps:get(?PLAYER_ID_FIELD, Map), +      session_token = maps:get(?SESSION_TOKEN_FIELD, Map), +      battle_id = maps:get(?BATTLE_ID_FIELD, Map), +      character_ix = CharacterIX, +      actions = Actions +   }. + +-spec get_player_id (type()) -> btl_player:id(). +get_player_id (Request) -> Request#type.player_id. + +-spec get_session_token (type()) -> binary(). +get_session_token (Request) -> Request#type.session_token. + +-spec get_battle_id (type()) -> binary(). +get_battle_id (Request) -> Request#type.battle_id. + +-spec get_character_ix (type()) -> non_neg_integer(). +get_character_ix (Request) -> Request#type.character_ix. + +-spec get_actions (type()) -> list(btl_battle_action:type()). +get_actions (Request) -> Request#type.actions. diff --git a/src/battle/struct/btl_character_turn_update.erl b/src/battle/struct/btl_character_turn_update.erl new file mode 100644 index 0000000..a6b29d9 --- /dev/null +++ b/src/battle/struct/btl_character_turn_update.erl @@ -0,0 +1,85 @@ +-module(btl_character_turn_update). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   type, +   { +      data :: btl_character_turn_data:type(), +      timeline :: list(any()), +      db :: list(sh_db_query:op()) +   } +). + +-opaque type() :: #type{}. + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      new/1, + +      get_data/1, +      get_timeline/1, +      get_db/1, + +      set_data/2, +      add_to_timeline/3, +      add_to_db/2 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec new (btl_character_turn_data:type()) -> type(). +new (Data) -> +   #type +   { +      data = Data, +      timeline = [], +      db = [] +   }. + +-spec get_data (type()) -> btl_character_turn_data:type(). +get_data (Update) -> Update#type.data. + +-spec get_timeline (type()) -> list(any()). +get_timeline (Update) -> Update#type.timeline. + +-spec get_db (type()) -> list(sh_db_query:op()). +get_db (Update) -> Update#type.db. + +-spec set_data (btl_character_turn_data:type(), type()) -> type(). +set_data (Data, Update) -> +   Update#type{ data = Data}. + +-spec add_to_timeline +   ( +      btl_turn_result:type(), +      sh_db_query:op(), +      type() +   ) -> type(). +add_to_timeline (Item, DBUpdate, Update) -> +   add_to_db +   ( +      DBUpdate, +      Update#type +      { +         timeline = [btl_turn_result:encode(Item)|Update#type.timeline] +      } +   ). + +-spec add_to_db (sh_db_query:op(), type()) -> type(). +add_to_db (Item, Update) -> +   Update#type{ db = [Item|Update#type.db] }. diff --git a/src/battle/struct/btl_direction.erl b/src/battle/struct/btl_direction.erl new file mode 100644 index 0000000..9fb5a01 --- /dev/null +++ b/src/battle/struct/btl_direction.erl @@ -0,0 +1,38 @@ +-module(btl_direction). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type enum() :: ('up' | 'down' | 'left' | 'right'). +-type type() :: enum(). + +-export_type([enum/0, type/0]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      decode/1, +      encode/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode (binary()) -> enum(). +decode (<<"U">>) -> up; +decode (<<"D">>) -> down; +decode (<<"L">>) -> left; +decode (<<"R">>) -> right. + +-spec encode (enum()) -> binary(). +encode (up) -> <<"U">>; +encode (down) -> <<"D">>; +encode (left) -> <<"L">>; +encode (right) -> <<"R">>. diff --git a/src/battle/struct/btl_location.erl b/src/battle/struct/btl_location.erl new file mode 100644 index 0000000..9670cb0 --- /dev/null +++ b/src/battle/struct/btl_location.erl @@ -0,0 +1,90 @@ +-module(btl_location). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type type() :: ({non_neg_integer(), non_neg_integer()} | 'nowhere'). + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      decode/1, +      encode/1, +      get_nowhere/0 +   ] +). + +-export +( +   [ +      apply_direction/2, +      dist/2 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec validate ({integer(), integer()}) -> type(). +validate ({X, Y}) -> +   if +      (X < 0) -> nowhere; +      (Y < 0) -> nowhere; +      true -> {X, Y} +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec get_nowhere () -> type(). +get_nowhere () -> nowhere. + +-spec apply_direction (btl_direction:enum(), type()) -> type(). +apply_direction (left, {X, Y}) -> +   validate({(X - 1), Y}); +apply_direction (right, {X, Y}) -> +   validate({(X + 1), Y}); +apply_direction (up, {X, Y}) -> +   validate({X, (Y - 1)}); +apply_direction (down, {X, Y}) -> +   validate({X, (Y + 1)}); +apply_direction (_, nowhere) -> +   error("Trying to move from 'nowhere'."), +   nowhere. + +-spec dist(type(), type()) -> non_neg_integer(). +dist ({OX, OY}, {DX, DY}) -> +   (abs(DY - OY) + abs(DX - OX)); +dist (_, _) -> +   error("Trying to measure distance to 'nowhere'"), +   999. + +-spec encode (type()) -> {list(any())}. +encode ({X, Y}) -> +   { +      [ +         {<<"x">>, X}, +         {<<"y">>, Y} +      ] +   }; +encode (nowhere) -> +   { +      [ +         {<<"x">>, -1}, +         {<<"y">>, -1} +      ] +   }. + +-spec decode (map()) -> type(). +decode (Map) -> +   X = maps:get(<<"x">>, Map), +   Y = maps:get(<<"y">>, Map), + +   true = (is_integer(X) and is_integer(Y)), + +   validate({X, Y}). diff --git a/src/battle/struct/btl_player.erl b/src/battle/struct/btl_player.erl new file mode 100644 index 0000000..1cb1d93 --- /dev/null +++ b/src/battle/struct/btl_player.erl @@ -0,0 +1,104 @@ +-module(btl_player). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( +   player, +   { +      ix :: non_neg_integer(), +      id :: id(), +      character_ix :: non_neg_integer(), +      timeline :: list(any()), +      is_active :: boolean() +   } +). + +-opaque type() :: #player{}. + +-export_type([type/0, id/0]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      get_id/1, +      get_index/1, +      get_character_index/1, +      get_timeline/1, + +      get_is_active/1, +      set_is_active/2, + +      add_to_timeline/2, +      reset_timeline/1, + +      get_timeline_field/0, +      get_is_active_field/0 +   ] +). + +-export +( +   [ +      new/3 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec get_id (type()) -> id(). +get_id (Player) -> Player#player.id. + +-spec get_index (type()) -> non_neg_integer(). +get_index (Player) -> Player#player.ix. + +-spec get_character_index (type()) -> non_neg_integer(). +get_character_index (Player) -> Player#player.character_ix. + +-spec get_timeline (type()) -> list(any()). +get_timeline (Player) -> Player#player.timeline. + +-spec get_is_active (type()) -> boolean(). +get_is_active (Player) -> Player#player.is_active. + +-spec set_is_active (boolean(), type()) -> type(). +set_is_active (Val, Player) -> Player#player{ is_active = Val }. + +-spec add_to_timeline (list(any()), type()) -> type(). +add_to_timeline (NewEvents, Player) -> +   OldTimeline = Player#player.timeline, + +   Player#player +   { +      timeline = (NewEvents ++ OldTimeline) +   }. + +-spec reset_timeline (type()) -> type(). +reset_timeline (Player) -> Player#player{ timeline = [] }. + +-spec new (non_neg_integer(), non_neg_integer(), id()) -> type(). +new (IX, CharacterIX, ID) -> +   #player +   { +      ix = IX, +      character_ix = CharacterIX, +      id = ID, +      is_active = true, +      timeline = [] +   }. + +-spec get_timeline_field () -> non_neg_integer(). +get_timeline_field () -> #player.timeline. + +-spec get_is_active_field () -> non_neg_integer(). +get_is_active_field () -> #player.is_active. diff --git a/src/battle/struct/btl_player_turn.erl b/src/battle/struct/btl_player_turn.erl new file mode 100644 index 0000000..e0665f4 --- /dev/null +++ b/src/battle/struct/btl_player_turn.erl @@ -0,0 +1,106 @@ +-module(btl_player_turn). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   player_turn, +   { +      number :: non_neg_integer(), +      player_ix :: non_neg_integer() +   } +). + +-opaque type() :: #player_turn{}. + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      new/2, +      next/2 +   ] +). + +%%%% Accessors +-export +( +   [ +      get_number/1, +      get_player_ix/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec next_valid_player +   ( +      non_neg_integer(), +      array:array(btl_player:type()), +      non_neg_integer(), +      non_neg_integer() +   ) -> non_neg_integer(). +next_valid_player (StartingPoint, _Players, _PlayersCount, StartingPoint) -> +   StartingPoint; +next_valid_player (CandidateIX, Players, PlayersCount, StartingPoint) -> +   Candidate = array:get(CandidateIX, Players), + +   case btl_player:get_is_active(Candidate) of +      true -> CandidateIX; +      _ -> +         next_valid_player +         ( +            ((CandidateIX + 1) rem PlayersCount), +            Players, +            PlayersCount, +            StartingPoint +         ) +   end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec new (non_neg_integer(), non_neg_integer()) -> type(). +new (Number, PlayerIX) -> +   #player_turn +   { +      number = Number, +      player_ix = PlayerIX +   }. + +-spec get_number (type()) -> non_neg_integer(). +get_number (PlayerTurn) -> PlayerTurn#player_turn.number. + +-spec get_player_ix (type()) -> non_neg_integer(). +get_player_ix (PlayerTurn) -> PlayerTurn#player_turn.player_ix. + +-spec next (array:array(btl_player:type()), type()) -> type(). +next (Players, CurrentPlayerTurn) -> +   CurrentPlayerIX = CurrentPlayerTurn#player_turn.player_ix, +   CurrentTurnNumber = CurrentPlayerTurn#player_turn.number, +   PlayersCount = array:size(Players), + +   NextPlayerIX = +      next_valid_player +      ( +         ((CurrentPlayerIX + 1) rem PlayersCount), +         Players, +         PlayersCount, +         CurrentPlayerIX +      ), + +   NextTurnNumber = +      case (NextPlayerIX < CurrentPlayerIX) of +         true -> (CurrentTurnNumber + 1); +         _ -> CurrentTurnNumber +      end, + +   new(NextTurnNumber, NextPlayerIX). diff --git a/src/battle/struct/btl_tile.erl b/src/battle/struct/btl_tile.erl new file mode 100644 index 0000000..16e671b --- /dev/null +++ b/src/battle/struct/btl_tile.erl @@ -0,0 +1,124 @@ +-module(btl_tile). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( +   tile, +   { +      id :: id(), +      name :: binary(), +      cost :: non_neg_integer(), +      class_range_min :: non_neg_integer(), +      class_range_max :: non_neg_integer() +   } +). + +-opaque id() :: non_neg_integer(). +-opaque class_id() :: non_neg_integer(). +-opaque type() :: #tile{}. + +-export_type([type/0, class_id/0, id/0]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( +   [ +      get_id/1, +      get_name/1, +      get_cost/1, +      get_range_minimum/1, +      get_range_maximum/1, +      from_id/1, +      cost_when_oob/0 +   ] +). + +-export +( +   [ +      class_id_to_type_id/1, +      class_id_from_int/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec class_id_to_type_id (class_id()) -> id(). +class_id_to_type_id (ClassID) -> +   case ClassID of +      0 -> 0; +      1 -> 1; +      2 -> 2; +      N when ((N >= 3) and (N =< 17)) -> 3 +   end. + +-spec from_id (id()) -> type(). +from_id (0) -> +   #tile +   { +      id = 0, +      name = <<"[Grassland] Grass">>, +      cost = 6, +      class_range_min = 0, +      class_range_max = 0 +   }; +from_id (1) -> +   #tile +   { +      id = 1, +      name = <<"[Grassland] Mushroom Infestation">>, +      cost = 12, +      class_range_min = 1, +      class_range_max = 1 +   }; +from_id (2) -> +   #tile +   { +      id = 2, +      name = <<"[Grassland] Tree Remains">>, +      cost = 24, +      class_range_min = 2, +      class_range_max = 2 +   }; +from_id (3) -> +   #tile +   { +      id = 3, +      name = <<"[Grassland] Clear Water">>, +      cost = cost_when_occupied(), +      class_range_min = 3, +      class_range_max = 17 +   }. + +-spec cost_when_oob () -> non_neg_integer(). +cost_when_oob () -> 255. + +-spec cost_when_occupied () -> non_neg_integer(). +cost_when_occupied () -> 201. + +-spec get_id (type()) -> non_neg_integer(). +get_id (Tile) -> Tile#tile.id. + +-spec get_cost (type()) -> non_neg_integer(). +get_cost (Tile) -> Tile#tile.cost. + +-spec get_name (type()) -> binary(). +get_name (Tile) -> Tile#tile.name. + +-spec get_range_minimum (type()) -> non_neg_integer(). +get_range_minimum (Tile) -> Tile#tile.class_range_min. + +-spec get_range_maximum (type()) -> non_neg_integer(). +get_range_maximum (Tile) -> Tile#tile.class_range_max. + +-spec class_id_from_int (non_neg_integer()) -> id(). +class_id_from_int (I) -> I. diff --git a/src/battle/struct/btl_turn_result.erl b/src/battle/struct/btl_turn_result.erl new file mode 100644 index 0000000..97169e3 --- /dev/null +++ b/src/battle/struct/btl_turn_result.erl @@ -0,0 +1,215 @@ +-module(btl_turn_result). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +-record +( +   switched_weapon, +   { +      character_ix :: btl_character:id() +   } +). + +-record +( +   moved, +   { +      character_ix :: btl_character:id(), +      path :: list(btl_direction:enum()), +      new_location :: btl_location:type() +   } +). + +-record +( +   attacked, +   { +      attacker_ix :: btl_character:id(), +      defender_ix :: btl_character:id(), +      sequence :: list(btl_attack:type()) +   } +). + +-record +( +   player_won, +   { +      player_ix :: non_neg_integer() +   } +). + +-record +( +   player_lost, +   { +      player_ix :: non_neg_integer() +   } +). + +-record +( +   player_turn_started, +   { +      player_ix :: non_neg_integer() +   } +). + +-opaque type() :: ( +   #switched_weapon{} +   | #moved{} +   | #attacked{} +   | #player_won{} +   | #player_lost{} +   | #player_turn_started{} +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export_type([type/0]). + +-export +( +   [ +      new_player_won/1, +      new_player_lost/1, +      new_player_turn_started/1, +      new_character_switched_weapons/1, +      new_character_moved/3, +      new_character_attacked/3 +   ] +). + +-export +( +   [ +      encode/1 +   ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec new_player_won (non_neg_integer()) -> type(). +new_player_won (PlayerIX) -> +   #player_won { player_ix = PlayerIX }. + +-spec new_player_lost (non_neg_integer()) -> type(). +new_player_lost (PlayerIX) -> +   #player_lost { player_ix = PlayerIX }. + +-spec new_player_turn_started (non_neg_integer()) -> type(). +new_player_turn_started (PlayerIX) -> +   #player_turn_started { player_ix = PlayerIX }. + +-spec new_character_switched_weapons (btl_character:id()) -> type(). +new_character_switched_weapons (CharacterIX) -> +   #switched_weapon { character_ix = CharacterIX }. + +-spec new_character_moved +   ( +      btl_character:id(), +      list(btl_direction:enum()), +      btl_location:type() +   ) +   -> type(). +new_character_moved (CharacterIX, Path, NewLocation) -> +   #moved +   { +      character_ix = CharacterIX, +      path = Path, +      new_location = NewLocation +   }. + +-spec new_character_attacked +   ( +      btl_character:id(), +      btl_character:id(), +      list(btl_attack:type()) +   ) +   -> type(). +new_character_attacked (AttackerIX, DefenderIX, AttackSequence) -> +   #attacked +   { +      attacker_ix = AttackerIX, +      defender_ix = DefenderIX, +      sequence = AttackSequence +   }. + +-spec encode (type()) -> {list(any())}. +encode (TurnResult) when is_record(TurnResult, switched_weapon) -> +   CharacterIX = TurnResult#switched_weapon.character_ix, + +   { +      [ +         {<<"t">>, <<"swp">>}, +         {<<"ix">>, CharacterIX} +      ] +   }; +encode (TurnResult) when is_record(TurnResult, moved) -> +   CharacterIX = TurnResult#moved.character_ix, +   Path = TurnResult#moved.path, +   NewLocation = TurnResult#moved.new_location, + +   EncodedPath = lists:map(fun btl_direction:encode/1, Path), +   EncodedNewLocation = btl_location:encode(NewLocation), + +   { +      [ +         {<<"t">>, <<"mv">>}, +         {<<"ix">>, CharacterIX}, +         {<<"p">>, EncodedPath}, +         {<<"nlc">>, EncodedNewLocation} +      ] +   }; +encode (TurnResult) when is_record(TurnResult, attacked) -> +   AttackerIX = TurnResult#attacked.attacker_ix, +   DefenderIX = TurnResult#attacked.defender_ix, +   Sequence = TurnResult#attacked.sequence, + +   EncodedSequence = lists:map(fun btl_attack:encode/1, Sequence), + +   { +      [ +         {<<"t">>, <<"atk">>}, +         {<<"aix">>, AttackerIX}, +         {<<"dix">>, DefenderIX}, +         {<<"seq">>, EncodedSequence} +      ] +   }; +encode (TurnResult) when is_record(TurnResult, player_won) -> +   PlayerIX = TurnResult#player_won.player_ix, + +   { +      [ +         {<<"t">>, <<"pwo">>}, +         {<<"ix">>, PlayerIX} +      ] +   }; +encode (TurnResult) when is_record(TurnResult, player_lost) -> +   PlayerIX = TurnResult#player_lost.player_ix, + +   { +      [ +         {<<"t">>, <<"plo">>}, +         {<<"ix">>, PlayerIX} +      ] +   }; +encode (TurnResult) when is_record(TurnResult, player_turn_started) -> +   PlayerIX = TurnResult#player_turn_started.player_ix, + +   { +      [ +         {<<"t">>, <<"pts">>}, +         {<<"ix">>, PlayerIX} +      ] +   }; +encode (Other) -> +   io:format("~n invalid encode param\"~p\"~n", [Other]), +   true = Other. | 


