game_session.erl 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. -module(game_session).
  2. -behaviour(gen_server).
  3. -include_lib("server/include/social_actions.hrl").
  4. -include_lib("server/include/logging.hrl").
  5. -include_lib("server/include/games.hrl").
  6. -include_lib("server/include/classes.hrl").
  7. -include_lib("server/include/requests.hrl").
  8. -include_lib("server/include/settings.hrl").
  9. -include_lib("server/include/log.hrl").
  10. -export([start_link/1]).
  11. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  12. -export([process_request/2, process_request/3]).
  13. -export([bot_session_attach/2]).
  14. -define(SERVER, ?MODULE).
  15. -record(state, {
  16. user = undefined,
  17. rpc, %% rpc process
  18. rpc_mon, %% rpc monitor referense
  19. games = [],
  20. rels_notif_channel = undefined,
  21. rels_players =[]
  22. }).
  23. -record(participation, {
  24. game_id :: 'GameId'(), %% generated by id_generator
  25. reg_num :: integer(),
  26. rel_module :: atom(),
  27. rel_pid :: pid(), %% relay, which handles communication gameman maps game_id onto pid
  28. tab_module :: atom(),
  29. tab_pid :: pid(),
  30. ref :: any(), %% monitor reference to relay
  31. role = viewer :: atom() %% [viewer, player, ghost]
  32. }).
  33. start_link(RPC) when is_pid(RPC) ->
  34. gen_server:start_link(?MODULE, [RPC], []).
  35. bot_session_attach(Pid, UserInfo) ->
  36. gen_server:cast(Pid, {bot_session_attach, UserInfo}).
  37. process_request(Pid, Msg) ->
  38. gas:info(?MODULE,"Client Request: ~p to: ~p",[Msg,Pid]),
  39. gen_server:call(Pid, {client_request, Msg}).
  40. process_request(Pid, Source, Msg) ->
  41. gas:info(?MODULE,"Client Request ~p to: ~p from: ~p",[Msg,Pid,Source]),
  42. gen_server:call(Pid, {client_request, Msg}).
  43. send_message_to_player(Pid, Message) ->
  44. gas:info(?MODULE,"Server Response ~p to ~p",[Message,Pid]),
  45. Pid ! {server,Message}, ok.
  46. init([RPC]) ->
  47. MonRef = erlang:monitor(process, RPC),
  48. {ok, #state{rpc = RPC, rpc_mon = MonRef}}.
  49. handle_call({client_request, Request}, From, State) ->
  50. handle_client_request(Request, From, State);
  51. handle_call(Request, From, State) ->
  52. gas:info(?MODULE,"Unrecognized call: ~p", [Request]),
  53. {stop, {unknown_call, From, Request}, State}.
  54. handle_cast({bot_session_attach, UserInfo}, State = #state{user = undefined}) ->
  55. % gas:info(?MODULE,"bot session attach", []),
  56. {noreply, State#state{user = UserInfo}};
  57. handle_cast(Msg, State) ->
  58. gas:info(?MODULE,"Unrecognized cast: ~p", [Msg]),
  59. {stop, {error, {unknown_cast, Msg}}, State}.
  60. handle_info({relay_event, SubscrId, RelayMsg}, State) ->
  61. handle_relay_message(RelayMsg, SubscrId, State);
  62. handle_info({relay_kick, SubscrId, Reason}, State) ->
  63. gas:info(?MODULE,"Recived a kick notification from the table: ~p", [Reason]),
  64. handle_relay_kick(Reason, SubscrId, State);
  65. handle_info({delivery, ["user_action", Action, Who, Whom], _} = Notification,
  66. #state{rels_players = RelsPlayers, user = User, rpc = RPC } = State) ->
  67. gas:info(?MODULE,"Handle_info/2 Delivery: ~p", [Notification]),
  68. UserId = User#'PlayerInfo'.id,
  69. case list_to_binary(Who) of
  70. UserId ->
  71. PlayerId = list_to_binary(Whom),
  72. case lists:member(PlayerId, RelsPlayers) of
  73. true ->
  74. Type = case Action of
  75. "subscribe" -> ?SOCIAL_ACTION_SUBSCRIBE;
  76. "unsubscribe" -> ?SOCIAL_ACTION_UNSUBSCRIBE;
  77. "block" -> ?SOCIAL_ACTION_BLOCK;
  78. "unblock" -> ?SOCIAL_ACTION_UNBLOCK
  79. end,
  80. Msg = #social_action_msg{initiator = UserId,
  81. recipient = PlayerId,
  82. type = Type
  83. },
  84. % TODO: put real db change notification from users:343 module here
  85. % wf:send_db_subscription_change
  86. % should be additionaly subscribed in bg feed worker binded to USER_EXCHANGE
  87. ok = send_message_to_player(RPC, Msg);
  88. false ->
  89. do_nothing
  90. end;
  91. _ ->
  92. do_nothing
  93. end,
  94. {noreply, State};
  95. handle_info({'DOWN', MonitorRef, _Type, _Object, _Info} = Msg, State = #state{rpc_mon = MonitorRef}) ->
  96. gas:info("connection closed, shutting down session:~p", [Msg]),
  97. {stop, normal, State};
  98. handle_info({'DOWN', OtherRef, process, _Object, Info} = _Msg,
  99. #state{games = Games, rpc = RPC} = State) ->
  100. case lists:keyfind(OtherRef, #participation.ref, Games) of
  101. #participation{} ->
  102. gas:info(?MODULE,"The table is down: ~p", [Info]),
  103. gas:info(?MODULE,"Closing the client and sutting down the session.", []),
  104. send_message_to_player(RPC,
  105. #disconnect{reason_id = <<"tableDown">>,
  106. reason = <<"The table you are playing on is unexpectedly down.">>}),
  107. {stop, table_down, State};
  108. _ ->
  109. {noreply, State}
  110. end;
  111. handle_info(Info, State) ->
  112. gas:info(?MODULE,"Unrecognized info: ~p", [Info]),
  113. {noreply, State}.
  114. terminate(Reason, #state{rels_notif_channel = RelsChannel}) ->
  115. gas:info(?MODULE,"Terminating session: ~p", [Reason]),
  116. if RelsChannel =/= undefined -> nsm_mq_channel:close(RelsChannel);
  117. true -> do_nothing end,
  118. ok.
  119. code_change(_OldVsn, State, _Extra) ->
  120. {ok, State}.
  121. %%===================================================================
  122. handle_client_request(#session_attach{token = Token}, _From,
  123. #state{user = undefined} = State) ->
  124. gas:info(?MODULE,"Checking session token: ~p", [Token]),
  125. case auth_server:get_user_info(wf:to_binary(Token)) of
  126. false ->
  127. gas:error(?MODULE,"failed session attach: ~p", [Token]),
  128. {stop, normal, {error, invalid_token}, State};
  129. UserInfo ->
  130. gas:info(?MODULE,"successfull session attach. Your user info: ~p", [UserInfo]),
  131. {reply, UserInfo, State#state{user = UserInfo}}
  132. end;
  133. handle_client_request(_, _From, #state{user = undefined} = State) ->
  134. gas:info(?MODULE,"Unknown session call", []),
  135. {reply, {error, do_session_attach_first}, State};
  136. handle_client_request(#get_game_info{}, _From, State) ->
  137. gas:info(?MODULE,"Session get game info", []),
  138. {reply, {error, not_implemented}, State};
  139. handle_client_request(#logout{}, _From, State) ->
  140. gas:info(?MODULE,"Logout", []),
  141. {stop, normal, ok, State};
  142. handle_client_request(#get_player_stats{player_id = PlayerId, game_type = GameModule}, _From, #state{rpc = RPC} = State) ->
  143. Res = GameModule:get_player_stats(PlayerId),
  144. gas:info(?MODULE,"Get player stats: ~p", [Res]),
  145. send_message_to_player(RPC, Res),
  146. {reply, Res, State};
  147. handle_client_request(#chat{chat_id = GameId, message = Msg0}, _From,
  148. #state{user = User, games = Games} = State) ->
  149. gas:info(?MODULE,"Chat", []),
  150. Msg = #chat_msg{chat = GameId, content = Msg0,
  151. author_id = User#'PlayerInfo'.id,
  152. author_nick = User#'PlayerInfo'.login
  153. },
  154. Participation = get_relay(GameId, Games),
  155. Res = case Participation of
  156. false ->
  157. {error, chat_not_registered};
  158. #participation{rel_pid = Srv, rel_module = RMod} ->
  159. RMod:publish(Srv, Msg)
  160. end,
  161. {reply, Res, State};
  162. handle_client_request(#social_action_msg{type=Type, initiator=P1, recipient=P2}, _From,
  163. #state{user = User} = State) when User =/= undefined ->
  164. UserIdBin = User#'PlayerInfo'.id,
  165. gas:info(?MODULE,"Social action msg from ~p to ~p (casted by ~p)", [P1, P2, UserIdBin]),
  166. UserId = binary_to_list(UserIdBin),
  167. case Type of
  168. ?SOCIAL_ACTION_SUBSCRIBE ->
  169. Subject = binary_to_list(P2),
  170. nsm_users:subscribe_user(UserId, Subject),
  171. {reply, ok, State};
  172. ?SOCIAL_ACTION_UNSUBSCRIBE ->
  173. Subject = binary_to_list(P2),
  174. nsm_users:remove_subscribe(UserId, Subject),
  175. {reply, ok, State};
  176. ?SOCIAL_ACTION_BLOCK ->
  177. Subject = binary_to_list(P2),
  178. wf:send(["subscription", "user", UserId, "block_user"], {Subject}),
  179. {reply, ok, State};
  180. ?SOCIAL_ACTION_UNBLOCK ->
  181. Subject = binary_to_list(P2),
  182. wf:send(["subscription", "user", UserId, "unblock_user"], {Subject}),
  183. {reply, ok, State};
  184. ?SOCIAL_ACTION_LOVE ->
  185. {reply, ok, State};
  186. ?SOCIAL_ACTION_HAMMER ->
  187. {reply, ok, State};
  188. UnknownAction ->
  189. gas:error(?MODULE,"Unknown social action msg from ~p to ~p: ~w", [P1,P2, UnknownAction]),
  190. {reply, {error, unknown_action}, State}
  191. end;
  192. handle_client_request(#social_action{} = Msg, _From,
  193. #state{user = User, games = Games} = State) ->
  194. gas:info(?MODULE,"Social action", []),
  195. GameId = Msg#social_action.game,
  196. Res = #social_action_msg{type = Msg#social_action.type,
  197. game = GameId,
  198. recipient = Msg#social_action.recipient,
  199. initiator = User#'PlayerInfo'.id},
  200. Participation = get_relay(GameId, Games),
  201. Ans = case Participation of
  202. false ->
  203. {error, chat_not_registered};
  204. #participation{rel_pid = Srv, rel_module=RMod} ->
  205. RMod:publish(Srv, Res)
  206. end,
  207. {reply, Ans, State};
  208. handle_client_request(#subscribe_player_rels{players = Players}, _From,
  209. #state{user = User, rels_notif_channel = RelsChannel,
  210. rels_players = RelsPlayers, rpc = RPC} = State) ->
  211. gas:info(?MODULE,"Subscribe player relations notifications: ~p", [Players]),
  212. UserId = User#'PlayerInfo'.id,
  213. UserIdStr = binary_to_list(UserId),
  214. %% Create subscription if we need
  215. NewRelsChannel =
  216. if RelsChannel == undefined ->
  217. % {ok, Channel} = nsx_msg:subscribe_for_user_actions(UserIdStr, self()),
  218. % Channel;
  219. RelsChannel;
  220. true ->
  221. RelsChannel
  222. end,
  223. %% Add players relations to which we need to common list
  224. F = fun(PlayerId, Acc) ->
  225. case lists:member(PlayerId, Acc) of
  226. true -> Acc;
  227. false -> [PlayerId | Acc]
  228. end
  229. end,
  230. NewRelsPlayers = lists:foldl(F, RelsPlayers, Players),
  231. %% Notify the client about current state of subscription relations.
  232. %% (Blocking relations state should be "false" at the start)
  233. F2 =
  234. fun(PlayerId) ->
  235. PlayerIdStr = binary_to_list(PlayerId),
  236. Type = case nsm_users:is_user_subscr(UserIdStr, PlayerIdStr) of
  237. true -> ?SOCIAL_ACTION_SUBSCRIBE;
  238. false -> ?SOCIAL_ACTION_UNSUBSCRIBE
  239. end,
  240. Msg = #social_action_msg{initiator = UserId,
  241. recipient = PlayerId,
  242. type = Type},
  243. ok = send_message_to_player(RPC, Msg)
  244. end,
  245. lists:foreach(F2, Players),
  246. NewState = State#state{rels_players = NewRelsPlayers,
  247. rels_notif_channel = NewRelsChannel},
  248. {reply, ok, NewState};
  249. handle_client_request(#unsubscribe_player_rels{players = Players}, _From,
  250. #state{rels_notif_channel = RelsChannel,
  251. rels_players = RelsPlayers
  252. } = State) ->
  253. gas:info(?MODULE,"Unsubscribe player relations notifications", []),
  254. %% Remove players from common list
  255. NewRelsPlayers = RelsPlayers -- Players,
  256. %% Remove subscription if we don't need it now
  257. NewRelsChannel =
  258. if NewRelsPlayers == [] -> nsm_mq_channel:close(RelsChannel),
  259. undefined;
  260. true ->
  261. RelsChannel
  262. end,
  263. NewState = State#state{rels_players = NewRelsPlayers,
  264. rels_notif_channel = NewRelsChannel},
  265. {reply, ok, NewState};
  266. handle_client_request(#join_game{game = GameId}, _From,
  267. #state{user = User, rpc = RPC, games = Games} = State) ->
  268. UserId = User#'PlayerInfo'.id,
  269. gas:info(?MODULE,"Join game ~p user ~p from ~p", [GameId, UserId,_From]),
  270. case get_relay(GameId, Games) of
  271. #participation{} ->
  272. {reply, {error, already_joined}, State};
  273. false ->
  274. gas:info(?MODULE,"Requesting main relay info...",[]),
  275. case game_manager:get_relay_mod_pid(GameId) of
  276. {FLMod, FLPid} ->
  277. gas:info(?MODULE,"Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
  278. case FLMod:reg(FLPid, User) of
  279. {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
  280. gas:info(?MODULE,"join to game relay: ~p",[{RMod, RPid}]),
  281. {ok, _SubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
  282. Ref = erlang:monitor(process, RPid),
  283. Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
  284. rel_module = RMod, rel_pid = RPid,
  285. tab_module = TMod, tab_pid = TPid, role = player},
  286. Res = #'TableInfo'{}, % FIXME: The client should accept 'ok' responce
  287. {reply, Res, State#state{games = [Part | State#state.games]}};
  288. {error, finished} ->
  289. gas:info(?MODULE,"The game is finished: ~p.",[GameId]),
  290. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
  291. reason = null}),
  292. {reply, {error, finished}, State};
  293. {error, out} ->
  294. gas:info(?MODULE,"Out of the game: ~p.",[GameId]),
  295. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"disconnected">>,
  296. reason = null}),
  297. {reply, {error, out}, State};
  298. {error, not_allowed} ->
  299. gas:error(?MODULE,"Not allowed to connect: ~p.",[GameId]),
  300. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
  301. reason = null}),
  302. {reply, {error, not_allowed}, State}
  303. end;
  304. undefined ->
  305. gas:error(?MODULE,"Game not found: ~p.",[GameId]),
  306. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
  307. reason = null}),
  308. {reply, {error, not_exists}, State}
  309. end
  310. end;
  311. handle_client_request(#game_action{game = GameId} = Msg, _From, State) ->
  312. gas:info(?MODULE,"Game action ~p", [{GameId,Msg,_From}]),
  313. Participation = get_relay(GameId, State#state.games),
  314. case Participation of
  315. false ->
  316. {reply, {error, game_not_found}, State};
  317. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  318. UId = (State#state.user)#'PlayerInfo'.id,
  319. gas:info(?MODULE,"PLAYER ~p MOVES ~p in GAME ~p",[UId,Msg,GameId]),
  320. {reply, TMod:submit(TPid, RegNum, Msg), State}
  321. end;
  322. handle_client_request(#pause_game{game = GameId, action = Action}, _From, State) ->
  323. Participation = get_relay(GameId, State#state.games),
  324. gas:info(?MODULE,"Pause game: ~p, user: ~p games: ~p",
  325. [GameId, State#state.user, State#state.games]),
  326. case Participation of
  327. false ->
  328. gas:info(?MODULE,"A", []),
  329. {reply, {error, game_not_found}, State};
  330. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  331. Signal = case Action of
  332. pause -> pause_game;
  333. resume -> resume_game
  334. end,
  335. Res = TMod:signal(TPid, RegNum, {Signal, self()}),
  336. gas:info(?MODULE,"B. Res: ~p", [Res]),
  337. {reply, Res, State}
  338. end;
  339. handle_client_request(Request, _From, State) ->
  340. gas:info(?MODULE,"unrecognized client request: ~p", [Request]),
  341. {stop, {unknown_client_request, Request}, State}.
  342. %%===================================================================
  343. handle_relay_message(Msg, _SubscrId, #state{rpc = RPC} = State) ->
  344. try send_message_to_player(RPC, Msg) of
  345. ok -> {noreply, State};
  346. tcp_closed -> {stop, normal, State};
  347. E -> {stop, normal, State}
  348. catch exit:{normal, {gen_server,call, [RPC, {server, _}]}} -> {stop, normal, State};
  349. exit:{noproc, {gen_server,call, [RPC, {server, _}]}} -> {stop, normal, State};
  350. E:R -> {stop, normal, State} end.
  351. %%===================================================================
  352. %% The notification from the current table to rejoin to the game
  353. %% because the user for example was moved to another table.
  354. handle_relay_kick({rejoin, GameId}, _SubscrId,
  355. #state{user = User, games = Games, rpc = RPC} = State) ->
  356. gas:info(?MODULE,"Requesting main relay info...",[]),
  357. UserId = User#'PlayerInfo'.id,
  358. case game_manager:get_relay_mod_pid(GameId) of
  359. {FLMod, FLPid} ->
  360. gas:info(?MODULE,"Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
  361. case FLMod:reg(FLPid, User) of
  362. {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
  363. gas:info(?MODULE,"Join to game relay: ~p",[{RMod, RPid}]),
  364. {ok, _NewSubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
  365. Ref = erlang:monitor(process, RPid),
  366. Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
  367. rel_module = RMod, rel_pid = RPid,
  368. tab_module = TMod, tab_pid = TPid, role = player},
  369. NewGames = lists:keyreplace(GameId, #participation.game_id, Games, Part),
  370. {noreply, State#state{games = NewGames}};
  371. {error, finished} ->
  372. gas:info(?MODULE,"The game is finished: ~p.",[GameId]),
  373. send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
  374. reason = null}),
  375. {stop, normal, State};
  376. {error, out} ->
  377. gas:info(?MODULE,"Out of the game: ~p.",[GameId]),
  378. send_message_to_player(RPC, #disconnect{reason_id = <<"kicked">>,
  379. reason = null}),
  380. {stop, normal, State};
  381. {error, not_allowed} ->
  382. gas:error(?MODULE,"Not allowed to connect: ~p.",[GameId]),
  383. send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
  384. reason = null}),
  385. {stop, {error, not_allowed_to_join}, State}
  386. end;
  387. undefined ->
  388. gas:error(?MODULE,"Game not found: ~p.",[GameId]),
  389. send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
  390. reason = null}),
  391. {stop, {error, game_not_found}, State}
  392. end;
  393. handle_relay_kick(Reason, _SubscrId, #state{rpc = RPC} = State) ->
  394. {ReasonId, ReasonText} =
  395. case Reason of
  396. table_closed -> {<<"tableClosed">>, null};
  397. table_down -> {null, <<"The table was closed unexpectedly.">>};
  398. game_over -> {null, <<"The game is over.">>};
  399. _ -> {<<"kicked">>, null}
  400. end,
  401. send_message_to_player(RPC, #disconnect{reason_id = ReasonId, reason = ReasonText}),
  402. {stop, normal, State}.
  403. %%===================================================================
  404. get_relay(GameId, GameList) ->
  405. lists:keyfind(GameId, #participation.game_id, GameList).
  406. % TODO: flush message to web socket process