game_session.erl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. gas:info("API payload ~p pid ~p",[Msg,Pid]),
  41. gen_server:call(Pid, {client_request, Msg}).
  42. process_request(Pid, Source, Msg) ->
  43. gas: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. gas:info("unrecognized call: ~p", [Request]),
  52. {stop, {unknown_call, From, Request}, State}.
  53. handle_cast({bot_session_attach, UserInfo}, State = #state{user = undefined}) ->
  54. % gas:info(?MODULE,"bot session attach", []),
  55. {noreply, State#state{user = UserInfo}};
  56. handle_cast(Msg, State) ->
  57. gas: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. gas: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. gas: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. % wf:send_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. gas: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. gas:info(?MODULE,"The table is down: ~p", [Info]),
  105. gas:info(?MODULE,"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. gas:info("session: unrecognized info: ~p", [Info]),
  114. {noreply, State}.
  115. terminate(Reason, #state{rels_notif_channel = RelsChannel}) ->
  116. wf: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. gas:info("checking session token: ~p", [Token]),
  126. case auth_server:get_user_info(Token) of
  127. false ->
  128. gas:error("failed session attach: ~p", [Token]),
  129. {stop, normal, {error, invalid_token}, State};
  130. UserInfo ->
  131. gas: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. gas:info("checking debug session token: ~p", [{Token,Id}]),
  137. case {?IS_TEST, auth_server:get_user_info(Token, Id)} of
  138. {_Test, false} ->
  139. gas:error("... ~p", [{_Test,false}]),
  140. {stop, normal, {error, invalid_token}, State};
  141. {false, true} ->
  142. gas:error("... ~p", [{false,true}]),
  143. {stop, normal, {error, invalid_token}, State};
  144. {true, UserInfo} ->
  145. gas:info("... ~p", [{true,UserInfo}]),
  146. {reply, UserInfo, State#state{user = UserInfo}};
  147. {false, UserInfo} ->
  148. gas:info("... ~p", [{true,UserInfo}]),
  149. {reply, UserInfo, State#state{user = UserInfo}}
  150. end;
  151. handle_client_request(_, _From, #state{user = undefined} = State) ->
  152. gas:info(?MODULE,"unknown session call", []),
  153. {reply, {error, do_session_attach_first}, State};
  154. handle_client_request(#get_game_info{}, _From, State) ->
  155. gas:info(?MODULE,"session get game info", []),
  156. {reply, {error, not_implemented}, State};
  157. handle_client_request(#logout{}, _From, State) ->
  158. gas:info(?MODULE,"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. gas:info(?MODULE,"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. gas:info(?MODULE,"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. gas:info(?MODULE,"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. wf:send(["subscription", "user", UserId, "block_user"], {Subject}),
  198. {reply, ok, State};
  199. ?SOCIAL_ACTION_UNBLOCK ->
  200. Subject = binary_to_list(P2),
  201. wf:send(["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. gas:info(?MODULE,"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. gas:info(?MODULE,"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. RelsChannel;
  239. true ->
  240. RelsChannel
  241. end,
  242. %% Add players relations to which we need to common list
  243. F = fun(PlayerId, Acc) ->
  244. case lists:member(PlayerId, Acc) of
  245. true -> Acc;
  246. false -> [PlayerId | Acc]
  247. end
  248. end,
  249. NewRelsPlayers = lists:foldl(F, RelsPlayers, Players),
  250. %% Notify the client about current state of subscription relations.
  251. %% (Blocking relations state should be "false" at the start)
  252. F2 =
  253. fun(PlayerId) ->
  254. PlayerIdStr = binary_to_list(PlayerId),
  255. Type = case nsm_users:is_user_subscr(UserIdStr, PlayerIdStr) of
  256. true -> ?SOCIAL_ACTION_SUBSCRIBE;
  257. false -> ?SOCIAL_ACTION_UNSUBSCRIBE
  258. end,
  259. Msg = #social_action_msg{initiator = UserId,
  260. recipient = PlayerId,
  261. type = Type},
  262. ok = send_message_to_player(RPC, Msg)
  263. end,
  264. lists:foreach(F2, Players),
  265. NewState = State#state{rels_players = NewRelsPlayers,
  266. rels_notif_channel = NewRelsChannel},
  267. {reply, ok, NewState};
  268. handle_client_request(#unsubscribe_player_rels{players = Players}, _From,
  269. #state{rels_notif_channel = RelsChannel,
  270. rels_players = RelsPlayers
  271. } = State) ->
  272. gas:info(?MODULE,"unsubscribe player relations notifications", []),
  273. %% Remove players from common list
  274. NewRelsPlayers = RelsPlayers -- Players,
  275. %% Remove subscription if we don't need it now
  276. NewRelsChannel =
  277. if NewRelsPlayers == [] -> nsm_mq_channel:close(RelsChannel),
  278. undefined;
  279. true ->
  280. RelsChannel
  281. end,
  282. NewState = State#state{rels_players = NewRelsPlayers,
  283. rels_notif_channel = NewRelsChannel},
  284. {reply, ok, NewState};
  285. handle_client_request(#join_game{game = GameId}, _From,
  286. #state{user = User, rpc = RPC, games = Games} = State) ->
  287. UserId = User#'PlayerInfo'.id,
  288. gas:info(?MODULE,"join game ~p user ~p from ~p", [GameId, UserId,_From]),
  289. case get_relay(GameId, Games) of
  290. #participation{} ->
  291. {reply, {error, already_joined}, State};
  292. false ->
  293. gas:info(?MODULE,"Requesting main relay info...",[]),
  294. case game_manager:get_relay_mod_pid(GameId) of
  295. {FLMod, FLPid} ->
  296. gas:info(?MODULE,"Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
  297. case FLMod:reg(FLPid, User) of
  298. {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
  299. gas:info(?MODULE,"join to game relay: ~p",[{RMod, RPid}]),
  300. {ok, _SubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
  301. Ref = erlang:monitor(process, RPid),
  302. Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
  303. rel_module = RMod, rel_pid = RPid,
  304. tab_module = TMod, tab_pid = TPid, role = player},
  305. Res = #'TableInfo'{}, % FIXME: The client should accept 'ok' responce
  306. {reply, Res, State#state{games = [Part | State#state.games]}};
  307. {error, finished} ->
  308. gas:info(?MODULE,"The game is finished: ~p.",[GameId]),
  309. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
  310. reason = null}),
  311. {reply, {error, finished}, State};
  312. {error, out} ->
  313. gas:info(?MODULE,"Out of the game: ~p.",[GameId]),
  314. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"disconnected">>,
  315. reason = null}),
  316. {reply, {error, out}, State};
  317. {error, not_allowed} ->
  318. ?ERROR("Not allowed to connect: ~p.",[GameId]),
  319. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
  320. reason = null}),
  321. {reply, {error, not_allowed}, State}
  322. end;
  323. undefined ->
  324. ?ERROR("Game not found: ~p.",[GameId]),
  325. ok = send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
  326. reason = null}),
  327. {reply, {error, not_exists}, State}
  328. end
  329. end;
  330. handle_client_request(#game_action{game = GameId} = Msg, _From, State) ->
  331. % gas:info(?MODULE,"game action ~p", [{GameId,Msg,_From}]),
  332. Participation = get_relay(GameId, State#state.games),
  333. case Participation of
  334. false ->
  335. {reply, {error, game_not_found}, State};
  336. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  337. UId = (State#state.user)#'PlayerInfo'.id,
  338. gas:info(?MODULE,"PLAYER ~p MOVES ~p in GAME ~p",[UId,Msg,GameId]),
  339. {reply, TMod:submit(TPid, RegNum, Msg), State}
  340. end;
  341. handle_client_request(#pause_game{game = GameId, action = Action}, _From, State) ->
  342. % gas:info(?MODULE,"pause game", []),
  343. % UId = (State#state.user)#'PlayerInfo'.id,
  344. Participation = get_relay(GameId, State#state.games),
  345. % gas:info(?MODULE,"ID: ~p, pause game: ~p, my games: ~p", [UId, GameId, State#state.games]),
  346. case Participation of
  347. false ->
  348. gas:info(?MODULE,"A", []),
  349. {reply, {error, game_not_found}, State};
  350. #participation{reg_num = RegNum, tab_pid = TPid, tab_module = TMod} ->
  351. Signal = case Action of
  352. <<"pause">> -> pause_game;
  353. <<"resume">> -> resume_game
  354. end,
  355. Res = TMod:signal(TPid, RegNum, {Signal, self()}),
  356. gas:info(?MODULE,"B. Res: ~p", [Res]),
  357. {reply, Res, State}
  358. end;
  359. handle_client_request(Request, _From, State) ->
  360. gas:info(?MODULE,"unrecognized client request: ~p", [Request]),
  361. {stop, {unknown_client_request, Request}, State}.
  362. %%===================================================================
  363. handle_relay_message(Msg, _SubscrId, #state{rpc = RPC} = State) ->
  364. try send_message_to_player(RPC, Msg) of
  365. ok ->
  366. {noreply, State};
  367. tcp_closed ->
  368. {stop, normal, State};
  369. E -> error_logger:info_msg("ERROR SEND MESSAGE TO PLAYER: ~p",[E]),
  370. {stop, normal, State}
  371. catch
  372. exit:{normal, {gen_server,call, [RPC, {server, _}]}} ->
  373. {stop, normal, State};
  374. exit:{noproc, {gen_server,call, [RPC, {server, _}]}} ->
  375. {stop, normal, State};
  376. E:R ->
  377. {stop, normal, State}
  378. end.
  379. %%===================================================================
  380. %% The notification from the current table to rejoin to the game
  381. %% because the user for example was moved to another table.
  382. handle_relay_kick({rejoin, GameId}, _SubscrId,
  383. #state{user = User, games = Games, rpc = RPC} = State) ->
  384. gas:info(?MODULE,"Requesting main relay info...",[]),
  385. UserId = User#'PlayerInfo'.id,
  386. case game_manager:get_relay_mod_pid(GameId) of
  387. {FLMod, FLPid} ->
  388. gas:info(?MODULE,"Found the game: ~p. Trying to register...",[{FLMod, FLPid}]),
  389. case FLMod:reg(FLPid, User) of
  390. {ok, {RegNum, {RMod, RPid}, {TMod, TPid}}} ->
  391. gas:info(?MODULE,"join to game relay: ~p",[{RMod, RPid}]),
  392. {ok, _NewSubscrId} = RMod:subscribe(RPid, self(), UserId, RegNum),
  393. Ref = erlang:monitor(process, RPid),
  394. Part = #participation{ref = Ref, game_id = GameId, reg_num = RegNum,
  395. rel_module = RMod, rel_pid = RPid,
  396. tab_module = TMod, tab_pid = TPid, role = player},
  397. NewGames = lists:keyreplace(GameId, #participation.game_id, Games, Part),
  398. {noreply, State#state{games = NewGames}};
  399. {error, finished} ->
  400. gas:info(?MODULE,"The game is finished: ~p.",[GameId]),
  401. send_message_to_player(RPC, #disconnect{reason_id = <<"gameFinished">>,
  402. reason = null}),
  403. {stop, normal, State};
  404. {error, out} ->
  405. gas:info(?MODULE,"Out of the game: ~p.",[GameId]),
  406. send_message_to_player(RPC, #disconnect{reason_id = <<"kicked">>,
  407. reason = null}),
  408. {stop, normal, State};
  409. {error, not_allowed} ->
  410. ?ERROR("Not allowed to connect: ~p.",[GameId]),
  411. send_message_to_player(RPC, #disconnect{reason_id = <<"notAllowed">>,
  412. reason = null}),
  413. {stop, {error, not_allowed_to_join}, State}
  414. end;
  415. undefined ->
  416. ?ERROR("Game not found: ~p.",[GameId]),
  417. send_message_to_player(RPC, #disconnect{reason_id = <<"notExists">>,
  418. reason = null}),
  419. {stop, {error, game_not_found}, State}
  420. end;
  421. handle_relay_kick(Reason, _SubscrId, #state{rpc = RPC} = State) ->
  422. {ReasonId, ReasonText} =
  423. case Reason of
  424. table_closed -> {<<"tableClosed">>, null};
  425. table_down -> {null, <<"The table was closed unexpectedly.">>};
  426. game_over -> {null, <<"The game is over.">>};
  427. _ -> {<<"kicked">>, null}
  428. end,
  429. send_message_to_player(RPC, #disconnect{reason_id = ReasonId, reason = ReasonText}),
  430. {stop, normal, State}.
  431. %%===================================================================
  432. get_relay(GameId, GameList) ->
  433. lists:keyfind(GameId, #participation.game_id, GameList).
  434. % TODO: flush message to web socket process
  435. send_message_to_player(Pid, Message) ->
  436. % gas:info("MESSAGE to ~p ~p",[Pid,Message]),
  437. Pid ! {server,Message},
  438. ok.