summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornsensfel <SpamShield0@noot-noot.org>2018-07-11 17:54:14 +0200
committernsensfel <SpamShield0@noot-noot.org>2018-07-11 17:54:14 +0200
commitfde827cba1ff3d889135c74ee1978098465fd200 (patch)
treed6022f800aa8226bf79a26a19189965c8cbfb2fe /src/battle
parentdf59024199c387903e3d4a901171939a358489d3 (diff)
"Battlemap" -> "Battle".
Diffstat (limited to 'src/battle')
-rw-r--r--src/battle/btl_handler.erl33
-rw-r--r--src/battle/btl_shim.erl183
-rw-r--r--src/battle/game-logic/btl_movement.erl60
-rw-r--r--src/battle/game-logic/btl_next_turn.erl160
-rw-r--r--src/battle/game-logic/btl_turn_actions.erl391
-rw-r--r--src/battle/game-logic/btl_victory.erl194
-rw-r--r--src/battle/io/btl_security.erl33
-rw-r--r--src/battle/query/btl_character_turn.erl282
-rw-r--r--src/battle/query/btl_load_state.erl159
-rw-r--r--src/battle/reply/btl_add_armor.erl34
-rw-r--r--src/battle/reply/btl_add_char.erl83
-rw-r--r--src/battle/reply/btl_add_tile.erl30
-rw-r--r--src/battle/reply/btl_add_weapon.erl54
-rw-r--r--src/battle/reply/btl_set_map.erl31
-rw-r--r--src/battle/reply/btl_set_timeline.erl27
-rw-r--r--src/battle/reply/btl_turn_results.erl27
-rw-r--r--src/battle/struct/btl_attack.erl306
-rw-r--r--src/battle/struct/btl_battle.erl216
-rw-r--r--src/battle/struct/btl_battle_action.erl114
-rw-r--r--src/battle/struct/btl_battlemap.erl100
-rw-r--r--src/battle/struct/btl_character.erl290
-rw-r--r--src/battle/struct/btl_character_turn_data.erl115
-rw-r--r--src/battle/struct/btl_character_turn_request.erl84
-rw-r--r--src/battle/struct/btl_character_turn_update.erl85
-rw-r--r--src/battle/struct/btl_direction.erl38
-rw-r--r--src/battle/struct/btl_location.erl90
-rw-r--r--src/battle/struct/btl_player.erl104
-rw-r--r--src/battle/struct/btl_player_turn.erl106
-rw-r--r--src/battle/struct/btl_tile.erl124
-rw-r--r--src/battle/struct/btl_turn_result.erl215
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.