summaryrefslogtreecommitdiff |
diff options
Diffstat (limited to 'src/battlemap')
30 files changed, 3671 insertions, 0 deletions
diff --git a/src/battlemap/Makefile b/src/battlemap/Makefile new file mode 100644 index 0000000..c5bb150 --- /dev/null +++ b/src/battlemap/Makefile @@ -0,0 +1,72 @@ +## Directories +SRC_DIR ?= src +BIN_DIR ?= ebin +CONF_DIR ?= conf +INCLUDE_DIR ?= include +UNUSED_WWW_DIR ?= www + +YAWS_CONF ?= $(CONF_DIR)/yaws.conf +YAWS_API_HEADER ?= /my/src/yaws/include/yaws_api.hrl + +DIALYZER_PLT_FILE ?= tacticians-server.plt + +## Binaries +YAWS ?= yaws +ERLC ?= erlc +ERLC_OPTS ?= +DIALYZER ?= dialyzer + +################################################################################ +REQUIRED_HEADERS = $(INCLUDE_DIR)/yaws_api.hrl + +SRC_FILES = $(wildcard $(SRC_DIR)/*.erl) +MODULES = $(patsubst %.erl,%,$(SRC_FILES)) +SUB_DIRS = $(filter-out $(MODULES),$(sort $(dir $(wildcard $(SRC_DIR)/*/)))) +BIN_FILES = $(patsubst $(SRC_DIR)/%.erl,$(BIN_DIR)/%.beam,$(SRC_FILES)) + +export +################################################################################ +all: + for subdir in $(SUB_DIRS) ; do \ + echo "Building dir $$subdir" ; \ + $(MAKE) build SRC_DIR=$$subdir || exit 1;\ + done + +debug: $(DIALYZER_PLT_FILE) + $(MAKE) build_debug + $(DIALYZER) --check_plt --plt $(DIALYZER_PLT_FILE) + $(DIALYZER) --get_warnings $(SRC_DIR)/*.erl $(SRC_DIR)/*/*.erl \ + --src --plt $(DIALYZER_PLT_FILE) + +build_debug: + $(MAKE) clean + $(MAKE) ERLC_OPTS=+debug_info + +build: $(BIN_DIR) $(REQUIRED_HEADERS) $(BIN_FILES) + +run: all $(UNUSED_WWW_DIR) + $(YAWS) --conf $(YAWS_CONF) + +clean: + rm -rf $(BIN_DIR)/* + +$(DIALYZER_PLT_FILE): + $(DIALYZER) --build_plt --apps erts kernel stdlib jiffy --output_plt $@ + $(MAKE) build_debug + $(DIALYZER) --add_to_plt --plt $@ -r $(BIN_DIR) + +$(INCLUDE_DIR)/yaws_api.hrl: $(YAWS_API_HEADER) $(INCLUDE_DIR) + cp $< $@ + +$(BIN_DIR): + mkdir -p $@ + +$(UNUSED_WWW_DIR): + mkdir -p $@ + +$(INCLUDE_DIR): + mkdir -p $@ + +.SECONDEXPANSION: +$(BIN_FILES): $(BIN_DIR)/%.beam : $(SRC_DIR)/%.erl $$(wildcard $$(SRC_DIR)/%/.) + $(ERLC) $(ERLC_OPTS) -o $(BIN_DIR) $< diff --git a/src/battlemap/src/battle/battle_turn.erl b/src/battlemap/src/battle/battle_turn.erl new file mode 100644 index 0000000..638e8f9 --- /dev/null +++ b/src/battlemap/src/battle/battle_turn.erl @@ -0,0 +1,161 @@ +-module(battle_turn). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + handle_post_play/1, + store_timeline/2 + ] +). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec activate_relevant_character_instances + ( + list(non_neg_integer()), + array:array(character_instance:struct()), + player:id(), + (-1 | non_neg_integer()) + ) + -> {list(non_neg_integer()), array:array(character_instance:struct())}. +activate_relevant_character_instances (IXs, CharacterInstances, _Owner, -1) -> + {IXs, CharacterInstances}; +activate_relevant_character_instances (IXs, CharacterInstances, Owner, IX) -> + CharacterInstance = array:get(IX, CharacterInstances), + Character = character_instance:get_character(CharacterInstance), + case character:get_owner_id(Character) of + OwnerID when (OwnerID == Owner) -> + activate_relevant_character_instances + ( + [IX|IXs], + array:set + ( + IX, + character_instance:set_is_active(true, CharacterInstance), + CharacterInstances + ), + Owner, + (IX - 1) + ); + + _ -> + activate_relevant_character_instances + ( + IXs, + CharacterInstances, + Owner, + (IX - 1) + ) + end. + +-spec start_next_players_turn (battle:struct()) -> + {list(non_neg_integer()), battle:struct()}. +start_next_players_turn (Battle) -> + Players = battle:get_players(Battle), + PlayerTurn = battle:get_current_player_turn(Battle), + CurrentPlayerIX = player_turn:get_player_ix(PlayerTurn), + CurrentTurnNumber = player_turn:get_number(PlayerTurn), + CharacterInstances = battle:get_character_instances(Battle), + + NextPlayerIX = ((CurrentPlayerIX + 1) rem (array:size(Players))), + NextPlayerTurn = + player_turn:new + ( + case NextPlayerIX of + 0 -> (CurrentTurnNumber + 1); + _ -> CurrentTurnNumber + end, + NextPlayerIX + ), + + NextPlayer = array:get(NextPlayerIX, Players), + UpdatedNextPlayer = player:reset_timeline(NextPlayer), + + {ActivatedCharacterInstanceIXs, UpdatedCharacterInstances} = + activate_relevant_character_instances + ( + [], + CharacterInstances, + player:get_id(NextPlayer), + (array:size(CharacterInstances) - 1) + ), + UpdatedBattle = + battle:set_player + ( + NextPlayerIX, + UpdatedNextPlayer, + battle:set_character_instances + ( + UpdatedCharacterInstances, + battle:set_current_player_turn + ( + NextPlayerTurn, + Battle + ) + ) + ), + % TODO: have a diff operation for the player's timeline being reset. + {ActivatedCharacterInstanceIXs, UpdatedBattle}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec store_timeline + ( + list(any()), + battle:struct() + ) + -> battle:struct(). +store_timeline (TurnEffects, Battle) -> + PlayerTurn = battle:get_current_player_turn(Battle), + PlayerIX = player_turn:get_player_ix(PlayerTurn), + Player = battle:get_player(PlayerIX, Battle), + + UpdatedPlayer = player:add_to_timeline(TurnEffects, Player), + + battle:set_player(PlayerIX, UpdatedPlayer, Battle). + + +-spec handle_post_play (battle:struct()) + -> {database_diff:struct(), battle:struct()}. +handle_post_play (Battle) -> + CharacterInstances = battle:get_character_instances(Battle), + + AnActiveCharacterInstanceRemains = + array:foldl + ( + fun (_IX, CharacterInstance, Prev) -> + (Prev or character_instance:get_is_active(CharacterInstance)) + end, + false, + CharacterInstances + ), + + case AnActiveCharacterInstanceRemains of + true -> + io:format("~nThere are still active characters.~n"), + {[], Battle}; + + false -> + io:format("~nThere are no more active characters.~n"), + {UpdatedCharacterInstanceIXs, UpdatedBattle} = + start_next_players_turn(Battle), + { + lists:map + ( + fun (IX) -> + {set, character_instance, IX, is_active, true} + end, + UpdatedCharacterInstanceIXs + ), + UpdatedBattle + } + end. diff --git a/src/battlemap/src/battle/movement.erl b/src/battlemap/src/battle/movement.erl new file mode 100644 index 0000000..588fad9 --- /dev/null +++ b/src/battlemap/src/battle/movement.erl @@ -0,0 +1,58 @@ +-module(movement). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([cross/4]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec cross + ( + battlemap:struct(), + list(location:type()), + list(direction:enum()), + non_neg_integer(), + location:type() + ) + -> {location:type(), non_neg_integer()}. +cross (_Battlemap, _ForbiddenLocations, [], Cost, Location) -> + {Location, Cost}; +cross (Battlemap, ForbiddenLocations, [Step|NextSteps], Cost, Location) -> + NextLocation = location:apply_direction(Step, Location), + NextTile = battlemap:get_tile_id(NextLocation, Battlemap), + NextCost = (Cost + 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 + ( + battlemap:struct(), + list(location:type()), + list(direction:enum()), + location:type() + ) + -> {location:type(), non_neg_integer()}. +cross (Battlemap, ForbiddenLocations, Path, Location) -> + cross(Battlemap, ForbiddenLocations, Path, 0, Location). diff --git a/src/battlemap/src/battle/roll.erl b/src/battlemap/src/battle/roll.erl new file mode 100644 index 0000000..074054b --- /dev/null +++ b/src/battlemap/src/battle/roll.erl @@ -0,0 +1,32 @@ +-module(roll). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + percentage/0, + between/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec between (non_neg_integer(), non_neg_integer()) -> non_neg_integer(). +between (Min, Max) -> + Diff = (Max - Min), + (Min + (rand:uniform(Diff + 1) - 1)). + +-spec percentage () -> 0..100. +percentage () -> + between(0, 100). diff --git a/src/battlemap/src/handler.erl b/src/battlemap/src/handler.erl new file mode 100644 index 0000000..0ecc8be --- /dev/null +++ b/src/battlemap/src/handler.erl @@ -0,0 +1,23 @@ +-module(handler). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([start/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +start (_YawsParams) -> + {ok, Pid} = timed_caches_manager:start(), + database_shim:generate_db(Pid), + timed_caches_manager:new_cache(Pid, battle_db, none), + ok. diff --git a/src/battlemap/src/io/security.erl b/src/battlemap/src/io/security.erl new file mode 100644 index 0000000..60f6661 --- /dev/null +++ b/src/battlemap/src/io/security.erl @@ -0,0 +1,33 @@ +-module(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/battlemap/src/io/timed_cache.erl b/src/battlemap/src/io/timed_cache.erl new file mode 100644 index 0000000..52b98d6 --- /dev/null +++ b/src/battlemap/src/io/timed_cache.erl @@ -0,0 +1,130 @@ +-module(timed_cache). +-behavior(gen_server). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% 'gen_server' Exports +-export +( + [ + init/1, + handle_cast/2, + handle_call/3, %% No reply will ever be given. + terminate/2, + code_change/3, + format_status/2, + handle_info/2 + ] +). + +%%%% Actual Interface +-export +( + [ + fetch/3, + update/4, + invalidate/3 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec add_to_cache (atom(), any(), any()) -> any(). +add_to_cache (DB, Owner, ObjectID) -> + {ok, TimerPID} = gen_server:start(?MODULE, {DB, {Owner, ObjectID}}, []), + {ok, Data} = database_shim:fetch(DB, ObjectID), + ets:insert(DB, {{Owner, ObjectID}, TimerPID, Data}), + Data. + +-spec add_update_to_cache (atom(), any(), any(), any()) -> 'ok'. +add_update_to_cache (DB, Owner, ObjectID, Data) -> + {ok, TimerPID} = gen_server:start(?MODULE, {DB, {Owner, ObjectID}}, []), + ets:insert(DB, {{Owner, ObjectID}, TimerPID, Data}), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% 'gen_server' functions +init ({DB, ObjectID}) -> + io:format("~nCache entry added: ~p.~n", [{DB, ObjectID}]), + {ok, {DB, ObjectID}, timed_caches_manager:get_timeout()}. + +handle_call (invalidate, _, State) -> + {stop, normal, State}; +handle_call (ping, _, State) -> + {noreply, State, timed_caches_manager:get_timeout()}. + +handle_cast (invalidate, State) -> + {stop, normal, State}; +handle_cast (ping, State) -> + {noreply, State, timed_caches_manager:get_timeout()}. + +terminate (_, {DB, ObjectID}) -> + io:format + ( + "~nCache entry timed out or was invalidated: ~p.~n", + [{DB, ObjectID}] + ), + ets:delete(DB, ObjectID). + +code_change (_, State, _) -> + {ok, State}. + +format_status (_, [_, State]) -> + [{data, [{"State", State}]}]. + +handle_info(timeout, State) -> + {stop, normal, State}; +handle_info(_, {DB, ObjectID}) -> + {noreply, {DB, ObjectID}, timed_caches_manager:get_timeout()}. + +%%%% Interface Functions +-spec fetch (atom(), any(), any()) -> any(). +fetch (DB, Owner, ObjectID) -> + io:format("~nfetch from cache: ~p.~n", [{DB, {Owner, ObjectID}}]), + case ets:lookup(DB, {Owner, ObjectID}) of + [] -> add_to_cache(DB, Owner, ObjectID); + + [{_, TimerPID, Data}] -> + gen_server:cast(TimerPID, ping), + Data + end. + +-spec update (atom(), any(), any(), any()) -> 'ok'. +update (DB, Owner, ObjectID, Data) -> + io:format("~nUpdating cache: ~p.~n", [{DB, {Owner, ObjectID}}]), + case ets:lookup(DB, {Owner, ObjectID}) of + [] -> ok; + + [{_OwnerID, TimerPID, _Data}] -> + gen_server:stop(TimerPID) + end, + add_update_to_cache(DB, Owner, ObjectID, Data). + +-spec invalidate (atom(), any(), any()) -> 'ok'. +invalidate (DB, Owner, ObjectID) -> + case ets:lookup(DB, {Owner, ObjectID}) of + [] -> + io:format + ( + "~nInvalidation request on non-stored entry: ~p.~n", + [{DB, Owner, ObjectID}] + ), + ok; + + [{_, TimerPID, _}] -> + io:format + ( + "~nInvalidation request on stored entry: ~p.~n", + [{DB, Owner, ObjectID}] + ), + gen_server:stop(TimerPID), + ok + end. diff --git a/src/battlemap/src/io/timed_caches_manager.erl b/src/battlemap/src/io/timed_caches_manager.erl new file mode 100644 index 0000000..5901964 --- /dev/null +++ b/src/battlemap/src/io/timed_caches_manager.erl @@ -0,0 +1,152 @@ +-module(timed_caches_manager). +-behavior(gen_server). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% 'gen_server' Exports +-export( + [ + init/1, + handle_cast/2, + handle_call/3, + terminate/2, + code_change/3, + format_status/2, + handle_info/2 + ] +). + +%%%% Actual Interface +-export( + [ + start/0, + new_cache/3, + delete_cache/2, + get_timeout/0 + ] +) +. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +remove_cache (DB) -> + ets:delete(DB). + +add_cache (DB, none) -> + io:format("~nTimed Caches Manager added a new cache. ~n"), + ets:new( + DB, + [ + set, + public, + named_table, + {keypos, 1}, + {read_concurrency, true}, + {heir, none} + ] + ); +add_cache (DB, Heir) -> + io:format("~nTimed Caches Manager added a new cache. ~n"), + ets:new( + DB, + [ + set, + public, + named_table, + {keypos, 1}, + {read_concurrency, true}, + {heir, Heir, DB} + ] + ). + +inherit_cache (CacheList, DB, Heir) -> + case lists:member(DB, CacheList) of + true -> + ets:setopts(DB, {heir, Heir, DB}), + CacheList; + + false -> + [DB|CacheList] + end. + +remove_cache (CacheList, DB) -> + case lists:member(DB, CacheList) of + true -> + remove_cache(DB), + lists:delete(DB, CacheList); + false -> + CacheList + end. + +add_cache (CacheList, DB, Heir) -> + case lists:member(DB, CacheList) of + true when (Heir =:= none) -> + CacheList; + + true -> + ets:setopts(DB, {heir, Heir, DB}), + CacheList; + + false -> + add_cache(DB, Heir), + [DB|CacheList] + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% 'gen_server' functions +init (CacheList) -> + io:format("~nStarting Timed Caches Manager..."), + {ok, CacheList}. + +handle_call ({remove, CacheName}, _Caller, State) -> + {noreply, remove_cache(State, CacheName)}; +handle_call ({add, CacheName, Heir}, _Caller, State)-> + {noreply, add_cache(State, CacheName, Heir)}; +handle_call ({inherit, CacheName, Heir}, _Caller, State)-> + {noreply, inherit_cache(State, CacheName, Heir)}; +handle_call (terminate, _, State) -> + {stop, normal, State}. + +handle_cast ({remove, CacheName}, State) -> + {noreply, remove_cache(State, CacheName)}; +handle_cast ({add, CacheName, Heir}, State)-> + {noreply, add_cache(State, CacheName, Heir)}; +handle_cast ({inherit, CacheName, Heir}, State)-> + {noreply, inherit_cache(State, CacheName, Heir)}; +handle_cast (terminate, State) -> + {stop, normal, State}. + +terminate (_Reason, []) -> + ok; +terminate (Reason, [CacheName|OtherCaches]) -> + remove_cache(CacheName), + terminate(Reason, OtherCaches). + +code_change (_, State, _) -> + {ok, State}. + +format_status (_, [_, State]) -> + [{data, [{"State", State}]}]. + +handle_info(_, State) -> + {noreply, State}. + +%%%% Interface Functions +start () -> + gen_server:start(timed_caches_manager, [], []). + +new_cache (ManagerPid, DB, Heir) -> + gen_server:cast(ManagerPid, {add, DB, Heir}). + +delete_cache (ManagerPid, DB) -> + gen_server:cast(ManagerPid, {remove, DB}). + +get_timeout () -> + 120000. % 2min. diff --git a/src/battlemap/src/query/character_turn.erl b/src/battlemap/src/query/character_turn.erl new file mode 100644 index 0000000..85c5db8 --- /dev/null +++ b/src/battlemap/src/query/character_turn.erl @@ -0,0 +1,255 @@ +-module(character_turn). +% FIXME: There's still too much of a mess in this module. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-include("../../include/yaws_api.hrl"). + +-record +( + input, + { + player_id :: player:id(), + session_token :: binary(), + battle_id :: binary(), + character_instance_ix :: non_neg_integer(), + actions :: list(battle_action:struct()) + } +). + +-record +( + relevant_data, + { + battle :: battle:struct(), + played_character_instance :: character_instance:struct() + } +). + +-type input() :: #input{}. +-type relevant_data() :: #relevant_data{}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([out/1]). + +-export_type([relevant_data/0]). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec parse_input (binary()) -> input(). +parse_input (Req) -> + JSONReqMap = jiffy:decode(Req, [return_maps]), + CharacterInstanceIX = binary_to_integer(maps:get(<<"cix">>, JSONReqMap)), + EncodedActions = maps:get(<<"act">>, JSONReqMap), + Actions = lists:map(fun battle_action:decode/1, EncodedActions), + + #input + { + player_id = maps:get(<<"pid">>, JSONReqMap), + session_token = maps:get(<<"stk">>, JSONReqMap), + battle_id = maps:get(<<"bid">>, JSONReqMap), + character_instance_ix = CharacterInstanceIX, + actions = Actions + }. + +-spec fetch_relevant_data (input()) -> relevant_data(). +fetch_relevant_data (Input) -> + PlayerID = Input#input.player_id, + BattleID = Input#input.battle_id, + CharacterInstanceIX = Input#input.character_instance_ix, + + Battle = timed_cache:fetch(battle_db, PlayerID, BattleID), + CharacterInstance = + battle:get_character_instance(CharacterInstanceIX, Battle), + + #relevant_data + { + battle = Battle, + played_character_instance = CharacterInstance + }. + +-spec assert_character_instance_can_be_played + ( + relevant_data(), + input() + ) + -> true. +assert_character_instance_can_be_played (RData, Input) -> + PlayerID = Input#input.player_id, + CharacterInstance = RData#relevant_data.played_character_instance, + Battle = RData#relevant_data.battle, + Character = character_instance:get_character(CharacterInstance), + CurrentPlayerIX = + player_turn:get_player_ix + ( + battle:get_current_player_turn(Battle) + ), + CurrentPlayer = battle:get_player(CurrentPlayerIX, Battle), + CharacterOwnerID = character:get_owner_id(Character), + + PlayerID = player:get_id(CurrentPlayer), + PlayerID = CharacterOwnerID, + + true = character_instance:get_is_active(CharacterInstance). + +-spec finalize_and_fuse_relevant_data + ( + relevant_data(), + input() + ) + -> battle:struct(). +finalize_and_fuse_relevant_data (RData, Input) -> + Battle = RData#relevant_data.battle, + CharacterInstance = RData#relevant_data.played_character_instance, + + io:format("~nNot a character instance? ~p~n", [CharacterInstance]), + + FinalizedCharacterInstance = + character_instance:set_is_active(false, CharacterInstance), + + battle:set_character_instance + ( + Input#input.character_instance_ix, + FinalizedCharacterInstance, + Battle + ). + +%-spec send_to_database (list(database_diff:struct()), input()) -> 'ok'. +-spec send_to_database (battle:struct(), input()) -> 'ok'. +send_to_database (FinalizedBattle, Input) -> + PlayerID = Input#input.player_id, + BattleID = Input#input.battle_id, + + %% TODO: differential commit + database_shim:commit + ( + battle_db, + PlayerID, + BattleID, + FinalizedBattle + ). + +-spec update_cache (battle:struct(), input()) -> 'ok'. +update_cache (Battle, Input) -> + PlayerID = Input#input.player_id, + BattleID = Input#input.battle_id, + + timed_cache:update + ( + battle_db, + PlayerID, + BattleID, + Battle + ). + +-spec generate_reply ( list(any())) -> binary(). +generate_reply (EncodedClientUpdate) -> + jiffy:encode([turn_results:generate(EncodedClientUpdate)]). + +handle_actions (RData, Input) -> + Battle = RData#relevant_data.battle, + CharacterInstance = RData#relevant_data.played_character_instance, + CharacterInstanceIX = Input#input.character_instance_ix, + Actions = Input#input.actions, + + { + ActionsDiffUpdates, + ClientUpdates, + PostActionBattle, + PostActionCharacterInstance + } = + lists:foldl + ( + fun + ( + Action, + { + CurrActionsDiffUpdates, + CurrClientUpdates, + CurrBattle, + CurrCharacterInstance + } + ) -> + { + NewActionsDiffUpdates, + NewClientUpdates, + NewBattle, + NewCharacterInstance + } = + battle_action:handle + ( + CurrBattle, + CurrCharacterInstance, + CharacterInstanceIX, + Action + ), + { + (NewActionsDiffUpdates ++ CurrActionsDiffUpdates), + (NewClientUpdates ++ CurrClientUpdates), + NewBattle, + NewCharacterInstance + } + end, + {[], [], Battle, CharacterInstance}, + Actions + ), + { + ActionsDiffUpdates, + ClientUpdates, + RData#relevant_data + { + battle = PostActionBattle, + played_character_instance = PostActionCharacterInstance + } + }. + +-spec handle (binary()) -> binary(). +handle (Req) -> + Input = parse_input(Req), + PlayerID = Input#input.player_id, + PlayerSessionToken = Input#input.session_token, + + security:assert_identity(PlayerID, PlayerSessionToken), + security:lock_queries(PlayerID), + + RData = fetch_relevant_data(Input), + + assert_character_instance_can_be_played(RData, Input), + + {ActionsDiffUpdate, ClientUpdate, UpdatedRData} = + handle_actions(RData, Input), + + EncodedClientUpdate = lists:map(fun turn_result:encode/1, ClientUpdate), + + UpdatedBattle = finalize_and_fuse_relevant_data(UpdatedRData, Input), + + UpdatedBattle2 = + battle_turn:store_timeline(EncodedClientUpdate, UpdatedBattle), + + {TurnDiffUpdate, FinalizedBattle} = + battle_turn:handle_post_play(UpdatedBattle2), + + DiffUpdate = (TurnDiffUpdate ++ ActionsDiffUpdate), + + %send_to_database(DiffUpdate, Input), + send_to_database(FinalizedBattle, Input), + update_cache(FinalizedBattle, Input), + + io:format("~nCharacter turn result:~n~p~n", [DiffUpdate]), + + security:unlock_queries(PlayerID), + + generate_reply(EncodedClientUpdate). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +out(A) -> + { + content, + "application/json; charset=UTF-8", + handle(A#arg.clidata) + }. diff --git a/src/battlemap/src/query/load_state.erl b/src/battlemap/src/query/load_state.erl new file mode 100644 index 0000000..a03a20f --- /dev/null +++ b/src/battlemap/src/query/load_state.erl @@ -0,0 +1,110 @@ +-module(load_state). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-include("../../include/yaws_api.hrl"). + +-record +( + input, + { + player_id :: player:id(), + session_token :: binary(), + battle_id :: binary() + } +). + +-record +( + query_state, + { + battle :: battle:struct() + } +). + +-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(<<"bmi">>, 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 = + 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, + + jiffy:encode + ( + [ + set_timeline:generate(battle:get_encoded_last_turns_effects(Battle)), + set_map:generate(battle:get_battlemap(Battle)) + | + array:sparse_to_list + ( + array:map + ( + fun (IX, CharacterInstance) -> + add_char:generate(IX, CharacterInstance, PlayerID) + end, + battle:get_character_instances(Battle) + ) + ) + ] + ). + +-spec handle (binary()) -> binary(). +handle (Req) -> + Input = parse_input(Req), + security:assert_identity(Input#input.player_id, Input#input.session_token), + security:lock_queries(Input#input.player_id), + QueryState = fetch_data(Input), + 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/battlemap/src/reply/add_char.erl b/src/battlemap/src/reply/add_char.erl new file mode 100644 index 0000000..b3ef128 --- /dev/null +++ b/src/battlemap/src/reply/add_char.erl @@ -0,0 +1,74 @@ +-module(add_char). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/3]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec attributes_as_json + ( + attributes:struct() + ) -> + {list({binary(), non_neg_integer()})}. +attributes_as_json (Attributes) -> + { + [ + {<<"con">>, attributes:get_constitution(Attributes)}, + {<<"dex">>, attributes:get_dexterity(Attributes)}, + {<<"int">>, attributes:get_intelligence(Attributes)}, + {<<"min">>, attributes:get_mind(Attributes)}, + {<<"spe">>, attributes:get_speed(Attributes)}, + {<<"str">>, attributes:get_strength(Attributes)} + ] + }. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate + ( + non_neg_integer(), + character_instance:struct(), + player:id() + ) + -> {list(any())}. +generate (IX, CharacterInstance, PlayerID) -> + Character = character_instance:get_character(CharacterInstance), + Location = character_instance:get_location(CharacterInstance), + Attributes = character:get_attributes(Character), + {ActiveWeapon, SecondaryWeapon} = character:get_weapon_ids(Character), + OwnerID = character:get_owner_id(Character), + + { + [ + {<<"msg">>, <<"add_char">>}, + {<<"ix">>, IX}, + {<<"nam">>, character:get_name(Character)}, + {<<"ico">>, character:get_icon(Character)}, + {<<"prt">>, character:get_portrait(Character)}, + { + <<"hea">>, + character_instance:get_current_health(CharacterInstance) + }, + {<<"lc">>, location:encode(Location)}, + {<<"pla">>, OwnerID}, + { + <<"ena">>, + ( + character_instance:get_is_active(CharacterInstance) + and + (OwnerID == PlayerID) + ) + }, + {<<"att">>, attributes_as_json(Attributes)}, + {<<"awp">>, ActiveWeapon}, + {<<"swp">>, SecondaryWeapon} + ] + }. diff --git a/src/battlemap/src/reply/set_map.erl b/src/battlemap/src/reply/set_map.erl new file mode 100644 index 0000000..6a7cd39 --- /dev/null +++ b/src/battlemap/src/reply/set_map.erl @@ -0,0 +1,28 @@ +-module(set_map). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export([generate/1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate (battlemap:struct()) -> {list(any())}. +generate (Battlemap) -> + { + [ + {<<"msg">>, <<"set_map">>}, + {<<"w">>, battlemap:get_width(Battlemap)}, + {<<"h">>, battlemap:get_height(Battlemap)}, + {<<"t">>, array:sparse_to_list(battlemap:get_tile_ids(Battlemap))} + ] + }. diff --git a/src/battlemap/src/reply/set_timeline.erl b/src/battlemap/src/reply/set_timeline.erl new file mode 100644 index 0000000..bfe621a --- /dev/null +++ b/src/battlemap/src/reply/set_timeline.erl @@ -0,0 +1,27 @@ +-module(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/battlemap/src/reply/turn_results.erl b/src/battlemap/src/reply/turn_results.erl new file mode 100644 index 0000000..0f3ff25 --- /dev/null +++ b/src/battlemap/src/reply/turn_results.erl @@ -0,0 +1,27 @@ +-module(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/battlemap/src/shim/database_shim.erl b/src/battlemap/src/shim/database_shim.erl new file mode 100644 index 0000000..a26087d --- /dev/null +++ b/src/battlemap/src/shim/database_shim.erl @@ -0,0 +1,137 @@ +-module(database_shim). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + generate_db/1, + fetch/2, + commit/4 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec create_db (pid()) -> 'ok'. +create_db (_Heir) -> + ets:new + ( + db_shim, + [ + set, + public, + named_table, + {keypos, 1}, + {read_concurrency, true} + ] + ), + io:format("~ndb_shim ets created.~n"), + ok. + +-spec add_to_db (any(), any()) -> 'ok'. +add_to_db (ID, Val) -> + io:format("~nadd to db_shim: ~p.~n", [{ID, Val}]), + ets:insert(db_shim, {ID, Val}), + ok. + +-spec generate_random_characters + ( + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + non_neg_integer(), + list(character:struct()) + ) + -> list(character:struct()). +generate_random_characters +( + 0, + 0, + _CharactersPerPlayer, + _TotalCharacterCount, + Result +) -> + Result; +generate_random_characters +( + MaxPlayerID, + 0, + CharactersPerPlayer, + TotalCharacterCount, + Result +) -> + generate_random_characters + ( + (MaxPlayerID - 1), + CharactersPerPlayer, + CharactersPerPlayer, + TotalCharacterCount, + Result + ); +generate_random_characters +( + MaxPlayerID, + PlayerCharacterCount, + CharactersPerPlayer, + TotalCharacterCount, + Result +) -> + NewCharacter = + character:random + ( + TotalCharacterCount, + list_to_binary(integer_to_list(MaxPlayerID)) + ), + generate_random_characters + ( + MaxPlayerID, + (PlayerCharacterCount - 1), + CharactersPerPlayer, + (TotalCharacterCount + 1), + [NewCharacter|Result] + ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec generate_db (pid()) -> 'ok'. +generate_db (Heir) -> + Pid = self(), + spawn(fun () -> create_db(Heir), Pid ! ok, receive ok -> ok end end), + receive + ok -> ok + end, + BattlemapWidth = roll:between(16, 64), + BattlemapHeight = roll:between(16, 64), + Battlemap = battlemap:random(0, BattlemapWidth, BattlemapHeight), + Characters = generate_random_characters(1, 8, 8, 0, []), + PlayersAsList = [player:new(<<"0">>), player:new(<<"1">>)], + Battle = + battle:random + ( + <<"0">>, + PlayersAsList, + Battlemap, + Characters + ), + + add_to_db({battle_db, <<"0">>}, Battle). + +-spec fetch (atom(), any()) -> ({'ok', any()} | 'nothing'). +fetch (DB, ObjectID) -> + io:format("~ndb_shim lookup: ~p.~n", [{DB, ObjectID}]), + case ets:lookup(db_shim, {DB, ObjectID}) of + [{_Key, Value}] -> {ok, Value}; + [] -> nothing + end. + +-spec commit (atom(), any(), any(), any()) -> 'ok'. +commit (DB, _Owner, ObjectID, Value) -> + add_to_db({DB, ObjectID}, Value). diff --git a/src/battlemap/src/struct/attack.erl b/src/battlemap/src/struct/attack.erl new file mode 100644 index 0000000..71bc2bb --- /dev/null +++ b/src/battlemap/src/struct/attack.erl @@ -0,0 +1,299 @@ +-module(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 struct() :: #attack{}. +-type maybe_struct() :: ('nothing' | struct()). +-opaque step() :: {order(), boolean()}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export_type([struct/0, maybe_struct/0, step/0]). + +-export +( + [ + get_sequence/3, + get_description_of/3, + apply_to_healths/3 + ] +). + +-export +( + [ + encode/1 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec roll_precision + ( + statistics:struct(), + statistics:struct() + ) + -> precision(). +roll_precision (AttackerStatistics, DefenderStatistics) -> + DefenderDodges = statistics:get_dodges(DefenderStatistics), + AttackerAccuracy = statistics:get_accuracy(AttackerStatistics), + MissChance = max(0, (DefenderDodges - AttackerAccuracy)), + case roll:percentage() of + X when (X =< MissChance) -> misses; + X when (X =< (MissChance * 2)) -> grazes; + _ -> hits + end. + +-spec roll_damage + ( + statistics:struct(), + statistics:struct() + ) + -> {non_neg_integer(), boolean()}. +roll_damage (AttackerStatistics, _DefenderStatistics) -> + {MinimumDamage, MaximumDamage} = statistics:get_damages(AttackerStatistics), + MaximumRoll = max(1, MaximumDamage - MinimumDamage), + BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1), + CriticalHitChance = statistics:get_critical_hits(AttackerStatistics), + case roll:percentage() of + X when (X =< CriticalHitChance) -> {(BaseDamage * 2), true}; + _ -> {BaseDamage, false} + end. + +-spec roll_parry (statistics:struct()) -> boolean(). +roll_parry (DefenderStatistics) -> + DefenderParryChance = statistics:get_parries(DefenderStatistics), + (roll:percentage() =< DefenderParryChance). + +-spec effect_of_attack + ( + order(), + statistics:struct(), + statistics:struct(), + boolean() + ) + -> struct(). +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(), + statistics:struct(), + statistics:struct() + ) + -> maybe_struct(). +get_description_of +( + {first, CanParry}, + AttackerStatistics, + DefenderStatistics +) -> + effect_of_attack(first, AttackerStatistics, DefenderStatistics, CanParry); +get_description_of +( + {second, CanParry}, + AttackerStatistics, + DefenderStatistics +) -> + AttackerDoubleAttackChange = statistics:get_double_hits(AttackerStatistics), + + case 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_struct(), + non_neg_integer(), + non_neg_integer() + ) + -> {maybe_struct(), 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(), + weapon:struct(), + weapon:struct() + ) + -> list(step()). +get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) -> + {AttackerDefenseRange, AttackerAttackRange} = + weapon:get_ranges(AttackerWeapon), + {DefenderDefenseRange, DefenderAttackRange} = + 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 (struct()) -> {list(any())}. +% This shouldn't be a possibility. Types in this module are a mess... +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/src/struct/attributes.erl b/src/battlemap/src/struct/attributes.erl new file mode 100644 index 0000000..6728831 --- /dev/null +++ b/src/battlemap/src/struct/attributes.erl @@ -0,0 +1,108 @@ +-module(attributes). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( + attributes, + { + constitution :: integer(), + dexterity :: integer(), + intelligence :: integer(), + mind :: integer(), + speed :: integer(), + strength :: integer() + } +). + +-opaque struct() :: #attributes{}. + +-export_type([struct/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( + [ + get_constitution/1, + get_dexterity/1, + get_intelligence/1, + get_mind/1, + get_speed/1, + get_strength/1, + + set_constitution/2, + set_dexterity/2, + set_intelligence/2, + set_mind/2, + set_speed/2, + set_strength/2 + ] +). + +%%%% Accessors +-export +( + [ + random/0 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_constitution (struct()) -> integer(). +get_constitution (Att) -> Att#attributes.constitution. + +-spec get_dexterity (struct()) -> integer(). +get_dexterity (Att) -> Att#attributes.dexterity. + +-spec get_intelligence (struct()) -> integer(). +get_intelligence (Att) -> Att#attributes.intelligence. + +-spec get_mind (struct()) -> integer(). +get_mind (Att) -> Att#attributes.mind. + +-spec get_speed (struct()) -> integer(). +get_speed (Att) -> Att#attributes.speed. + +-spec get_strength (struct()) -> integer(). +get_strength (Att) -> Att#attributes.strength. + +-spec set_constitution (integer(), struct()) -> struct(). +set_constitution (Val, Att) -> Att#attributes{ constitution = Val }. + +-spec set_dexterity (integer(), struct()) -> struct(). +set_dexterity (Val, Att) -> Att#attributes{ dexterity = Val }. + +-spec set_intelligence (integer(), struct()) -> struct(). +set_intelligence (Val, Att) -> Att#attributes{ intelligence = Val }. + +-spec set_mind (integer(), struct()) -> struct(). +set_mind (Val, Att) -> Att#attributes{ mind = Val }. + +-spec set_speed (integer(), struct()) -> struct(). +set_speed (Val, Att) -> Att#attributes{ speed = Val }. + +-spec set_strength (integer(), struct()) -> struct(). +set_strength (Val, Att) -> Att#attributes{ strength = Val }. + +-spec random () -> struct(). +random () -> + #attributes + { + constitution = roll:percentage(), + dexterity = roll:percentage(), + intelligence = roll:percentage(), + mind = roll:percentage(), + speed = roll:percentage(), + strength = roll:percentage() + }. diff --git a/src/battlemap/src/struct/battle.erl b/src/battlemap/src/struct/battle.erl new file mode 100644 index 0000000..5ac12e4 --- /dev/null +++ b/src/battlemap/src/struct/battle.erl @@ -0,0 +1,250 @@ +-module(battle). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( + battle, + { + id :: id(), + battlemap :: battlemap:struct(), + character_instances :: array:array(character_instance:struct()), + players :: array:array(player:struct()), + current_player_turn :: player_turn:struct() + } +). + +-opaque struct() :: #battle{}. + +-export_type([struct/0, id/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( + [ + get_id/1, + get_battlemap/1, + get_character_instances/1, + get_character_instance/2, + get_players/1, + get_player/2, + get_current_player_turn/1, + get_encoded_last_turns_effects/1, + + set_battlemap/2, + set_character_instances/2, + set_character_instance/3, + set_players/2, + set_player/3, + set_current_player_turn/2 + ] +). + +-export +( + [ + random/4 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +get_all_timelines (Result, CurrentIndex, EndPoint, ArraySize, Players) -> + Player = array:get(CurrentIndex, Players), + Timeline = 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 (struct()) -> id(). +get_id (Battle) -> Battle#battle.id. + +-spec get_battlemap (struct()) -> battlemap:struct(). +get_battlemap (Battle) -> + Battle#battle.battlemap. + +-spec get_character_instances (struct()) -> + array:array(character_instance:struct()). +get_character_instances (Battle) -> + Battle#battle.character_instances. + +-spec get_character_instance (non_neg_integer(), struct()) -> + character_instance:struct(). +get_character_instance (IX, Battle) -> + array:get(IX, Battle#battle.character_instances). + +-spec get_players (struct()) -> array:array(player:struct()). +get_players (Battle) -> + Battle#battle.players. + +-spec get_player (non_neg_integer(), struct()) -> player:struct(). +get_player (IX, Battle) -> + array:get(IX, Battle#battle.players). + +-spec get_current_player_turn (struct()) -> player_turn:struct(). +get_current_player_turn (Battle) -> + Battle#battle.current_player_turn. + +-spec get_encoded_last_turns_effects (struct()) -> list(any()). +get_encoded_last_turns_effects (Battle) -> + CurrentPlayerTurn = Battle#battle.current_player_turn, + Players = Battle#battle.players, + CurrentPlayerIX = 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 (battlemap:struct(), struct()) -> struct(). +set_battlemap (Battlemap, Battle) -> + Battle#battle + { + battlemap = Battlemap + }. + +-spec set_character_instances + ( + array:array(character_instance:struct()), + struct() + ) + -> struct(). +set_character_instances (CharacterInstances, Battle) -> + Battle#battle + { + character_instances = CharacterInstances + }. + +-spec set_character_instance + ( + non_neg_integer(), + character_instance:struct(), + struct() + ) + -> struct(). +set_character_instance (IX, CharacterInstance, Battle) -> + Battle#battle + { + character_instances = + array:set + ( + IX, + CharacterInstance, + Battle#battle.character_instances + ) + }. + +-spec set_players + ( + array:array(player:struct()), + struct() + ) + -> struct(). +set_players (Players, Battle) -> + Battle#battle + { + players = Players + }. + +-spec set_player + ( + non_neg_integer(), + player:struct(), + struct() + ) + -> struct(). +set_player (IX, Player, Battle) -> + Battle#battle + { + players = + array:set + ( + IX, + Player, + Battle#battle.players + ) + }. + +-spec set_current_player_turn + ( + player_turn:struct(), + struct() + ) + -> struct(). +set_current_player_turn (PlayerTurn, Battle) -> + Battle#battle + { + current_player_turn = PlayerTurn + }. + +-spec random + ( + id(), + list(player:struct()), + battlemap:struct(), + list(character:struct()) + ) + -> struct(). +random (ID, PlayersAsList, Battlemap, Characters) -> + BattlemapWidth = battlemap:get_width(Battlemap), + BattlemapHeight = battlemap:get_height(Battlemap), + {CharacterInstancesAsList, _ForbiddenLocations} = + lists:mapfoldl + ( + fun (Character, ForbiddenLocations) -> + CharacterOwner = character:get_owner_id(Character), + NewCharacterInstance = + character_instance:random + ( + Character, + BattlemapWidth, + BattlemapHeight, + ForbiddenLocations + ), + NewCharacterInstanceActive = + case CharacterOwner of + <<"0">> -> + character_instance:set_is_active + ( + true, + NewCharacterInstance + ); + + _ -> + NewCharacterInstance + end, + NewCharacterInstanceLocation = + character_instance:get_location(NewCharacterInstanceActive), + { + NewCharacterInstanceActive, + [NewCharacterInstanceLocation|ForbiddenLocations] + } + end, + [], + Characters + ), + + #battle + { + id = ID, + battlemap = Battlemap, + character_instances = array:from_list(CharacterInstancesAsList), + players = array:from_list(PlayersAsList), + current_player_turn = player_turn:new(0, 0) + }. diff --git a/src/battlemap/src/struct/battle_action.erl b/src/battlemap/src/struct/battle_action.erl new file mode 100644 index 0000000..8aaaef9 --- /dev/null +++ b/src/battlemap/src/struct/battle_action.erl @@ -0,0 +1,300 @@ +-module(battle_action). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( + move, + { + path :: list(direction:enum()) + } +). + +-record +( + switch_weapon, + { + } +). + +-record +( + attack, + { + target_ix :: non_neg_integer() + } +). + +-type category() :: ('move' | 'switch_weapon' | 'attack' | 'nothing'). +-opaque struct() :: (#move{} | #switch_weapon{} | #attack{}). + +-export_type([category/0, struct/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + decode/1, + handle/4, + can_follow/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode_mov_action (map()) -> struct(). +decode_mov_action (JSONMap) -> + PathInBinary = maps:get(<<"p">>, JSONMap), + Path = lists:map(fun direction:decode/1, PathInBinary), + + #move { path = Path }. + +-spec decode_atk_action (map()) -> struct(). +decode_atk_action (JSONMap) -> + TargetIX = binary_to_integer(maps:get(<<"tix">>, JSONMap)), + + #attack { target_ix = TargetIX }. + +-spec decode_swp_action (map()) -> struct(). +decode_swp_action (_JSONMap) -> + #switch_weapon{}. + +-spec handle_attack_sequence + ( + character_instance:struct(), + character_instance:struct(), + list(attack:step()) + ) + -> {list(attack:struct()), non_neg_integer(), non_neg_integer()}. +handle_attack_sequence +( + CharacterInstance, + TargetCharacterInstance, + AttackSequence +) -> + Character = character_instance:get_character(CharacterInstance), + TargetCharacter = character_instance:get_character(TargetCharacterInstance), + CharacterStatistics = character:get_statistics(Character), + TargetCharacterStatistics = character:get_statistics(TargetCharacter), + + AttackPlannedEffects = + lists:map + ( + fun (AttackStep) -> + attack:get_description_of + ( + AttackStep, + CharacterStatistics, + TargetCharacterStatistics + ) + end, + AttackSequence + ), + + lists:foldl + ( + fun + ( + AttackEffectCandidate, + {AttackValidEffects, AttackerHealth, DefenderHealth} + ) -> + {AttackResult, NewAttackerHealth, NewDefenderHealth} = + attack:apply_to_healths + ( + AttackEffectCandidate, + AttackerHealth, + DefenderHealth + ), + case AttackResult of + nothing -> {AttackValidEffects, AttackerHealth, DefenderHealth}; + _ -> + { + (AttackValidEffects ++ [AttackResult]), + NewAttackerHealth, + NewDefenderHealth + } + end + end, + { + [], + character_instance:get_current_health(CharacterInstance), + character_instance:get_current_health(TargetCharacterInstance) + }, + AttackPlannedEffects + ). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec decode (map()) -> struct(). +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 handle +( + battle:struct(), + character_instance:struct(), + non_neg_integer(), + struct() +) +-> +{ + list(database_diff:struct()), + list(turn_result:struct()), + battle:struct(), + character_instance:struct() +}. +handle (Battle, CharacterInstance, CharacterInstanceIX, BattleAction) +when is_record(BattleAction, switch_weapon) -> + Character = character_instance:get_character(CharacterInstance), + CharacterAttributes = character:get_attributes(Character), + {PrimaryWeaponID, SecondaryWeaponID} = character:get_weapon_ids(Character), + + UpdatedWeaponIDs = {SecondaryWeaponID, PrimaryWeaponID}, + UpdatedCharacterStatistics = + statistics:new(CharacterAttributes, UpdatedWeaponIDs), + UpdatedCharacter = + character:set_statistics + ( + UpdatedCharacterStatistics, + character:set_weapon_ids(UpdatedWeaponIDs, Character) + ), + UpdatedCharacterInstance = + character_instance:set_character(UpdatedCharacter, CharacterInstance), + + { + % TODO: hide that into database_diff structs. + [ + {character_instance, CharacterInstanceIX, wp0, SecondaryWeaponID}, + {character_instance, CharacterInstanceIX, wp1, PrimaryWeaponID} + % ... statistics as well. + ], + [turn_result:new_character_switched_weapons(CharacterInstanceIX)], + Battle, + UpdatedCharacterInstance + }; +handle (Battle, CharacterInstance, CharacterInstanceIX, BattleAction) +when is_record(BattleAction, move) -> + Character = character_instance:get_character(CharacterInstance), + CharacterStatistics = character:get_statistics(Character), + Battlemap = battle:get_battlemap(Battle), + Path = BattleAction#move.path, + CharacterMovementPoints = + statistics:get_movement_points(CharacterStatistics), + + ForbiddenLocations = + array:foldl + ( + fun (IX, CharInst, Prev) -> + case IX of + CharacterInstanceIX -> Prev; + _ -> [character_instance:get_location(CharInst)|Prev] + end + end, + [], + battle:get_character_instances(Battle) + ), + + {NewLocation, Cost} = + movement:cross + ( + Battlemap, + ForbiddenLocations, + Path, + character_instance:get_location(CharacterInstance) + ), + + true = (Cost =< CharacterMovementPoints), + + UpdatedCharacterInstance = + character_instance:set_location(NewLocation, CharacterInstance), + + { + % TODO: hide that into database_diff structs. + [{character_instance, CharacterInstanceIX, loc, NewLocation}], + % TODO: hide that into turn_result structs. + [turn_result:new_character_moved(CharacterInstanceIX, Path, NewLocation)], + Battle, + UpdatedCharacterInstance + }; +handle (Battle, CharacterInstance, CharacterInstanceIX, BattleAction) +when is_record(BattleAction, attack) -> + Character = character_instance:get_character(CharacterInstance), + TargetIX = BattleAction#attack.target_ix, + TargetCharacterInstance = battle:get_character_instance(TargetIX, Battle), + TargetCharacter = character_instance:get_character(TargetCharacterInstance), + + Range = + location:dist + ( + character_instance:get_location(CharacterInstance), + character_instance:get_location(TargetCharacterInstance) + ), + + {AttackingWeaponID, _} = character:get_weapon_ids(Character), + {DefendingWeaponID, _} = character:get_weapon_ids(TargetCharacter), + + AttackingWeapon = weapon:from_id(AttackingWeaponID), + DefendingWeapon = weapon:from_id(DefendingWeaponID), + + AttackSequence = + attack:get_sequence(Range, AttackingWeapon, DefendingWeapon), + + {AttackEffects, RemainingAttackerHealth, RemainingDefenderHealth} = + handle_attack_sequence + ( + CharacterInstance, + TargetCharacterInstance, + AttackSequence + ), + + UpdatedCharacterInstance = + character_instance:set_current_health + ( + RemainingAttackerHealth, + CharacterInstance + ), + + UpdatedBattle = + battle:set_character_instance + ( + TargetIX, + character_instance:set_current_health + ( + RemainingDefenderHealth, + TargetCharacterInstance + ), + Battle + ), + { + % TODO: hide that into database_diff structs. + [], % TODO + [ + turn_result:new_character_attacked + ( + CharacterInstanceIX, + TargetIX, + AttackEffects + ) + ], + UpdatedBattle, + UpdatedCharacterInstance + }. diff --git a/src/battlemap/src/struct/battlemap.erl b/src/battlemap/src/struct/battlemap.erl new file mode 100644 index 0000000..59e0639 --- /dev/null +++ b/src/battlemap/src/struct/battlemap.erl @@ -0,0 +1,121 @@ +-module(battlemap). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( + battlemap, + { + id :: id(), + width :: integer(), + height :: integer(), + tile_ids :: array:array(tile:id()) + } +). + +-opaque struct() :: #battlemap{}. + +-export_type([struct/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 + ( + tile:id(), + list(tile:id()), + non_neg_integer(), + non_neg_integer(), + non_neg_integer() + ) + -> list(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 roll:percentage() of + N when (N >= 10) -> PreviousTileID; + _ -> tile:random_id() + end, + generate_random_tile_ids(NewTile, [NewTile|Result], (X - 1), Y, Width). + +-spec location_to_array_index + ( + non_neg_integer(), + 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 (struct()) -> id(). +get_id (Battlemap) -> Battlemap#battlemap.id. + +-spec get_width (struct()) -> integer(). +get_width (Battlemap) -> Battlemap#battlemap.width. + +-spec get_height (struct()) -> integer(). +get_height (Battlemap) -> Battlemap#battlemap.height. + +-spec get_tile_ids (struct()) -> array:array(tile:id()). +get_tile_ids (Battlemap) -> Battlemap#battlemap.tile_ids. + +-spec get_tile_id (location:type(), struct()) -> 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() + ) + -> struct(). +random (ID, Width, Height) -> + InitialTile = 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/src/struct/character.erl b/src/battlemap/src/struct/character.erl new file mode 100644 index 0000000..8e1099e --- /dev/null +++ b/src/battlemap/src/struct/character.erl @@ -0,0 +1,133 @@ +-module(character). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: non_neg_integer(). + +-record +( + character, + { + id :: id(), + owner_id :: player:id(), + name :: binary(), + icon :: binary(), + portrait :: binary(), + attributes :: attributes:struct(), + statistics :: statistics:struct(), + weapon_ids :: {weapon:id(), weapon:id()} + } +). + +-opaque struct() :: #character{}. + +-export_type([struct/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, + + set_weapon_ids/2, + set_statistics/2 + ] +). + +-export +( + [ + random/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_id (struct()) -> id(). +get_id (Char) -> Char#character.id. + +-spec get_owner_id (struct()) -> player:id(). +get_owner_id (Char) -> Char#character.owner_id. + +-spec get_name (struct()) -> binary(). +get_name (Char) -> Char#character.name. + +-spec get_icon (struct()) -> binary(). +get_icon (Char) -> Char#character.icon. + +-spec get_portrait (struct()) -> binary(). +get_portrait (Char) -> Char#character.portrait. + +-spec get_attributes (struct()) -> attributes:struct(). +get_attributes (Char) -> Char#character.attributes. + +-spec get_weapon_ids (struct()) -> {weapon:id(), weapon:id()}. +get_weapon_ids (Char) -> Char#character.weapon_ids. + +-spec get_statistics (struct()) -> statistics:struct(). +get_statistics (Char) -> Char#character.statistics. + +-spec set_weapon_ids + ( + {weapon:id(), weapon:id()}, + struct() + ) + -> struct(). +set_weapon_ids (WeaponIDs, Char) -> + Char#character + { + weapon_ids = WeaponIDs + }. + +-spec set_statistics + ( + statistics:struct(), + struct() + ) + -> struct(). +set_statistics (Stats, Char) -> + Char#character + { + statistics = Stats + }. + +-spec random + ( + non_neg_integer(), + player:id() + ) + -> struct(). +random (ID, OwnerID) -> + WeaponIDs = {weapon:random_id(), weapon:random_id()}, + Attributes = attributes:random(), + Statistics = 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 + }. diff --git a/src/battlemap/src/struct/character_instance.erl b/src/battlemap/src/struct/character_instance.erl new file mode 100644 index 0000000..9b64f9a --- /dev/null +++ b/src/battlemap/src/struct/character_instance.erl @@ -0,0 +1,160 @@ +-module(character_instance). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( + character_instance, + { + character :: character:struct(), + location :: {non_neg_integer(), non_neg_integer()}, + current_health :: non_neg_integer(), + active :: boolean() + } +). + +-opaque struct() :: #character_instance{}. + +-export_type([struct/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + new/2, + random/4 + ] +). + +%%%% Accessors +-export +( + [ + get_character/1, + get_location/1, + get_current_health/1, + get_is_active/1, + + set_character/2, + set_location/2, + set_current_health/2, + set_is_active/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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_character (struct()) -> character:struct(). +get_character (CharInst) -> CharInst#character_instance.character. + +-spec get_location (struct()) -> {non_neg_integer(), non_neg_integer()}. +get_location (CharInst) -> CharInst#character_instance.location. + +-spec get_current_health (struct()) -> non_neg_integer(). +get_current_health (CharInst) -> CharInst#character_instance.current_health. + +-spec get_is_active (struct()) -> boolean(). +get_is_active (CharInst) -> + ( + CharInst#character_instance.active + and + (CharInst#character_instance.current_health > 0) + ). + +-spec set_character (character:struct(), struct()) -> struct(). +set_character (Char, CharInst) -> + CharInst#character_instance + { + character = Char + }. + +-spec set_location + ( + {non_neg_integer(), non_neg_integer()}, + struct() + ) + -> struct(). +set_location (Location, CharInst) -> + CharInst#character_instance + { + location = Location + }. + +-spec set_current_health (non_neg_integer(), struct()) -> struct(). +set_current_health (Health, CharInst) -> + CharInst#character_instance + { + current_health = Health + }. + +-spec set_is_active (boolean(), struct()) -> struct(). +set_is_active (Active, CharInst) -> + CharInst#character_instance + { + active = Active + }. + +%%%% Utils +-spec new + ( + character:struct(), + {non_neg_integer(), non_neg_integer()} + ) + -> struct(). +new (Character, Location) -> + CharacterStatistics = character:get_statistics(Character), + #character_instance + { + character = Character, + location = Location, + current_health = statistics:get_health(CharacterStatistics), + active = false + }. + +-spec random + ( + character:struct(), + non_neg_integer(), + non_neg_integer(), + list({non_neg_integer(), non_neg_integer()}) + ) + -> struct(). +random (Character, BattlemapWidth, BattlemapHeight, ForbiddenLocations) -> + new + ( + Character, + find_random_location(BattlemapWidth, BattlemapHeight, ForbiddenLocations) + ). diff --git a/src/battlemap/src/struct/direction.erl b/src/battlemap/src/struct/direction.erl new file mode 100644 index 0000000..84ae272 --- /dev/null +++ b/src/battlemap/src/struct/direction.erl @@ -0,0 +1,37 @@ +-module(direction). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type enum() :: ('up' | 'down' | 'left' | 'right'). + +-export_type([enum/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/src/struct/location.erl b/src/battlemap/src/struct/location.erl new file mode 100644 index 0000000..b8e2bf3 --- /dev/null +++ b/src/battlemap/src/struct/location.erl @@ -0,0 +1,71 @@ +-module(location). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type type() :: {non_neg_integer(), non_neg_integer()}. + +-export_type([type/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + decode/1, + encode/1 + ] +). + +-export +( + [ + apply_direction/2, + dist/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec validate ({integer(), integer()}) -> type(). +validate ({X, Y}) -> + true = (X >= 0), + true = (Y >= 0), + {X, Y}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec apply_direction (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)}). + +-spec dist(type(), type()) -> non_neg_integer(). +dist ({OX, OY}, {DX, DY}) -> + (abs(DY - OY) + abs(DX - OX)). + +-spec encode (type()) -> {list(any())}. +encode ({X, Y}) -> + { + [ + {<<"x">>, X}, + {<<"y">>, Y} + ] + }. + +-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/src/struct/player.erl b/src/battlemap/src/struct/player.erl new file mode 100644 index 0000000..c4aefd1 --- /dev/null +++ b/src/battlemap/src/struct/player.erl @@ -0,0 +1,72 @@ +-module(player). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type id() :: binary(). + +-record +( + player, + { + id :: id(), + timeline :: list(any()) + } +). + +-opaque struct() :: #player{}. + +-export_type([struct/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 (struct()) -> id(). +get_id (Player) -> Player#player.id. + +-spec get_timeline (struct()) -> list(any()). +get_timeline (Player) -> Player#player.timeline. + +-spec add_to_timeline (list(any()), struct()) -> struct(). +add_to_timeline (NewEvents, Player) -> + OldTimeline = Player#player.timeline, + + Player#player + { + timeline = (NewEvents ++ OldTimeline) + }. + +-spec reset_timeline (struct()) -> struct(). +reset_timeline (Player) -> Player#player{ timeline = [] }. + +-spec new (id()) -> struct(). +new (ID) -> + #player + { + id = ID, + timeline = [] + }. + diff --git a/src/battlemap/src/struct/player_turn.erl b/src/battlemap/src/struct/player_turn.erl new file mode 100644 index 0000000..7795f35 --- /dev/null +++ b/src/battlemap/src/struct/player_turn.erl @@ -0,0 +1,58 @@ +-module(player_turn). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( + player_turn, + { + number :: non_neg_integer(), + player_ix :: non_neg_integer() + } +). + +-opaque struct() :: #player_turn{}. + +-export_type([struct/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + new/2 + ] +). + +%%%% Accessors +-export +( + [ + get_number/1, + get_player_ix/1 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec new (non_neg_integer(), non_neg_integer()) -> struct(). +new (Number, PlayerIX) -> + #player_turn + { + number = Number, + player_ix = PlayerIX + }. + +-spec get_number (struct()) -> non_neg_integer(). +get_number (PlayerTurn) -> PlayerTurn#player_turn.number. + +-spec get_player_ix (struct()) -> non_neg_integer(). +get_player_ix (PlayerTurn) -> PlayerTurn#player_turn.player_ix. diff --git a/src/battlemap/src/struct/statistics.erl b/src/battlemap/src/struct/statistics.erl new file mode 100644 index 0000000..6e29ea4 --- /dev/null +++ b/src/battlemap/src/struct/statistics.erl @@ -0,0 +1,193 @@ +-module(statistics). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record +( + statistics, + { + movement_points :: non_neg_integer(), + health :: non_neg_integer(), + dodges :: non_neg_integer(), + parries :: non_neg_integer(), + damage_min :: non_neg_integer(), + damage_max :: non_neg_integer(), + accuracy :: non_neg_integer(), + double_hits :: non_neg_integer(), + critical_hits :: non_neg_integer() + } +). + +-opaque struct() :: #statistics{}. + +-export_type([struct/0]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-export +( + [ + get_movement_points/1, + get_health/1, + get_dodges/1, + get_parries/1, + get_damage_min/1, + get_damage_max/1, + get_accuracy/1, + get_double_hits/1, + get_critical_hits/1, + + get_damages/1 + ] +). + +-export +( + [ + new/2 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec ceil (float()) -> integer(). +ceil (F) -> + I = trunc(F), + case (F > I) of + true -> (I + 1); + _ -> I + end. + +-spec float_to_int (float()) -> integer(). +float_to_int (F) -> ceil(F). + +-spec min_max (number(), number(), number()) -> number(). +min_max (Min, Max, V) -> min(Max, max(Min, V)). + +-spec average (list(number())) -> number(). +%average ([]) -> 0; +average (L) -> lists:sum(L) / length(L). + +% V | 010 | 030 | 050 | 070 | 100 | +% F | 004 | 023 | 058 | 104 | 200 | +-spec gentle_squared_growth (number()) -> non_neg_integer(). +gentle_squared_growth (V) -> float_to_int(math:pow(V, 1.8) / 20). + +% V | 010 | 030 | 050 | 070 | 100 | +% F | 001 | 005 | 018 | 041 | 100 | +-spec sudden_squared_growth (number()) -> non_neg_integer(). +sudden_squared_growth (V) -> float_to_int(math:pow(V, 2.5) / 1000). + +% V | 010 | 030 | 050 | 070 | 100 | +% F | 002 | 006 | 016 | 049 | 256 | +-spec sudden_exp_growth (number()) -> non_neg_integer(). +sudden_exp_growth (V) -> float_to_int(math:pow(4, V / 25)). + +% V | 010 | 030 | 050 | 070 | 100 | +% F | 040 | 066 | 079 | 088 | 099 | +% Seems too generous, values for attributes below 50 should dip faster and +% lower. +%-spec already_high_slow_growth (non_neg_integer()) -> non_neg_integer(). +%already_high_slow_growth (V) -> float_to_int(30 * math:log((V + 5)/4)). + +-spec damage_base_modifier (non_neg_integer()) -> float(). +damage_base_modifier (Strength) -> ((math:pow(Strength, 1.8) / 2000.0) - 0.75). + +-spec apply_damage_base_modifier + ( + float(), + non_neg_integer() + ) + -> non_neg_integer(). +apply_damage_base_modifier (Modifier, BaseValue) -> + max(0, float_to_int(BaseValue + (BaseValue * Modifier))). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% Accessors +-spec get_movement_points (struct()) -> non_neg_integer(). +get_movement_points (Stats) -> Stats#statistics.movement_points. + +-spec get_health (struct()) -> non_neg_integer(). +get_health (Stats) -> Stats#statistics.health. + +-spec get_dodges (struct()) -> non_neg_integer(). +get_dodges (Stats) -> Stats#statistics.dodges. + +-spec get_parries (struct()) -> non_neg_integer(). +get_parries (Stats) -> Stats#statistics.parries. + +-spec get_damage_min (struct()) -> non_neg_integer(). +get_damage_min (Stats) -> Stats#statistics.damage_min. + +-spec get_damage_max (struct()) -> non_neg_integer(). +get_damage_max (Stats) -> Stats#statistics.damage_max. + +-spec get_accuracy (struct()) -> non_neg_integer(). +get_accuracy (Stats) -> Stats#statistics.accuracy. + +-spec get_double_hits (struct()) -> non_neg_integer(). +get_double_hits (Stats) -> Stats#statistics.double_hits. + +-spec get_critical_hits (struct()) -> non_neg_integer(). +get_critical_hits (Stats) -> Stats#statistics.critical_hits. + +-spec get_damages (struct()) -> {non_neg_integer(), non_neg_integer()}. +get_damages (Stats) -> + { + Stats#statistics.damage_min, + Stats#statistics.damage_max + }. + +-spec new + ( + attributes:struct(), + {weapon:id(), weapon:id()} + ) + -> struct(). +new (BaseAttributes, WeaponIDs) -> + {ActiveWeaponID, _} = WeaponIDs, + ActiveWeapon = weapon:from_id(ActiveWeaponID), + {MinDamage, MaxDamage} = weapon:get_damages(ActiveWeapon), + Attributes = weapon:apply_to_attributes(BaseAttributes, ActiveWeapon), + Constitution = attributes:get_constitution(Attributes), + Dexterity = attributes:get_dexterity(Attributes), + Intelligence = attributes:get_intelligence(Attributes), + Mind = attributes:get_mind(Attributes), + Speed = attributes:get_speed(Attributes), + Strength = attributes:get_strength(Attributes), + DamageBaseModifier = damage_base_modifier(Strength), + + #statistics + { + movement_points = + gentle_squared_growth + ( + average([Mind, Constitution, Constitution, Speed, Speed, Speed]) + ), + health = + gentle_squared_growth(average([Mind, Constitution, Constitution])), + dodges = + min_max(0, 100, sudden_exp_growth(average([Dexterity, Mind, Speed]))), + parries = + min_max + ( + 0, + 75, + sudden_exp_growth + ( + average([Dexterity, Intelligence, Speed, Strength]) + ) + ), + damage_min = apply_damage_base_modifier(DamageBaseModifier, MinDamage), + damage_max = apply_damage_base_modifier(DamageBaseModifier, MaxDamage), + accuracy = min_max(0, 100, sudden_squared_growth(Dexterity)), + double_hits = + min_max(0, 100, sudden_squared_growth(average([Mind, Speed]))), + critical_hits = min_max(0, 100, sudden_squared_growth(Intelligence)) + }. diff --git a/src/battlemap/src/struct/tile.erl b/src/battlemap/src/struct/tile.erl new file mode 100644 index 0000000..e86da56 --- /dev/null +++ b/src/battlemap/src/struct/tile.erl @@ -0,0 +1,47 @@ +-module(tile). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-opaque id() :: non_neg_integer(). +-opaque struct() :: id(). + +-export_type([struct/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 () -> + roll:between(0, 15). diff --git a/src/battlemap/src/struct/turn_result.erl b/src/battlemap/src/struct/turn_result.erl new file mode 100644 index 0000000..5f796ca --- /dev/null +++ b/src/battlemap/src/struct/turn_result.erl @@ -0,0 +1,142 @@ +-module(turn_result). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +-record +( + switched_weapon, + { + character_instance_ix :: character_instance:id() + } +). + +-record +( + moved, + { + character_instance_ix :: character_instance:id(), + path :: list(direction:enum()), + new_location :: location:type() + } +). + +-record +( + attacked, + { + attacker_ix :: character_instance:id(), + defender_ix :: character_instance:id(), + sequence :: list(attack:struct()) + } +). + +-opaque struct() :: (#switched_weapon{} | #moved{} | #attacked{}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export_type([struct/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 (character_instance:id()) -> struct(). +new_character_switched_weapons (CharacterInstanceIX) -> + #switched_weapon { character_instance_ix = CharacterInstanceIX }. + +-spec new_character_moved + ( + character_instance:id(), + list(direction:enum()), + location:type() + ) + -> struct(). +new_character_moved (CharacterInstanceIX, Path, NewLocation) -> + #moved + { + character_instance_ix = CharacterInstanceIX, + path = Path, + new_location = NewLocation + }. + +-spec new_character_attacked + ( + character_instance:id(), + character_instance:id(), + list(attack:struct()) + ) + -> struct(). +new_character_attacked (AttackerIX, DefenderIX, AttackSequence) -> + #attacked + { + attacker_ix = AttackerIX, + defender_ix = DefenderIX, + sequence = AttackSequence + }. + +-spec encode (struct()) -> {list(any())}. +encode (TurnResult) when is_record(TurnResult, switched_weapon) -> + CharacterInstanceIX = TurnResult#switched_weapon.character_instance_ix, + + { + [ + {<<"t">>, <<"swp">>}, + {<<"ix">>, CharacterInstanceIX} + ] + }; +encode (TurnResult) when is_record(TurnResult, moved) -> + CharacterInstanceIX = TurnResult#moved.character_instance_ix, + Path = TurnResult#moved.path, + NewLocation = TurnResult#moved.new_location, + + EncodedPath = lists:map(fun direction:encode/1, Path), + EncodedNewLocation = location:encode(NewLocation), + + { + [ + {<<"t">>, <<"mv">>}, + {<<"ix">>, CharacterInstanceIX}, + {<<"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 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/src/struct/weapon.erl b/src/battlemap/src/struct/weapon.erl new file mode 100644 index 0000000..80cb925 --- /dev/null +++ b/src/battlemap/src/struct/weapon.erl @@ -0,0 +1,361 @@ +-module(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 struct() :: #weapon{}. + +-export_type([struct/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 (struct()) -> id(). +get_id (Wp) -> Wp#weapon.id. + +-spec get_range_type (struct()) -> range_type(). +get_range_type (Wp) -> Wp#weapon.range_type. + +-spec get_ranges (struct()) -> {non_neg_integer(), non_neg_integer()}. +get_ranges (Wp) -> + ranges_of_type(Wp#weapon.range_type, Wp#weapon.range_mod). + +-spec get_damages (struct()) -> {non_neg_integer(), non_neg_integer()}. +get_damages (Wp) -> + damages_of_type(Wp#weapon.range_type, Wp#weapon.damage_mod). + +-spec can_parry (struct()) -> boolean(). +can_parry (Wp) -> (Wp#weapon.range_type == melee). + +-spec from_id (id()) -> struct(). +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 () -> roll:between(0, 24). + +-spec apply_to_attributes + ( + attributes:struct(), + weapon:struct() + ) + -> attributes:struct(). +apply_to_attributes (Attributes, Weapon) -> + Dexterity = attributes:get_dexterity(Attributes), + Speed = attributes:get_speed(Attributes), + RangeModifier = Weapon#weapon.range_mod, + DamageModifier = Weapon#weapon.damage_mod, + WithRangeModifier = + case RangeModifier of + long -> + attributes:set_dexterity(max(0, (Dexterity - 20)), Attributes); + _ -> Attributes + end, + case DamageModifier of + heavy -> + attributes:set_speed(max(0, (Speed - 20)), WithRangeModifier); + _ -> WithRangeModifier + end. + |