game_session.erl 21 KB

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