summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/battlemap/struct')
-rw-r--r--src/battlemap/struct/bm_attack.erl300
-rw-r--r--src/battlemap/struct/bm_battle.erl197
-rw-r--r--src/battlemap/struct/bm_battle_action.erl114
-rw-r--r--src/battlemap/struct/bm_battlemap.erl121
-rw-r--r--src/battlemap/struct/bm_character.erl239
-rw-r--r--src/battlemap/struct/bm_character_turn_data.erl102
-rw-r--r--src/battlemap/struct/bm_character_turn_request.erl84
-rw-r--r--src/battlemap/struct/bm_character_turn_update.erl73
-rw-r--r--src/battlemap/struct/bm_direction.erl38
-rw-r--r--src/battlemap/struct/bm_location.erl90
-rw-r--r--src/battlemap/struct/bm_player.erl72
-rw-r--r--src/battlemap/struct/bm_player_turn.erl73
-rw-r--r--src/battlemap/struct/bm_tile.erl47
-rw-r--r--src/battlemap/struct/bm_turn_result.erl142
-rw-r--r--src/battlemap/struct/bm_weapon.erl360
15 files changed, 2052 insertions, 0 deletions
diff --git a/src/battlemap/struct/bm_attack.erl b/src/battlemap/struct/bm_attack.erl
new file mode 100644
index 0000000..fe7d8b8
--- /dev/null
+++ b/src/battlemap/struct/bm_attack.erl
@@ -0,0 +1,300 @@
+-module(bm_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
+ (
+ bm_statistics:type(),
+ bm_statistics:type()
+ )
+ -> precision().
+roll_precision (AttackerStatistics, DefenderStatistics) ->
+ DefenderDodges = bm_statistics:get_dodges(DefenderStatistics),
+ AttackerAccuracy = bm_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
+ (
+ bm_statistics:type(),
+ bm_statistics:type()
+ )
+ -> {non_neg_integer(), boolean()}.
+roll_damage (AttackerStatistics, _DefenderStatistics) ->
+ {MinimumDamage, MaximumDamage} =
+ bm_statistics:get_damages(AttackerStatistics),
+ MaximumRoll = max(1, MaximumDamage - MinimumDamage),
+ BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1),
+ CriticalHitChance = bm_statistics:get_critical_hits(AttackerStatistics),
+ case sh_roll:percentage() of
+ X when (X =< CriticalHitChance) -> {(BaseDamage * 2), true};
+ _ -> {BaseDamage, false}
+ end.
+
+-spec roll_parry (bm_statistics:type()) -> boolean().
+roll_parry (DefenderStatistics) ->
+ DefenderParryChance = bm_statistics:get_parries(DefenderStatistics),
+ (sh_roll:percentage() =< DefenderParryChance).
+
+-spec effect_of_attack
+ (
+ order(),
+ bm_statistics:type(),
+ bm_statistics:type(),
+ boolean()
+ )
+ -> type().
+effect_of_attack (Order, AttackerStatistics, DefenderStatistics, CanParry) ->
+ ParryIsSuccessful = (CanParry and roll_parry(DefenderStatistics)),
+ {ActualAtkStatistics, ActualDefStatistics} =
+ case ParryIsSuccessful of
+ true -> {DefenderStatistics, AttackerStatistics};
+ false -> {AttackerStatistics, DefenderStatistics}
+ end,
+
+ Precision = roll_precision(ActualAtkStatistics, ActualDefStatistics),
+ {Damage, IsCritical} = roll_damage(ActualAtkStatistics, ActualDefStatistics),
+ ActualDamage =
+ case Precision of
+ misses -> 0;
+ grazes -> trunc(Damage / 2);
+ hits -> Damage
+ end,
+
+ #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(),
+ bm_statistics:type(),
+ bm_statistics:type()
+ )
+ -> maybe_type().
+get_description_of
+(
+ {first, CanParry},
+ AttackerStatistics,
+ DefenderStatistics
+) ->
+ effect_of_attack(first, AttackerStatistics, DefenderStatistics, CanParry);
+get_description_of
+(
+ {second, CanParry},
+ AttackerStatistics,
+ DefenderStatistics
+) ->
+ AttackerDoubleAttackChange =
+ bm_statistics:get_double_hits(AttackerStatistics),
+
+ case sh_roll:percentage() of
+ X when (X =< AttackerDoubleAttackChange) ->
+ effect_of_attack
+ (
+ second,
+ AttackerStatistics,
+ DefenderStatistics,
+ CanParry
+ );
+
+ _ ->
+ nothing
+ end;
+get_description_of
+(
+ {counter, CanParry},
+ AttackerStatistics,
+ DefenderStatistics
+) ->
+ effect_of_attack(counter, DefenderStatistics, AttackerStatistics, 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
+(
+ (Attack#attack.order == first)
+ or (Attack#attack.order == second)
+ or ((Attack#attack.order == counter) and Attack#attack.is_parry)
+) ->
+ Damage = Attack#attack.damage,
+
+ case AttackerHealth of
+ 0 ->
+ {nothing, AttackerHealth, DefenderHealth};
+
+ _ ->
+ {
+ Attack,
+ AttackerHealth,
+ max(0, (DefenderHealth - Damage))
+ }
+ end;
+apply_to_healths
+(
+ Attack,
+ AttackerHealth,
+ DefenderHealth
+)
+when
+(
+ (Attack#attack.order == counter)
+ or
+ (
+ (Attack#attack.is_parry)
+ and ((Attack#attack.order == first) or (Attack#attack.order == second))
+ )
+) ->
+ Damage = Attack#attack.damage,
+
+ case DefenderHealth of
+ 0 ->
+ {nothing, AttackerHealth, DefenderHealth};
+
+ _ ->
+ {
+ Attack,
+ max(0, (AttackerHealth - Damage)),
+ DefenderHealth
+ }
+ end.
+
+-spec get_sequence
+ (
+ non_neg_integer(),
+ bm_weapon:type(),
+ bm_weapon:type()
+ )
+ -> list(step()).
+get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) ->
+ {AttackerDefenseRange, AttackerAttackRange} =
+ bm_weapon:get_ranges(AttackerWeapon),
+ {DefenderDefenseRange, DefenderAttackRange} =
+ bm_weapon:get_ranges(DefenderWeapon),
+
+ AttackerCanAttack = (AttackRange =< AttackerAttackRange),
+ AttackerCanAttack = true,
+ AttackerCanDefend =
+ (AttackerCanAttack and (AttackRange > AttackerDefenseRange)),
+ AttackerCanParry =
+ (AttackerCanDefend and weapon:can_parry(AttackerWeapon)),
+
+ DefenderCanAttack = (AttackRange =< DefenderAttackRange),
+ DefenderCanDefend =
+ (DefenderCanAttack and (AttackRange > DefenderDefenseRange)),
+ DefenderCanParry =
+ (DefenderCanDefend and 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/battlemap/struct/bm_battle.erl b/src/battlemap/struct/bm_battle.erl
new file mode 100644
index 0000000..08c4943
--- /dev/null
+++ b/src/battlemap/struct/bm_battle.erl
@@ -0,0 +1,197 @@
+-module(bm_battle).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ battle,
+ {
+ id :: id(),
+ battlemap :: bm_battlemap:type(),
+ characters :: array:array(bm_character:type()),
+ players :: array:array(bm_player:type()),
+ current_player_turn :: bm_player_turn:type()
+ }
+).
+
+-opaque type() :: #battle{}.
+
+-export_type([type/0, id/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/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/4
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+get_all_timelines (Result, CurrentIndex, EndPoint, ArraySize, Players) ->
+ Player = array:get(CurrentIndex, Players),
+ Timeline = bm_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_battlemap (type()) -> bm_battlemap:type().
+get_battlemap (Battle) ->
+ Battle#battle.battlemap.
+
+-spec get_characters (type()) -> array:array(bm_character:type()).
+get_characters (Battle) ->
+ Battle#battle.characters.
+
+-spec get_character (non_neg_integer(), type()) -> bm_character:type().
+get_character (IX, Battle) ->
+ array:get(IX, Battle#battle.characters).
+
+-spec get_players (type()) -> array:array(bm_player:type()).
+get_players (Battle) ->
+ Battle#battle.players.
+
+-spec get_player (non_neg_integer(), type()) -> bm_player:type().
+get_player (IX, Battle) ->
+ array:get(IX, Battle#battle.players).
+
+-spec get_current_player_turn (type()) -> bm_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 = bm_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 (bm_battlemap:type(), type()) -> type().
+set_battlemap (Battlemap, Battle) ->
+ Battle#battle
+ {
+ battlemap = Battlemap
+ }.
+
+-spec set_characters (array:array(bm_character:type()), type()) -> type().
+set_characters (Characters, Battle) ->
+ Battle#battle
+ {
+ characters = Characters
+ }.
+
+-spec set_character (non_neg_integer(), bm_character:type(), type()) -> type().
+set_character (IX, Character, Battle) ->
+ Battle#battle
+ {
+ characters =
+ array:set
+ (
+ IX,
+ Character,
+ Battle#battle.characters
+ )
+ }.
+
+-spec set_players (array:array(bm_player:type()), type()) -> type().
+set_players (Players, Battle) ->
+ Battle#battle
+ {
+ players = Players
+ }.
+
+-spec set_player (non_neg_integer(), bm_player:type(), type()) -> type().
+set_player (IX, Player, Battle) ->
+ Battle#battle
+ {
+ players =
+ array:set
+ (
+ IX,
+ Player,
+ Battle#battle.players
+ )
+ }.
+
+-spec set_current_player_turn (bm_player_turn:type(), type()) -> type().
+set_current_player_turn (PlayerTurn, Battle) ->
+ Battle#battle
+ {
+ current_player_turn = PlayerTurn
+ }.
+
+-spec new
+ (
+ id(),
+ list(bm_player:type()),
+ bm_battlemap:type(),
+ list(bm_character:type())
+ )
+ -> type().
+new (ID, PlayersAsList, Battlemap, CharactersAsList) ->
+ #battle
+ {
+ id = ID,
+ battlemap = Battlemap,
+ characters = array:from_list(CharactersAsList),
+ players = array:from_list(PlayersAsList),
+ current_player_turn = bm_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/battlemap/struct/bm_battle_action.erl b/src/battlemap/struct/bm_battle_action.erl
new file mode 100644
index 0000000..abdc1c6
--- /dev/null
+++ b/src/battlemap/struct/bm_battle_action.erl
@@ -0,0 +1,114 @@
+-module(bm_battle_action).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ move,
+ {
+ path :: list(bm_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 bm_direction:decode/1, PathInBinary),
+
+ #move { path = Path }.
+
+-spec decode_atk_action (map()) -> type().
+decode_atk_action (JSONMap) ->
+ TargetIX = binary_to_integer(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(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/battlemap/struct/bm_battlemap.erl b/src/battlemap/struct/bm_battlemap.erl
new file mode 100644
index 0000000..c3eabb0
--- /dev/null
+++ b/src/battlemap/struct/bm_battlemap.erl
@@ -0,0 +1,121 @@
+-module(bm_battlemap).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ battlemap,
+ {
+ id :: id(),
+ width :: integer(),
+ height :: integer(),
+ tile_ids :: array:array(bm_tile:id())
+ }
+).
+
+-opaque type() :: #battlemap{}.
+
+-export_type([type/0, id/0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_width/1,
+ get_height/1,
+ get_tile_ids/1,
+ get_tile_id/2
+ ]
+).
+
+-export
+(
+ [
+ random/3
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec generate_random_tile_ids
+ (
+ bm_tile:id(),
+ list(bm_tile:id()),
+ non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer()
+ )
+ -> list(bm_tile:id()).
+generate_random_tile_ids (_PreviousTileID, Result, _X, 0, _Width) ->
+ Result;
+generate_random_tile_ids (PreviousTileID, Result, 0, Y, Width) ->
+ generate_random_tile_ids(PreviousTileID, Result, Width, (Y - 1), Width);
+generate_random_tile_ids (PreviousTileID, Result, X, Y, Width) ->
+ NewTile =
+ case sh_roll:percentage() of
+ N when (N >= 10) -> PreviousTileID;
+ _ -> bm_tile:random_id()
+ end,
+ generate_random_tile_ids(NewTile, [NewTile|Result], (X - 1), Y, Width).
+
+-spec location_to_array_index
+ (
+ non_neg_integer(),
+ bm_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_ids (type()) -> array:array(bm_tile:id()).
+get_tile_ids (Battlemap) -> Battlemap#battlemap.tile_ids.
+
+-spec get_tile_id (bm_location:type(), type()) -> bm_tile:id().
+get_tile_id (Location, Battlemap) ->
+ TileIX = location_to_array_index(Battlemap#battlemap.width, Location),
+ array:get(TileIX, Battlemap#battlemap.tile_ids).
+
+-spec random
+ (
+ non_neg_integer(),
+ non_neg_integer(),
+ non_neg_integer()
+ )
+ -> type().
+random (ID, Width, Height) ->
+ InitialTile = bm_tile:random_id(),
+ TileIDs = generate_random_tile_ids(InitialTile, [], Width, Height, Width),
+
+ #battlemap
+ {
+ id = list_to_binary(integer_to_list(ID)),
+ width = Width,
+ height = Height,
+ tile_ids = array:from_list(TileIDs)
+ }.
diff --git a/src/battlemap/struct/bm_character.erl b/src/battlemap/struct/bm_character.erl
new file mode 100644
index 0000000..e797b74
--- /dev/null
+++ b/src/battlemap/struct/bm_character.erl
@@ -0,0 +1,239 @@
+-module(bm_character).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: non_neg_integer().
+
+-record
+(
+ character,
+ {
+ id :: id(),
+ owner_id :: bm_player:id(),
+ name :: binary(),
+ icon :: binary(),
+ portrait :: binary(),
+ attributes :: sh_attributes:type(),
+ statistics :: bm_statistics:type(),
+ weapon_ids :: {bm_weapon:id(), bm_weapon:id()},
+ location :: {non_neg_integer(), non_neg_integer()},
+ current_health :: non_neg_integer(),
+ active :: boolean()
+ }
+).
+
+-opaque type() :: #character{}.
+
+-export_type([type/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_owner_id/1,
+ get_name/1,
+ get_icon/1,
+ get_portrait/1,
+ get_attributes/1,
+ get_statistics/1,
+ get_weapon_ids/1,
+ get_location/1,
+ get_current_health/1,
+ get_is_alive/1,
+ get_is_active/1,
+
+ set_weapon_ids/2,
+ set_statistics/2,
+ set_location/2,
+ set_current_health/2,
+ set_is_active/2,
+
+ get_statistics_field/0,
+ get_weapons_field/0,
+ get_location_field/0,
+ get_current_health_field/0,
+ get_active_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 = roll:between(0, (BattlemapWidth - 1)),
+ Y = 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_owner_id (type()) -> bm_player:id().
+get_owner_id (Char) -> Char#character.owner_id.
+
+-spec get_name (type()) -> binary().
+get_name (Char) -> Char#character.name.
+
+-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_weapon_ids (type()) -> {bm_weapon:id(), bm_weapon:id()}.
+get_weapon_ids (Char) -> Char#character.weapon_ids.
+
+-spec get_statistics (type()) -> bm_statistics:type().
+get_statistics (Char) -> Char#character.statistics.
+
+-spec get_location (type()) -> {non_neg_integer(), non_neg_integer()}.
+get_location (Char) ->
+ true = get_is_alive(Char),
+ Char#character.location.
+
+-spec get_current_health (type()) -> non_neg_integer().
+get_current_health (Char) -> Char#character.current_health.
+
+-spec get_is_alive (type()) -> boolean().
+get_is_alive (Char) ->
+ (Char#character.current_health > 0).
+
+-spec get_is_active (type()) -> boolean().
+get_is_active (Char) ->
+ (
+ Char#character.active
+ and
+ get_is_alive(Char)
+ ).
+
+-spec set_location
+ (
+ {non_neg_integer(), non_neg_integer()},
+ type()
+ )
+ -> type().
+set_location (Location, Char) ->
+ Char#character
+ {
+ location = Location
+ }.
+
+-spec set_current_health (non_neg_integer(), type()) -> type().
+set_current_health (Health, Char) ->
+ Char#character
+ {
+ current_health = max(0, Health)
+ }.
+
+-spec set_is_active (boolean(), type()) -> type().
+set_is_active (Active, Char) ->
+ Char#character
+ {
+ active = Active
+ }.
+
+-spec set_weapon_ids
+ (
+ {bm_weapon:id(), bm_weapon:id()},
+ type()
+ )
+ -> type().
+set_weapon_ids (WeaponIDs, Char) ->
+ Char#character
+ {
+ weapon_ids = WeaponIDs
+ }.
+
+-spec set_statistics
+ (
+ bm_statistics:type(),
+ type()
+ )
+ -> type().
+set_statistics (Stats, Char) ->
+ Char#character
+ {
+ statistics = Stats
+ }.
+
+%%%% Utils
+-spec random
+ (
+ non_neg_integer(),
+ bm_player:id(),
+ non_neg_integer(),
+ non_neg_integer(),
+ list({non_neg_integer(), non_neg_integer()})
+ )
+ -> type().
+random (ID, OwnerID, BattlemapWidth, BattlemapHeight, ForbiddenLocations) ->
+ Location =
+ find_random_location(BattlemapWidth, BattlemapHeight, ForbiddenLocations),
+ WeaponIDs = {bm_weapon:random_id(), bm_weapon:random_id()},
+ Attributes = sh_attributes:random(),
+ Statistics = bm_statistics:new(Attributes, WeaponIDs),
+ IDAsListString = integer_to_list(ID),
+ IDAsBinaryString = list_to_binary(IDAsListString),
+
+ #character
+ {
+ id = ID,
+ owner_id = OwnerID,
+ name = list_to_binary("Char" ++ IDAsListString),
+ icon = IDAsBinaryString,
+ portrait = IDAsBinaryString,
+ attributes = Attributes,
+ weapon_ids = WeaponIDs,
+ statistics = Statistics,
+ location = Location,
+ current_health = bm_statistics:get_health(Statistics),
+ active = false
+ }.
+
+-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_active_field() -> non_neg_integer().
+get_active_field () -> #character.active.
diff --git a/src/battlemap/struct/bm_character_turn_data.erl b/src/battlemap/struct/bm_character_turn_data.erl
new file mode 100644
index 0000000..aaf7426
--- /dev/null
+++ b/src/battlemap/struct/bm_character_turn_data.erl
@@ -0,0 +1,102 @@
+-module(bm_character_turn_data).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ type,
+ {
+ dirty :: boolean(),
+ battle :: bm_battle:type(),
+ character :: bm_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
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new (bm_battle:type(), non_neg_integer()) -> type().
+new (Battle, CharacterIX) ->
+ Character = bm_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()) -> bm_battle:type().
+get_battle (Data) -> Data#type.battle.
+
+-spec get_character (type()) -> bm_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 (bm_battle:type(), type()) -> type().
+set_battle (Battle, Data) ->
+ Data#type{ battle = Battle }.
+
+-spec set_character (bm_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 =
+ bm_battle:set_character
+ (
+ Data#type.character_ix,
+ Data#type.character,
+ Data#type.battle
+ )
+ }.
+
diff --git a/src/battlemap/struct/bm_character_turn_request.erl b/src/battlemap/struct/bm_character_turn_request.erl
new file mode 100644
index 0000000..d141d7e
--- /dev/null
+++ b/src/battlemap/struct/bm_character_turn_request.erl
@@ -0,0 +1,84 @@
+-module(bm_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 :: bm_player:id(),
+ session_token :: binary(),
+ battle_id :: binary(),
+ character_ix :: non_neg_integer(),
+ actions :: list(bm_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 = binary_to_integer(maps:get(?CHAR_IX_FIELD, Map)),
+ EncodedActions = maps:get(?ACTIONS_FIELD, Map),
+ Actions = lists:map(fun bm_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()) -> 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(bm_battle_action:type()).
+get_actions (Request) -> Request#type.actions.
diff --git a/src/battlemap/struct/bm_character_turn_update.erl b/src/battlemap/struct/bm_character_turn_update.erl
new file mode 100644
index 0000000..078075c
--- /dev/null
+++ b/src/battlemap/struct/bm_character_turn_update.erl
@@ -0,0 +1,73 @@
+-module(bm_character_turn_update).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record
+(
+ type,
+ {
+ data :: bm_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/2,
+ add_to_db/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new (bm_character_turn_data:type()) -> type().
+new (Data) ->
+ #type
+ {
+ data = Data,
+ timeline = [],
+ db = []
+ }.
+
+-spec get_data (type()) -> bm_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 (bm_character_turn_data:type(), type()) -> type().
+set_data (Data, Update) ->
+ Update#type{ data = Data}.
+
+-spec add_to_timeline (bm_turn_result:type(), type()) -> type().
+add_to_timeline (Item, Update) ->
+ Update#type{ timeline = [bm_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/battlemap/struct/bm_direction.erl b/src/battlemap/struct/bm_direction.erl
new file mode 100644
index 0000000..2da3936
--- /dev/null
+++ b/src/battlemap/struct/bm_direction.erl
@@ -0,0 +1,38 @@
+-module(bm_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/battlemap/struct/bm_location.erl b/src/battlemap/struct/bm_location.erl
new file mode 100644
index 0000000..056d10c
--- /dev/null
+++ b/src/battlemap/struct/bm_location.erl
@@ -0,0 +1,90 @@
+-module(bm_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 (bm_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/battlemap/struct/bm_player.erl b/src/battlemap/struct/bm_player.erl
new file mode 100644
index 0000000..3f14a6f
--- /dev/null
+++ b/src/battlemap/struct/bm_player.erl
@@ -0,0 +1,72 @@
+-module(bm_player).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-type id() :: binary().
+
+-record
+(
+ player,
+ {
+ id :: id(),
+ timeline :: list(any())
+ }
+).
+
+-opaque type() :: #player{}.
+
+-export_type([type/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ get_id/1,
+ get_timeline/1,
+ add_to_timeline/2,
+ reset_timeline/1
+ ]
+).
+
+-export
+(
+ [
+ new/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec get_id (type()) -> id().
+get_id (Player) -> Player#player.id.
+
+-spec get_timeline (type()) -> list(any()).
+get_timeline (Player) -> Player#player.timeline.
+
+-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 (id()) -> type().
+new (ID) ->
+ #player
+ {
+ id = ID,
+ timeline = []
+ }.
+
diff --git a/src/battlemap/struct/bm_player_turn.erl b/src/battlemap/struct/bm_player_turn.erl
new file mode 100644
index 0000000..5f2c074
--- /dev/null
+++ b/src/battlemap/struct/bm_player_turn.erl
@@ -0,0 +1,73 @@
+-module(bm_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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% 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 (non_neg_integer(), type()) -> type().
+next (PlayersCount, CurrentPlayerTurn) ->
+ CurrentPlayerIX = CurrentPlayerTurn#player_turn.player_ix,
+ CurrentTurnNumber = CurrentPlayerTurn#player_turn.number,
+
+ NextPlayerIX = ((CurrentPlayerIX + 1) rem PlayersCount),
+ NextTurnNumber =
+ case NextPlayerIX of
+ 0 -> (CurrentTurnNumber + 1);
+ _ -> CurrentTurnNumber
+ end,
+
+ new(NextTurnNumber, NextPlayerIX).
diff --git a/src/battlemap/struct/bm_tile.erl b/src/battlemap/struct/bm_tile.erl
new file mode 100644
index 0000000..c1dd448
--- /dev/null
+++ b/src/battlemap/struct/bm_tile.erl
@@ -0,0 +1,47 @@
+-module(bm_tile).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-opaque id() :: non_neg_integer().
+-opaque type() :: id().
+
+-export_type([type/0, id/0]).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ get_cost/1,
+ cost_when_oob/0
+ ]
+).
+
+-export
+(
+ [
+ random_id/0
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec cost_when_oob () -> non_neg_integer().
+cost_when_oob () -> 255.
+
+-spec get_cost (id()) -> non_neg_integer().
+get_cost (N) ->
+ if
+ (N =< 200) -> (N + 8);
+ true -> cost_when_oob()
+ end.
+
+-spec random_id () -> id().
+random_id () ->
+ sh_roll:between(0, 15).
diff --git a/src/battlemap/struct/bm_turn_result.erl b/src/battlemap/struct/bm_turn_result.erl
new file mode 100644
index 0000000..c3440bd
--- /dev/null
+++ b/src/battlemap/struct/bm_turn_result.erl
@@ -0,0 +1,142 @@
+-module(bm_turn_result).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+-record
+(
+ switched_weapon,
+ {
+ character_ix :: bm_character:id()
+ }
+).
+
+-record
+(
+ moved,
+ {
+ character_ix :: bm_character:id(),
+ path :: list(bm_direction:enum()),
+ new_location :: bm_location:type()
+ }
+).
+
+-record
+(
+ attacked,
+ {
+ attacker_ix :: bm_character:id(),
+ defender_ix :: bm_character:id(),
+ sequence :: list(bm_attack:type())
+ }
+).
+
+-opaque type() :: (#switched_weapon{} | #moved{} | #attacked{}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export_type([type/0]).
+
+-export
+(
+ [
+ new_character_switched_weapons/1,
+ new_character_moved/3,
+ new_character_attacked/3
+ ]
+).
+
+-export
+(
+ [
+ encode/1
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec new_character_switched_weapons (bm_character:id()) -> type().
+new_character_switched_weapons (CharacterIX) ->
+ #switched_weapon { character_ix = CharacterIX }.
+
+-spec new_character_moved
+ (
+ bm_character:id(),
+ list(bm_direction:enum()),
+ bm_location:type()
+ )
+ -> type().
+new_character_moved (CharacterIX, Path, NewLocation) ->
+ #moved
+ {
+ character_ix = CharacterIX,
+ path = Path,
+ new_location = NewLocation
+ }.
+
+-spec new_character_attacked
+ (
+ bm_character:id(),
+ bm_character:id(),
+ list(bm_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 bm_direction:encode/1, Path),
+ EncodedNewLocation = bm_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 bm_attack:encode/1, Sequence),
+
+ {
+ [
+ {<<"t">>, <<"atk">>},
+ {<<"aix">>, AttackerIX},
+ {<<"dix">>, DefenderIX},
+ {<<"seq">>, EncodedSequence}
+ ]
+ };
+encode (Other) ->
+ io:format("~n invalid encode param\"~p\"~n", [Other]),
+ true = Other.
diff --git a/src/battlemap/struct/bm_weapon.erl b/src/battlemap/struct/bm_weapon.erl
new file mode 100644
index 0000000..6c05799
--- /dev/null
+++ b/src/battlemap/struct/bm_weapon.erl
@@ -0,0 +1,360 @@
+-module(bm_weapon).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-opaque id() :: non_neg_integer().
+
+-type range_type() :: 'ranged' | 'melee'.
+-type range_mod() :: 'long' | 'short'.
+-type damage_type() :: 'slash' | 'pierce' | 'blunt'.
+-type damage_mod() :: 'heavy' | 'light'.
+
+-record
+(
+ weapon,
+ {
+ id :: id(),
+ name :: binary(),
+ range_type :: range_type(),
+ range_mod :: range_mod(),
+ damage_type :: damage_type(),
+ damage_mod :: damage_mod()
+ }
+).
+
+-opaque type() :: #weapon{}.
+
+-export_type([type/0, id/0]).
+-export_type
+(
+ [
+ range_type/0,
+ range_mod/0,
+ damage_type/0,
+ damage_mod/0
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-export
+(
+ [
+ get_id/1,
+ get_range_type/1,
+ get_ranges/1,
+ get_damages/1
+ ]
+).
+
+-export
+(
+ [
+ random_id/0,
+ from_id/1,
+ can_parry/1,
+ apply_to_attributes/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec ranges_of_type
+ (
+ range_type(),
+ range_mod()
+ )
+ -> {non_neg_integer(), non_neg_integer()}.
+ranges_of_type (ranged, long) -> {2, 6};
+ranges_of_type (ranged, short) -> {1, 4};
+ranges_of_type (melee, long) -> {0, 2};
+ranges_of_type (melee, short) -> {0, 1}.
+
+-spec damages_of_type
+ (
+ range_type(),
+ damage_mod()
+ )
+ -> {non_neg_integer(), non_neg_integer()}.
+damages_of_type (ranged, heavy) -> {10, 25};
+damages_of_type (ranged, light) -> {5, 20};
+damages_of_type (melee, heavy) -> {20, 35};
+damages_of_type (melee, light) -> {15, 30}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%% Accessors
+-spec get_id (type()) -> id().
+get_id (Wp) -> Wp#weapon.id.
+
+-spec get_range_type (type()) -> range_type().
+get_range_type (Wp) -> Wp#weapon.range_type.
+
+-spec get_ranges (type()) -> {non_neg_integer(), non_neg_integer()}.
+get_ranges (Wp) ->
+ ranges_of_type(Wp#weapon.range_type, Wp#weapon.range_mod).
+
+-spec get_damages (type()) -> {non_neg_integer(), non_neg_integer()}.
+get_damages (Wp) ->
+ damages_of_type(Wp#weapon.range_type, Wp#weapon.damage_mod).
+
+-spec can_parry (type()) -> boolean().
+can_parry (Wp) -> (Wp#weapon.range_type == melee).
+
+-spec from_id (id()) -> type().
+from_id (0) ->
+ #weapon{
+ id = 0,
+ name = <<"None">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = blunt,
+ damage_mod = light
+ };
+from_id (1) ->
+ #weapon{
+ id = 1,
+ name = <<"Dagger">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = slash,
+ damage_mod = light
+ };
+from_id (2) ->
+ #weapon{
+ id = 2,
+ name = <<"Sword">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = slash,
+ damage_mod = heavy
+ };
+from_id (3) ->
+ #weapon{
+ id = 3,
+ name = <<"Claymore">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = slash,
+ damage_mod = light
+ };
+from_id (4) ->
+ #weapon{
+ id = 4,
+ name = <<"Bardiche">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = slash,
+ damage_mod = heavy
+ };
+from_id (5) ->
+ #weapon{
+ id = 5,
+ name = <<"Stiletto">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = pierce,
+ damage_mod = light
+ };
+from_id (6) ->
+ #weapon{
+ id = 6,
+ name = <<"Pickaxe">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = pierce,
+ damage_mod = heavy
+ };
+from_id (7) ->
+ #weapon{
+ id = 7,
+ name = <<"Rapier">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = pierce,
+ damage_mod = light
+ };
+from_id (8) ->
+ #weapon{
+ id = 8,
+ name = <<"Pike">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = pierce,
+ damage_mod = heavy
+ };
+from_id (9) ->
+ #weapon{
+ id = 9,
+ name = <<"Club">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = blunt,
+ damage_mod = light
+ };
+from_id (10) ->
+ #weapon{
+ id = 10,
+ name = <<"Mace">>,
+ range_type = melee,
+ range_mod = short,
+ damage_type = blunt,
+ damage_mod = heavy
+ };
+from_id (11) ->
+ #weapon{
+ id = 11,
+ name = <<"Staff">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = blunt,
+ damage_mod = light
+ };
+from_id (12) ->
+ #weapon{
+ id = 12,
+ name = <<"War Hammer">>,
+ range_type = melee,
+ range_mod = long,
+ damage_type = blunt,
+ damage_mod = heavy
+ };
+from_id (13) ->
+ #weapon{
+ id = 13,
+ name = <<"Short Bow (Broadhead)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = slash,
+ damage_mod = light
+ };
+from_id (14) ->
+ #weapon{
+ id = 14,
+ name = <<"Short Bow (Blunt)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = blunt,
+ damage_mod = light
+ };
+from_id (15) ->
+ #weapon{
+ id = 15,
+ name = <<"Short Bow (Bodkin Point)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = pierce,
+ damage_mod = light
+ };
+from_id (16) ->
+ #weapon{
+ id = 16,
+ name = <<"Long Bow (Broadhead)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = slash,
+ damage_mod = light
+ };
+from_id (17) ->
+ #weapon{
+ id = 17,
+ name = <<"Long Bow (Blunt)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = blunt,
+ damage_mod = light
+ };
+from_id (18) ->
+ #weapon{
+ id = 18,
+ name = <<"Long Bow (Bodkin Point)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = pierce,
+ damage_mod = light
+ };
+from_id (19) ->
+ #weapon{
+ id = 19,
+ name = <<"Crossbow (Broadhead)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = slash,
+ damage_mod = heavy
+ };
+from_id (20) ->
+ #weapon{
+ id = 20,
+ name = <<"Crossbow (Blunt)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = blunt,
+ damage_mod = heavy
+ };
+from_id (21) ->
+ #weapon{
+ id = 21,
+ name = <<"Crossbow (Bodkin Point)">>,
+ range_type = ranged,
+ range_mod = short,
+ damage_type = pierce,
+ damage_mod = heavy
+ };
+from_id (22) ->
+ #weapon{
+ id = 22,
+ name = <<"Arbalest (Broadhead)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = slash,
+ damage_mod = heavy
+ };
+from_id (23) ->
+ #weapon{
+ id = 23,
+ name = <<"Arbalest (Blunt)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = blunt,
+ damage_mod = heavy
+ };
+from_id (24) ->
+ #weapon{
+ id = 24,
+ name = <<"Arbalest (Bodkin Point)">>,
+ range_type = ranged,
+ range_mod = long,
+ damage_type = pierce,
+ damage_mod = heavy
+ }.
+
+-spec random_id () -> id().
+random_id () -> sh_roll:between(0, 24).
+
+-spec apply_to_attributes
+ (
+ sh_attributes:type(),
+ bm_weapon:type()
+ )
+ -> attributes:type().
+apply_to_attributes (Attributes, Weapon) ->
+ Dexterity = sh_attributes:get_dexterity(Attributes),
+ Speed = sh_attributes:get_speed(Attributes),
+ RangeModifier = Weapon#weapon.range_mod,
+ DamageModifier = Weapon#weapon.damage_mod,
+ WithRangeModifier =
+ case RangeModifier of
+ long ->
+ sh_attributes:set_dexterity(max(0, (Dexterity - 20)), Attributes);
+ _ -> Attributes
+ end,
+ case DamageModifier of
+ heavy -> sh_attributes:set_speed(max(0, (Speed - 20)), WithRangeModifier);
+ _ -> WithRangeModifier
+ end.
+