123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- -module(game_session).
- -behaviour(gen_server).
- -include_lib("server/include/social_actions.hrl").
- -include_lib("server/include/logging.hrl").
- -include_lib("server/include/games.hrl").
- -include_lib("server/include/classes.hrl").
- -include_lib("server/include/requests.hrl").
- -include_lib("server/include/settings.hrl").
- -include_lib("server/include/log.hrl").
- -export([start_link/1]).
- -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
- -export([process_request/3]).
- -export([bot_session_attach/2]).
- -define(SERVER, ?MODULE).
- -record(state, {
- user = undefined,
- rpc, %% rpc process
- rpc_mon, %% rpc monitor referense
- games = [],
- rels_notif_channel = undefined,
- rels_players =[]
- }).
- -record(participation, {
- game_id :: 'GameId'(), %% generated by id_generator
- reg_num :: integer(),
- rel_module :: atom(),
- rel_pid :: pid(), %% relay, which handles communication gameman maps game_id onto pid
- tab_module :: atom(),
- tab_pid :: pid(),
- ref :: any(), %% monitor reference to relay
- role = viewer :: atom() %% [viewer, player, ghost]
- }).
- % TODO: perform start_link on web socket init
- start_link(RPC) when is_pid(RPC) ->
- gen_server:start_link(?MODULE, [RPC], []).
- bot_session_attach(Pid, UserInfo) ->
- gen_server:cast(Pid, {bot_session_attach, UserInfo}).
- % TODO: in case of game requests from web page handle them here
- process_request(Pid, Source, Msg) ->
- ?INFO("API from ~p payload ~p pid ~p",[Source,Msg,Pid]),
- gen_server:call(Pid, {client_request, Msg}).
- init([RPC]) ->
- MonRef = erlang:monitor(process, RPC),
- {ok, #state{rpc = RPC, rpc_mon = MonRef}}.
- handle_call({client_request, Request}, From, State) ->
- handle_client_request(Request, From, State);
- handle_call(Request, From, State) ->
- ?INFO("unrecognized call: ~p", [Request]),
- {stop, {unknown_call, From, Request}, State}.
- handle_cast({bot_session_attach, UserInfo}, State = #state{user = undefined}) ->
- % ?INFO("bot session attach", []),
- {noreply, State#state{user = UserInfo}};
- handle_cast(Msg, State) ->
- ?INFO("session: unrecognized cast: ~p", [Msg]),
- {stop, {error, {unknown_cast, Msg}}, State}.
- handle_info({relay_event, SubscrId, RelayMsg}, State) ->
- handle_relay_message(RelayMsg, SubscrId, State);
- handle_info({relay_kick, SubscrId, Reason}, State) ->
- ?INFO("Recived a kick notification from the table: ~p", [Reason]),
- handle_relay_kick(Reason, SubscrId, State);
- handle_info({delivery, ["user_action", Action, Who, Whom], _} = Notification,
- #state{rels_players = RelsPlayers,
- user = User,
- rpc = RPC
- } = State) ->
- ?INFO("~w:handle_info/2 Delivery: ~p", [?MODULE, Notification]),
- UserId = User#'PlayerInfo'.id,
- case list_to_binary(Who) of
- UserId ->
- PlayerId = list_to_binary(Whom),
- case lists:member(PlayerId, RelsPlayers) of
- true ->
- Type = case Action of
- "subscribe" -> ?SOCIAL_ACTION_SUBSCRIBE;
- "unsubscribe" -> ?SOCIAL_ACTION_UNSUBSCRIBE;
- "block" -> ?SOCIAL_ACTION_BLOCK;
- "unblock" -> ?SOCIAL_ACTION_UNBLOCK
- end,
- Msg = #social_action_msg{initiator = UserId,
- recipient = PlayerId,
- type = Type
- },
- % TODO: put real db change notification from users:343 module here
- % nsx_msg:notify_db_subscription_change
- % should be additionaly subscribed in bg feed worker binded to USER_EXCHANGE
- ok = send_message_to_player(RPC, Msg);
- false ->
- do_nothing
- end;
- _ ->
- do_nothing
- end,
- {noreply, State};
- handle_info({'DOWN', MonitorRef, _Type, _Object, _Info} = Msg, State = #state{rpc_mon = MonitorRef}) ->
- ?INFO("connection closed, shutting down session:~p", [Msg]),
- {stop, normal, State};
- handle_info({'DOWN', OtherRef, process, _Object, Info} = _Msg,
- #state{games = Games, rpc = RPC} = State) ->
- case lists:keyfind(OtherRef, #participation.ref, Games) of
- #participation{} ->
- ?INFO("The table is down: ~p", [Info]),
- ?INFO("Closing the client and sutting down the session.", []),
- send_message_to_player(RPC, #disconnect{reason_id = <<"tableDown">>,
- reason = <<"The table you are playing on is unexpectedly down.">>}),
- {stop, table_down, State};
- _ ->
- {noreply, State}
- end;
- handle_info(Info, State) ->
- ?INFO("session: unrecognized info: ~p", [Info]),
- {noreply, State}.
- terminate(Reason, #state{rels_notif_channel = RelsChannel}) ->
- ?INFO("terminating session. reason: ~p", [Reason]),
- if RelsChannel =/= undefined -> nsm_mq_channel:close(RelsChannel);
- true -> do_nothing end,
- ok.
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
- %%===================================================================
- handle_client_request(#session_attach{token = Token}, _From,
- #state{user = undefined} = State) ->
- ?INFO("checking session token: ~p", [Token]),
- case auth_server:get_user_info(Token) of
- false ->
- ?INFO("failed session attach: ~p", [Token]),
- {stop, normal, {error, invalid_token}, State};
- UserInfo ->
- ?INFO("successfull session attach. Your user info: ~p", [UserInfo]),
- {reply, UserInfo, State#state{user = UserInfo}}
- end;
- handle_client_request(#session_attach_debug{token = Token, id = Id}, _From,
- #state{user = undefined} = State) ->
- ?INFO("checking debug session token: ~p", [{Token,Id}]),
- case {?IS_TEST, auth_server:get_user_info(Token, Id)} of
- {_Test, false} ->
- ?INFO("... ~p", [{_Test,false}]),
- {stop, normal, {error, invalid_token}, State};
- {false, true} ->
- ?INFO("... ~p", [{false,true}]),
- {stop, normal, {error, invalid_token}, State};
- {true, UserInfo} ->
- ?INFO("... ~p", [{true,UserInfo}]),
- {reply, UserInfo, State#state{user = UserInfo}};
- {false, UserInfo} ->
- ?INFO("... ~p", [{true,UserInfo}]),
- {reply, UserInfo, State#state{user = UserInfo}}
- end;
- handle_client_request(_, _From, #state{user = undefined} = State) ->
- ?INFO("unknown session call", []),
- {reply, {error, do_session_attach_first}, State};
- handle_client_request(#get_game_info{}, _From, State) ->
- ?INFO("session get game info", []),
- {reply, {error, not_implemented}, State};
- handle_client_request(#logout{}, _From, State) ->
- ?INFO("client requests #logout{}", []),
- {stop, normal, ok, State};
- handle_client_request(#get_player_stats{player_id = PlayerId, game_type = Game}, _From,
- State) when is_binary(Game) ->
- ?INFO("get player stats", []),
- GameModule = api_utils:gametype_to_gamemodule(api_utils:gametype_to_atom(Game)),
- Res = GameModule:get_player_stats(PlayerId),
- {reply, Res, State};
- handle_client_request(#chat{chat_id = GameId, message = Msg0}, _From,
- #state{user = User, games = Games} = State) ->
- ?INFO("chat", []),
- Msg = #chat_msg{chat = GameId, content = Msg0,
- author_id = User#'PlayerInfo'.id,
- author_nick = User#'PlayerInfo'.login
- },
- Participation = get_relay(GameId, Games),
- Res = case Participation of
- false ->
- {error, chat_not_registered};
- #participation{rel_pid = Srv, rel_module = RMod} ->
- RMod:publish(Srv, Msg)
- end,
- {reply, Res, State};
- handle_client_request(#social_action_msg{type=Type, initiator=P1, recipient=P2}, _From,
- #state{user = User} = State) when User =/= undefined ->
- UserIdBin = User#'PlayerInfo'.id,
- ?INFO("social action msg from ~p to ~p (casted by ~p)", [P1, P2, UserIdBin]),
- UserId = binary_to_list(UserIdBin),
- case Type of
- ?SOCIAL_ACTION_SUBSCRIBE ->
- Subject = binary_to_list(P2),
- nsm_users:subscribe_user(UserId, Subject),
- {reply, ok, State};
- ?SOCIAL_ACTION_UNSUBSCRIBE ->
- Subject = binary_to_list(P2),
- nsm_users:remove_subscribe(UserId, Subject),
- {reply, ok, State};
- ?SOCIAL_ACTION_BLOCK ->
- Subject = binary_to_list(P2),
- nsx_msg:notify(["subscription", "user", UserId, "block_user"], {Subject}),
- {reply, ok, State};
- ?SOCIAL_ACTION_UNBLOCK ->
- Subject = binary_to_list(P2),
- nsx_msg:notify(["subscription", "user", UserId, "unblock_user"], {Subject}),
- {reply, ok, State};
- ?SOCIAL_ACTION_LOVE ->
- {reply, ok, State};
- ?SOCIAL_ACTION_HAMMER ->
- {reply, ok, State};
- UnknownAction ->
- ?ERROR("Unknown social action msg from ~p to ~p: ~w", [P1,P2, UnknownAction]),
- {reply, {error, unknown_action}, State}
- end;
- handle_client_request(#social_action{} = Msg, _From,
- #state{user = User, games = Games} = State) ->
- ?INFO("social action", []),
- GameId = Msg#social_action.game,
- Res = #social_action_msg{type = Msg#social_action.type,
- game = GameId,
- recipient = Msg#social_action.recipient,
- initiator = User#'PlayerInfo'.id},
- Participation = get_relay(GameId, Games),
- Ans = case Participation of
- false ->
- {error, chat_not_registered};
- #participation{rel_pid = Srv, rel_module=RMod} ->
- RMod:publish(Srv, Res)
- end,
- {reply, Ans, State};
- handle_client_request(#subscribe_player_rels{players = Players}, _From,
- #state{user = User, rels_notif_channel = RelsChannel,
- rels_players = RelsPlayers, rpc = RPC} = State) ->
- ?INFO("subscribe player relations notifications: ~p", [Players]),
- UserId = User#'PlayerInfo'.id,
- UserIdStr = binary_to_list(UserId),
- %% Create subscription if we need
- NewRelsChannel =
- if RelsChannel == undefined ->
- {ok, Channel} = nsx_msg:subscribe_for_user_actions(UserIdStr, self()),
- Channel;
- true ->
- RelsChannel
- end,
- %% Add players relations to which we need to common list
- F = fun(PlayerId, Acc) ->
- case lists:member(PlayerId, Acc) of
- true -> Acc;
- false -> [PlayerId | Acc]
- end
- end,
- NewRelsPlayers = lists:foldl(F, RelsPlayers, Players),
- %% Notify the client about current state of subscription relations.
- %% (Blocking relations state should be "false" at the start)
- F2 =
- fun(PlayerId) ->
- PlayerIdStr = binary_to_list(PlayerId),
- Type = case nsm_users:is_user_subscr(UserIdStr, PlayerIdStr) of
- true -> ?SOCIAL_ACTION_SUBSCRIBE;
- false -> ?SOCIAL_ACTION_UNSUBSCRIBE
- end,
- Msg = #social_action_msg{initiator = UserId,
- recipient = PlayerId,
- type = Type},
- ok = send_message_to_player(RPC, Msg)
- end,
- lists:foreach(F2, Players),
- NewState = State#state{rels_players = NewRelsPlayers,
- rels_notif_channel = NewRelsChannel},
- {reply, ok, NewState};
- handle_client_request(#unsubscribe_player_rels{players = Players}, _From,
- #state{rels_notif_channel = RelsChannel,
- rels_players = RelsPlayers
- } = State) ->
- ?INFO("unsubscribe player relations notifications", []),
- %% Remove players from common list
- NewRelsPlayers = RelsPlayers -- Players,
- %% Remove subscription if we don't need it now
- NewRelsChannel =
- if NewRelsPlayers == [] -> nsm_mq_channel:close(RelsChannel),
- undefined;
- true ->
- RelsChannel
- end,
- NewState = State#state{rels_players = NewRelsPlayers,
- rels_notif_channel = NewRelsChannel},
- {reply, ok, NewState};
- handle_client_request(#join_game{game = GameId}, _From,
- #state{user = User, rpc = RPC, games = Games} = State) ->
- UserId = User#'PlayerInfo'.id,
- ?INFO("join game ~p user ~p from ~p", [GameId, UserId,_From]),
- case get_relay(GameId, Games) of
- #participation{} ->
- {reply, {error, already_joined}, State};
- false ->
- ?INFO("Requesting main relay info...",[]),
- case game_manager:get_relay_mod_pid(GameId) of
- {FLMod, FLPid} ->
- ?INFO("Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
- case FLMod:reg(FLPid, User) of
- {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
- ?INFO("join to game relay: ~p",[{RMod, RPid}]),
- {ok, _SubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
- Ref = erlang:monitor(process, RPid),
- Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
- rel_module = RMod, rel_pid = RPid,
- tab_module = TMod, tab_pid = TPid, role = player},
- Res = #'TableInfo'{}, % FIXME: The client should accept 'ok' responce
- {reply, Res, State#state{games = [Part | State#state.games]}};
- {error, finished} ->
- ?INFO("The game is finished: ~p.",[GameId]),
- ok = send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
- reason = null}),
- {reply, {error, finished}, State};
- {error, out} ->
- ?INFO("Out of the game: ~p.",[GameId]),
- ok = send_message_to_player(RPC, #disconnect{reason_id = <<"disconnected">>,
- reason = null}),
- {reply, {error, out}, State};
- {error, not_allowed} ->
- ?ERROR("Not allowed to connect: ~p.",[GameId]),
- ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
- reason = null}),
- {reply, {error, not_allowed}, State}
- end;
- undefined ->
- ?ERROR("Game not found: ~p.",[GameId]),
- ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
- reason = null}),
- {reply, {error, not_exists}, State}
- end
- end;
- handle_client_request(#game_action{game = GameId} = Msg, _From, State) ->
- % ?INFO("game action ~p", [{GameId,Msg,_From}]),
- Participation = get_relay(GameId, State#state.games),
- case Participation of
- false ->
- {reply, {error, game_not_found}, State};
- #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
- UId = (State#state.user)#'PlayerInfo'.id,
- ?INFO("PLAYER ~p MOVES ~p in GAME ~p",[UId,Msg,GameId]),
- {reply, TMod:submit(TPid, RegNum, Msg), State}
- end;
- handle_client_request(#pause_game{game = GameId, action = Action}, _From, State) ->
- % ?INFO("pause game", []),
- % UId = (State#state.user)#'PlayerInfo'.id,
- Participation = get_relay(GameId, State#state.games),
- % ?INFO("ID: ~p, pause game: ~p, my games: ~p", [UId, GameId, State#state.games]),
- case Participation of
- false ->
- ?INFO("A", []),
- {reply, {error, game_not_found}, State};
- #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
- Signal = case Action of
- <<"pause">> -> pause_game;
- <<"resume">> -> resume_game
- end,
- Res = TMod:signal(TPid, RegNum, {Signal, self()}),
- ?INFO("B. Res: ~p", [Res]),
- {reply, Res, State}
- end;
- handle_client_request(Request, _From, State) ->
- ?INFO("unrecognized client request: ~p", [Request]),
- {stop, {unknown_client_request, Request}, State}.
- %%===================================================================
- handle_relay_message(Msg, _SubscrId, #state{rpc = RPC} = State) ->
- try send_message_to_player(RPC, Msg) of
- ok ->
- {noreply, State};
- tcp_closed ->
- {stop, normal, State};
- E -> error_logger:info_msg("ERROR SEND MESSAGE TO PLAYER: ~p",[E]),
- {stop, normal, State}
- catch
- exit:{normal, {gen_server,call, [RPC, {send_message, _}]}} ->
- {stop, normal, State};
- exit:{noproc, {gen_server,call, [RPC, {send_message, _}]}} ->
- {stop, normal, State};
- E:R ->
- {stop, normal, State}
- end.
- %%===================================================================
- %% The notification from the current table to rejoin to the game
- %% because the user for example was moved to another table.
- handle_relay_kick({rejoin, GameId}, _SubscrId,
- #state{user = User, games = Games, rpc = RPC} = State) ->
- ?INFO("Requesting main relay info...",[]),
- UserId = User#'PlayerInfo'.id,
- case game_manager:get_relay_mod_pid(GameId) of
- {FLMod, FLPid} ->
- ?INFO("Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
- case FLMod:reg(FLPid, User) of
- {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
- ?INFO("join to game relay: ~p",[{RMod, RPid}]),
- {ok, _NewSubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
- Ref = erlang:monitor(process, RPid),
- Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
- rel_module = RMod, rel_pid = RPid,
- tab_module = TMod, tab_pid = TPid, role = player},
- NewGames = lists:keyreplace(GameId, #participation.game_id, Games, Part),
- {noreply, State#state{games = NewGames}};
- {error, finished} ->
- ?INFO("The game is finished: ~p.",[GameId]),
- send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
- reason = null}),
- {stop, normal, State};
- {error, out} ->
- ?INFO("Out of the game: ~p.",[GameId]),
- send_message_to_player(RPC, #disconnect{reason_id = <<"kicked">>,
- reason = null}),
- {stop, normal, State};
- {error, not_allowed} ->
- ?ERROR("Not allowed to connect: ~p.",[GameId]),
- send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
- reason = null}),
- {stop, {error, not_allowed_to_join}, State}
- end;
- undefined ->
- ?ERROR("Game not found: ~p.",[GameId]),
- send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
- reason = null}),
- {stop, {error, game_not_found}, State}
- end;
- handle_relay_kick(Reason, _SubscrId, #state{rpc = RPC} = State) ->
- {ReasonId, ReasonText} =
- case Reason of
- table_closed -> {<<"tableClosed">>, null};
- table_down -> {null, <<"The table was closed unexpectedly.">>};
- game_over -> {null, <<"The game is over.">>};
- _ -> {<<"kicked">>, null}
- end,
- send_message_to_player(RPC, #disconnect{reason_id = ReasonId, reason = ReasonText}),
- {stop, normal, State}.
- %%===================================================================
- get_relay(GameId, GameList) ->
- lists:keyfind(GameId, #participation.game_id, GameList).
- % TODO: flush message to web socket process
- send_message_to_player(Pid, Message) ->
- ?INFO("MESSAGE to ~p ~p",[Pid,Message]),
- Pid ! {send_message,Message}, ok.
|