| summaryrefslogtreecommitdiff | 
diff options
| author | nsensfel <SpamShield0@noot-noot.org> | 2018-04-10 13:01:12 +0200 | 
|---|---|---|
| committer | nsensfel <SpamShield0@noot-noot.org> | 2018-04-10 13:01:12 +0200 | 
| commit | 1001c3f6cfefd880c1721f2b80c1795197d05365 (patch) | |
| tree | 8696137b5684547b80129d308bc854a7e74fa0b0 /src/battlemap | |
| parent | d7032b408c5f66a3cb62c44cf0953abe48c39ef9 (diff) | |
Changing how the server services are organized...
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. + | 


