game_session.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. -module(game_session).
  2. -behaviour(gen_server).
  3. -include_lib("server/include/requests.hrl").
  4. -include_lib("server/include/settings.hrl").
  5. -compile(export_all).
  6. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  7. -record(state, { user = undefined, rpc, rpc_mon, games = [] }).
  8. -record(participation, {
  9. game_id :: 'GameId'(),
  10. reg_num :: integer(),
  11. rel_module :: atom(),
  12. rel_pid :: pid(), %% relay, which handles communication gameman maps game_id onto pid
  13. tab_module :: atom(),
  14. tab_pid :: pid(),
  15. ref :: any(), %% monitor reference to relay
  16. role = viewer :: atom() %% [viewer, player, ghost]
  17. }).
  18. start(RPC) when is_pid(RPC) -> gen_server:start(?MODULE, [RPC], []).
  19. start_link(RPC) when is_pid(RPC) -> gen_server:start_link(?MODULE, [RPC], []).
  20. bot_session_attach(Pid, UserInfo) -> gen_server:cast(Pid, {bot_session_attach, UserInfo}).
  21. process_request(Pid, Msg) -> gen_server:call(Pid, {client_request, Msg}).
  22. process_request(Pid, Source, Msg) -> gen_server:call(Pid, {client_request, Msg}).
  23. send_message_to_player(Pid, Message) -> Pid ! {server,Message}, ok.
  24. init([RPC]) -> MonRef = erlang:monitor(process, RPC),
  25. {ok, #state{rpc = RPC, rpc_mon = MonRef}}.
  26. handle_call({client_request, Request}, From, State) -> handle_client_request(Request, From, State);
  27. handle_call(Request, From, State) ->
  28. gas:info(?MODULE,"Unrecognized call: ~p from ~p", [Request,From]),
  29. {stop, {unknown_call, From, Request}, State}.
  30. handle_cast({bot_session_attach, UserInfo}, State = #state{user = undefined}) ->
  31. {noreply, State#state{user = UserInfo}};
  32. handle_cast(Msg, State) ->
  33. gas:info(?MODULE,"Unrecognized cast: ~p", [Msg]),
  34. {stop, {error, {unknown_cast, Msg}}, State}.
  35. handle_info({relay_event, SubscrId, RelayMsg}, State) ->
  36. handle_relay_message(RelayMsg, SubscrId, State);
  37. handle_info({relay_kick, SubscrId, Reason}, State) ->
  38. gas:info(?MODULE,"Recived a kick notification from the table: ~p", [Reason]),
  39. handle_relay_kick(Reason, SubscrId, State);
  40. handle_info({'DOWN', MonitorRef, _Type, _Object, _Info} = Msg, State = #state{rpc_mon = MonitorRef}) ->
  41. gas:info(?MODULE, "connection closed, shutting down session:~p", [Msg]),
  42. {stop, normal, State};
  43. handle_info({'DOWN', OtherRef, process, _Object, Info} = _Msg, #state{games = Games, rpc = RPC} = State) ->
  44. case lists:keyfind(OtherRef, #participation.ref, Games) of
  45. #participation{} ->
  46. gas:info(?MODULE,"The table is down: ~p", [Info]),
  47. gas:info(?MODULE,"Closing the client and sutting down the session.", []),
  48. send_message_to_player(RPC,
  49. #disconnect{reason_id = <<"tableDown">>,
  50. reason = <<"The table you are playing on is unexpectedly down.">>}),
  51. {stop, table_down, State};
  52. _ -> {noreply, State} end;
  53. handle_info(Info, State=#state{rpc=RPC}) ->
  54. gas:info(?MODULE,"Unrecognized info: ~p", [Info]),
  55. {noreply, State}.
  56. terminate(Reason, #state{rpc=RPC}) ->
  57. gas:info(?MODULE,"Terminating session: ~p", [Reason]),
  58. ok.
  59. code_change(_OldVsn, State, _Extra) ->
  60. {ok, State}.
  61. %% Register User
  62. register_user_by_module(RPC,User,GameId,#state{games=Games} = State) ->
  63. UserId = User#'PlayerInfo'.id,
  64. case game:get_relay_mod_pid(GameId) of
  65. {FLMod, FLPid} ->
  66. gas:info(?MODULE,"Found the game: ~p by module ~p Trying to register...",[FLPid,FLMod]),
  67. case FLMod:reg(FLPid, User) of
  68. {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
  69. gas:info(?MODULE,"join to game relay: ~p",[{RMod, RPid}]),
  70. {ok, _SubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
  71. Ref = erlang:monitor(process, RPid),
  72. Part = #participation{
  73. ref = Ref, game_id = GameId, reg_num = RegNum,
  74. rel_module = RMod, rel_pid = RPid,
  75. tab_module = TMod, tab_pid = TPid, role = player},
  76. NewGames = game:plist_setkey(GameId,#participation.game_id,Games,Part),
  77. {reply, ok, State#state{games = NewGames}};
  78. {error, finished} ->
  79. gas:info(?MODULE,"The game is finished: ~p.",[GameId]),
  80. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>, reason = null}),
  81. {reply, {error, finished}, State};
  82. {error, out} ->
  83. gas:info(?MODULE,"Out of the game: ~p.",[GameId]),
  84. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"disconnected">>,reason = null}),
  85. {reply, {error, out}, State};
  86. {error, not_allowed} ->
  87. gas:error(?MODULE,"Not allowed to connect: ~p.",[GameId]),
  88. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,reason = null}),
  89. {reply, {error, not_allowed}, State};
  90. UnknownError ->
  91. gas:error(?MODULE,"Unknown Registration Error: ~p.",[GameId]),
  92. ok = send_message_to_player(RPC, #disconnect{reason_id = term_to_binary(UnknownError),reason = null}),
  93. {reply, UnknownError, State}
  94. end;
  95. undefined ->
  96. gas:error(?MODULE,"Game not found: ~p.",[GameId]),
  97. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>, reason = null}),
  98. {reply, {error, not_exists}, State}
  99. end.
  100. %%===================================================================
  101. handle_client_request(#session_attach{token = Token}, _From, #state{user = undefined} = State) ->
  102. gas:info(?MODULE,"Checking session token: ~p", [Token]),
  103. case auth_server:get_user_info(wf:to_binary(Token)) of
  104. false ->
  105. gas:error(?MODULE,"failed session attach: ~p", [Token]),
  106. {stop, normal, {error, invalid_token}, State};
  107. UserInfo ->
  108. gas:info(?MODULE,"successfull session attach. Your user info: ~p", [UserInfo]),
  109. {reply, UserInfo, State#state{user = UserInfo}} end;
  110. handle_client_request(_, _From, #state{user = undefined} = State) ->
  111. gas:info(?MODULE,"Unknown session call", []),
  112. {reply, {error, do_session_attach_first}, State};
  113. handle_client_request(#logout{}, _From, State) ->
  114. gas:info(?MODULE,"Logout", []),
  115. {stop, normal, ok, State};
  116. handle_client_request(#stats_action{player_id = PlayerId, game_type = GameModule}, _From, #state{rpc = RPC} = State) ->
  117. Res = game:get_player_info(GameModule, PlayerId),
  118. gas:info(?MODULE,"Get player stats: ~p", [Res]),
  119. send_message_to_player(RPC, Res),
  120. {reply, Res, State};
  121. handle_client_request(#chat{game = GameId, who = DisplayName, message = Msg0}, _From,
  122. #state{user = User, games = Games} = State) ->
  123. gas:info(?MODULE,"Chat Message ~n ~p", [Msg0]),
  124. Msg = #chat_event{game = GameId, message = Msg0, who = DisplayName },
  125. Participation = get_relay(GameId, Games),
  126. Res = case Participation of
  127. false ->
  128. {error, chat_not_registered};
  129. #participation{rel_pid = Srv, rel_module = RMod} ->
  130. RMod:publish(Srv, Msg)
  131. end,
  132. {reply, Res, State};
  133. handle_client_request(#join_game{game = GameId}, _From,
  134. #state{user = User, rpc = RPC, games = Games} = State) ->
  135. UserId = User#'PlayerInfo'.id,
  136. gas:info(?MODULE,"Join game ~p user ~p from ~p", [GameId, UserId,_From]),
  137. case get_relay(GameId, Games) of
  138. #participation{} -> {reply, {error, already_joined}, State};
  139. false -> register_user_by_module(RPC,User,GameId,State) end;
  140. handle_client_request(#game_action{game = GameId} = Msg, _From, State) ->
  141. gas:info(?MODULE,"Game action ~p", [{GameId,Msg,_From}]),
  142. Participation = get_relay(GameId, State#state.games),
  143. case Participation of
  144. false ->
  145. {reply, {error, game_not_found}, State};
  146. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  147. UId = (State#state.user)#'PlayerInfo'.id,
  148. gas:info(?MODULE,"PLAYER ~p MOVES ~p in GAME ~p",[UId,Msg,GameId]),
  149. {reply, TMod:submit(TPid, RegNum, Msg), State}
  150. end;
  151. handle_client_request(#pause_game{game = GameId, action = Action}, _From, State) ->
  152. Participation = get_relay(GameId, State#state.games),
  153. gas:info(?MODULE,"Pause game: ~p, user: ~p games: ~p",
  154. [GameId, State#state.user, State#state.games]),
  155. case Participation of
  156. false ->
  157. gas:info(?MODULE,"A", []),
  158. {reply, {error, game_not_found}, State};
  159. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  160. Signal = case Action of
  161. pause -> pause_game;
  162. resume -> resume_game
  163. end,
  164. Res = TMod:signal(TPid, RegNum, {Signal, self()}),
  165. gas:info(?MODULE,"B. Res: ~p", [Res]),
  166. {reply, Res, State}
  167. end;
  168. handle_client_request(Request, _From, State=#state{rpc=RPC}) ->
  169. gas:info(?MODULE,"unrecognized client request: ~p", [Request]),
  170. {stop, {unknown_client_request, Request}, State}.
  171. %%===================================================================
  172. handle_relay_message(Msg, _SubscrId, #state{rpc = RPC} = State) ->
  173. try send_message_to_player(RPC, Msg) of
  174. ok -> {noreply, State};
  175. tcp_closed -> {stop, normal, State};
  176. E -> {stop, normal, State}
  177. catch exit:{normal, {gen_server,call, [RPC, {server, _}]}} -> {stop, normal, State};
  178. exit:{noproc, {gen_server,call, [RPC, {server, _}]}} -> {stop, normal, State};
  179. E:R -> {stop, normal, State} end.
  180. %%===================================================================
  181. %% The notification from the current table to rejoin to the game
  182. %% because the user for example was moved to another table.
  183. handle_relay_kick({rejoin, GameId}, _SubscrId,
  184. #state{user = User, games = Games, rpc = RPC} = State) ->
  185. gas:info(?MODULE,"Rejoin request from relay. GameId ~p User ~p",[GameId,User#'PlayerInfo'.id]),
  186. register_user_by_module(RPC,User,GameId,State);
  187. handle_relay_kick(Reason, _SubscrId, #state{rpc = RPC} = State) ->
  188. {ReasonId, ReasonText} =
  189. case Reason of
  190. table_closed -> {<<"tableClosed">>, null};
  191. table_down -> {null, <<"The table was closed unexpectedly.">>};
  192. game_over -> {null, <<"The game is over.">>};
  193. _ -> {<<"kicked">>, null}
  194. end,
  195. send_message_to_player(RPC, #disconnect{reason_id = ReasonId, reason = ReasonText}),
  196. {stop, normal, State}.
  197. %%===================================================================
  198. get_relay(GameId, GameList) ->
  199. lists:keyfind(GameId, #participation.game_id, GameList).