okey.erl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. -module(okey).
  2. -compile(export_all).
  3. -include_lib("n2o/include/wf.hrl").
  4. -include_lib("server/include/requests.hrl").
  5. -include_lib("server/include/settings.hrl").
  6. -include_lib("avz/include/avz.hrl").
  7. -include_lib("kvs/include/user.hrl").
  8. -include_lib("db/include/journal.hrl").
  9. -define(GAMEID, game_form()).
  10. game_form() ->
  11. case wf:qs(<<"game">>) of
  12. undefined -> 1000001;
  13. X -> wf:to_integer(X) end.
  14. -record(player, {id, label, info, take, discard, history = []}).
  15. -define(RESET_ELEMENTS, [
  16. {gosterge, #label{ id = gosterge, body="Gosterge: "}},
  17. {h1, #dropdown{ id = h1, options = []}},
  18. {h2, #dropdown{ id = h2, options = []}},
  19. {h3, #dropdown{ id = h3, options = []}},
  20. {h4, #dropdown{ id = h4, options = []}} ]).
  21. new_user() ->
  22. Imagionary = anonymous:imagionary_users(),
  23. {Id,Name,Surname} = lists:nth(crypto:rand_uniform(1,length(Imagionary)),Imagionary),
  24. FakeId = anonymous:fake_id(Id),
  25. X = #user{
  26. id = FakeId,
  27. tokens=[{n2o,get(session_id)}],
  28. names = Name,
  29. surnames = Surname},
  30. wf:wire(wf:f("document.cookie='~s=~s; path=/; expires=~s';",
  31. ["n2o-name",wf:to_list(FakeId),js_session:cookie_expire(js_session:ttl())])),
  32. kvs:put(X),
  33. X.
  34. user() ->
  35. case wf:user() of
  36. undefined ->
  37. SessionUser = wf:cookie_req(<<"n2o-name">>,?REQ),
  38. SessionId = get(session_id),
  39. wf:info(?MODULE,"Auth User: ~p",[SessionUser]),
  40. wf:info(?MODULE,"Auth Id: ~p",[SessionId]),
  41. X = case kvs:get(user,SessionUser) of
  42. {ok,User} ->
  43. SS = lists:keyfind(n2o,1,User#user.tokens),
  44. case SS of
  45. {n2o,SessionId} -> User;
  46. _ -> new_user() end;
  47. _ -> new_user() end,
  48. wf:user(X),
  49. X;
  50. U-> U end.
  51. color(Id,Color) -> ok. % wf:wire(wf:f("document.querySelector('#~s').style.color = \"~s\";",[Id,Color])).
  52. unselect(Id) -> color(Id,black).
  53. select(Id) -> color(Id,red).
  54. update(A,B) -> ok. % wf:update(A,B);
  55. redraw_istaka(TilesList) ->
  56. redraw_tiles(TilesList, #dropdown{id = "istaka", postback = combo, source = [istaka]}).
  57. redraw_tiles(undefined, _DropDown) -> [];
  58. redraw_tiles([] = _TilesList, DropDown = #dropdown{id = ElementId}) ->
  59. okey:update(ElementId, [DropDown#dropdown{value = [], options = []}]);
  60. redraw_tiles([{Tile, _}| _ ] = TilesList, DropDown = #dropdown{id = ElementId}) ->
  61. okey:update(ElementId, [DropDown#dropdown{value = Tile,
  62. options = [#option{label = CVBin, value = CVBin} || {CVBin, _} <- TilesList]}]).
  63. redraw_players(Players) ->
  64. User = user(),
  65. [ begin PN = player_name(PI),
  66. okey:update(LabelId, #label{ id = LabelId,
  67. style= case User#user.id == Id of
  68. true -> "font-weight: bold;";
  69. _ -> "" end, body = <<" ",PN/binary," ">>})
  70. end || #player{label = LabelId, info = #'PlayerInfo'{id = Id} = PI} <- Players].
  71. update_players(UpdatedPlayer = #player{label = LabelId}, Players) ->
  72. lists:sort(
  73. fun(#player{label = E1}, #player{label = E2}) -> E1 < E2 end,
  74. [UpdatedPlayer | lists:keydelete(LabelId, #player.label, Players)]
  75. ).
  76. player_name(PI) -> auth_server:player_name(PI).
  77. tash(C,V) -> {wf:to_binary([wf:to_list(C)," ",wf:to_list(V)]), {C, V}}.
  78. main() -> #dtl{file="index", bindings=[{title,<<"N2O">>},{body,body()}]}.
  79. patch_users() ->
  80. [ begin
  81. Score = score_journal(User),
  82. kvs:put(User#user{tokens=game:plist_setkey(score,1,Tokens,{score,Score})})
  83. end|| User=#user{tokens=Tokens} <- kvs:all(user), Tokens /= [], Tokens /= undefined].
  84. send_roster(Pid) ->
  85. % X = [ send_roster_item(User) || User=#user{tokens=Tokens} <- kvs:all(user), Tokens /= [], Tokens /= undefined],
  86. X = [ begin
  87. {User#user.id,User#user.names,User#user.surnames,score(User)}
  88. end || User=#user{tokens=Tokens} <- kvs:all(user), Tokens /= [], Tokens /= undefined, proplists:get_value(score,Tokens,0) /= 0],
  89. XS = lists:sort(fun({_,_,_,S1},{_,_,_,S2}) -> S1 < S2 end,X),
  90. Lists = [lists:sublist(XS,100)], %split(170,XS,[]),
  91. [ send_roster_group(Pid,List) || List <- Lists],
  92. Pid ! {server,{roster_end,length(Lists)}},
  93. wf:info(?MODULE,"Users: ~p",[length(X)]).
  94. split(N,[],Result) -> Result;
  95. split(N,List,Result) when length(List) < N -> Result ++ [List];
  96. split(N,List,Result) -> {A,B}=lists:split(N,List), Result ++ [A] ++ split(N,B,Result).
  97. score(User) -> proplists:get_value(score,User#user.tokens,0).
  98. score_journal(User) ->
  99. Score = case kvs:get(reveal_log,User#user.id) of
  100. {ok,#reveal_log{score=S}} -> S;
  101. _ -> wf:info(?MODULE,"Score not found for User ~p",[User#user.id]), 0 end.
  102. already_online(Pid) ->
  103. [ Pid ! {user_online,User} || {_,_,{_,User}} <- game:online() ].
  104. send_roster_item(Pid,User) ->
  105. Pid ! {server,{roster_item,User#user.id,User#user.names,User#user.surnames,0}}.
  106. send_roster_group(Pid,List) ->
  107. % wf:info(?MODULE,"User Group: ~p",[List]),
  108. Pid ! {server,{roster_group,List}}.
  109. body() -> [].
  110. body2() ->
  111. wf:wire(#api{name=plusLogin, tag=plus}),
  112. [ #panel { id = history },
  113. #button { id = pluslogin, body = "Login", postback = login_button },
  114. #label { id = nothing, body = " Google"}, #br{}, #br{},
  115. #label { id = gosterge, body = "Gosterge"}, #br{},
  116. #label { id = player1, body = "Seat 1"}, #dropdown{id=h1,options=[]}, #br{},
  117. #label { id = player2, body = "Seat 2"}, #dropdown{id=h2,options=[]}, #br{},
  118. #label { id = player3, body = "Seat 3"}, #dropdown{id=h3,options=[]}, #br{},
  119. #label { id = player4, body = "Seat 4"}, #dropdown{id=h4,options=[]}, #br{}, #br{},
  120. #button { id = logout, body = "Logout", postback = login_button },
  121. #button { id = attach, body = "Attach", postback = attach },
  122. #button { id = join, body = "Join", postback = join, source = [games_ids]},
  123. #dropdown { id = games_ids, postback = combo, options = []}, #br{},
  124. #dropdown { id = take_src, options = [
  125. #option{label="Table",value="0"},
  126. #option{label="Left",value="1"}]},
  127. #button { id = take, body = "Take", postback = take, source=[take_src]},
  128. #dropdown { id = istaka, postback = combo, source=[istaka],options=[]},
  129. #button { id = discard, body = "Discard", postback = discard, source=[istaka]},
  130. #button { id = reveal, body = "Reveal", postback = reveal, source=[istaka]}, #br{},
  131. #button { id = saw_okey, body = "I Saw Okey", postback = i_saw_okey},
  132. #button { id = have_8, body = "8 Tashes", postback = i_have_8_tashes},
  133. #button { id = pause, body = "Pause", postback = pause},
  134. #button { id = info, body = "PlayerInfo", postback = player_info} ].
  135. event(terminate) ->
  136. User = user(),
  137. wf:send(broadcast,{user_offline,User}),
  138. wf:info(?MODULE,"EXTerminate",[]);
  139. event(init) ->
  140. js_session:ensure_sid([],?CTX),
  141. GamesIds = case game:get_all_games_ids() of
  142. [] -> [?GAMEID];
  143. List -> List end,
  144. okey:update(games_ids,#dropdown{id = games_ids, value = ?GAMEID, options =
  145. [#option{label = wf:to_list(GameId), value = wf:to_list(GameId)} || GameId <- GamesIds]}),
  146. wf:info(?MODULE,"Istaka on Started:"),
  147. event(attach),
  148. event(join);
  149. event(login_button) -> wf:wire(protocol:logout());
  150. event(join) ->
  151. GameId = get(okey_game_id),
  152. wf:wire(protocol:join(wf:to_list(GameId)));
  153. event(take) ->
  154. GameId = get(okey_game_id),
  155. wf:wire(protocol:take(wf:to_list(GameId), wf:q(take_src)));
  156. event(player_info) ->
  157. % wf:info(?MODULE,"Cowboy Cookies: ~p",[wf:cookies_req(?REQ)]),
  158. User = user(),
  159. wf:cookie(<<"user">>,<<"macim">>,<<"/ws/">>,24 * 60 * 60),
  160. Wire = protocol:player_info(
  161. wf:f("'~s'",[wf:to_list(User#user.id)]),wf:f("'~s'",[game_okey])),
  162. wf:info(?MODULE,"PlayeInfoJS: ~s",[Wire]),
  163. wf:wire(Wire);
  164. event(attach) ->
  165. {ok,GamePid} = game_session:start_link(self()),
  166. wf:session(<<"game_pid">>,GamePid),
  167. User = user(),
  168. wf:reg(User#user.id),
  169. wf:info(?MODULE,"User Attach: ~p",[User]),
  170. gproc:set_value({p,l,broadcast},{wf:peer(?REQ),User}),
  171. wf:info(?MODULE,"Games Online: ~p",[game:online()]),
  172. put(okey_im, User#user.id),
  173. wf:wire(wf:f("document.user = '~s';document.names = '~s';document.surnames = '~s';",
  174. [User#user.id,User#user.names,User#user.surnames])),
  175. wf:info(?MODULE,"Session User: ~p",[User]),
  176. GameId = case wf:q(games_ids) of undefined -> ?GAMEID; Res -> Res end,
  177. put(okey_game_id, GameId),
  178. Token = auth_server:generate_token(GameId,User),
  179. wf:wire(protocol:attach(wf:f("'~s'",[Token]))),
  180. Pid = self(),
  181. spawn(fun() ->
  182. send_roster(Pid),
  183. already_online(Pid),
  184. case kvs:get(user,User#user.id) of
  185. {ok,U} -> wf:send(broadcast,{user_online,U});
  186. _ -> skip end
  187. end),
  188. ok;
  189. event(discard) ->
  190. TilesList = get(game_okey_tiles),
  191. DiscardCombo = wf:q(istaka),
  192. GameId = get(okey_game_id),
  193. case lists:keyfind(erlang:list_to_binary(DiscardCombo), 1, TilesList) of
  194. {_, {C, V}} ->
  195. wf:wire(protocol:discard(wf:to_list(GameId), wf:to_list(C), wf:to_list(V)));
  196. false -> wf:info(?MODULE,"Discard Combo: ~p",[DiscardCombo]) end;
  197. event(reveal) ->
  198. TilesList = case get(game_okey_tiles) of undefined -> []; T -> T end,
  199. Discarded = wf:q(istaka),
  200. GameId = get(okey_game_id),
  201. case lists:keyfind(wf:to_binary(Discarded), 1, TilesList) of
  202. {_, {CD, VD} = Key} ->
  203. Hand = [{C,V} || {_, {C, V}} <- lists:keydelete(Key, 2, TilesList) ],
  204. HandJS = "[[" ++ string:join([
  205. wf:f("tuple(atom('OkeyPiece'),~p,~p)",[C,V]) || {C,V} <- Hand],",") ++ "],[]]",
  206. RevealJS = protocol:reveal(wf:to_list(GameId),wf:f("~p",[CD]),wf:f("~p",[VD]),HandJS),
  207. wf:info(?MODULE,"RevealJS: ~p",[lists:flatten(RevealJS)]),
  208. wf:wire(RevealJS);
  209. _ ->
  210. wf:info(?MODULE,"error discarded ~p", Discarded)
  211. end;
  212. event(i_saw_okey) ->
  213. wf:info(?MODULE,"i_saw_okey!"),
  214. GameId = get(okey_game_id),
  215. wf:wire(protocol:i_saw_okey(wf:to_list(GameId)));
  216. event(i_have_8_tashes) ->
  217. wf:info(?MODULE,"i_gave_8_tashes!"),
  218. GameId = get(okey_game_id),
  219. wf:wire(protocol:i_have_8_tashes(wf:to_list(GameId)));
  220. event(pause) ->
  221. Action =
  222. case get(game_okey_pause) of
  223. X when X == resume orelse X == undefined ->
  224. put(game_okey_pause, pause),
  225. okey:update(pause, [#button{id = pause, body = "Resume", postback = pause}]),
  226. "pause";
  227. pause ->
  228. put(game_okey_pause, resume),
  229. okey:update(pause, [#button{id = pause, body = <<"Pause">>, postback = pause}]),
  230. "resume"
  231. end,
  232. GameId = get(okey_game_id),
  233. wf:wire(protocol:pause(wf:to_list(GameId), wf:f("~p", [Action])));
  234. %event({binary,M}) -> {ok,<<"Hello">>};
  235. event({client,{message,From,Name,To,Message}}) ->
  236. wf:info(?MODULE,"Online Chat Message from ~p(~p) to ~p:~n ~p~n",[From,Name,To,Message]),
  237. wf:send(To,{server,{chat_message,{From,Name},To,wf:to_binary(Message)}}),
  238. ok;
  239. %event({client,{chat,GameId,Name,Message}}) ->
  240. % wf:info(?MODULE,"In-Game Chat Message from ~p(~p):~n ~p~n",[GameId,Name,Message]),
  241. % ok;
  242. event({client,Message}) ->
  243. wf:info(?MODULE,"Client: ~p", [Message]),
  244. case wf:session(<<"game_pid">>) of
  245. undefined -> skip;
  246. GamePid -> SyncRes = game_session:process_request(GamePid, Message),
  247. wf:info(?MODULE,"Sync Result: ~p",[SyncRes]) end;
  248. event({server, {game_event, _, okey_game_started, Args}}) ->
  249. wf:info(?MODULE,"Game Started: ~p", [Args]),
  250. {_, Tiles} = lists:keyfind(tiles, 1, Args),
  251. TilesList = [tash(C, V) || {_, C, V} <- Tiles],
  252. wf:info(?MODULE,"Tile List: ~p",[TilesList]),
  253. case lists:keyfind(gosterge, 1, Args) of
  254. {_, {_, C, V}} ->
  255. wf:info(?MODULE,"Gosterge: ~p ~p",[C,V]),
  256. okey:update(gosterge, #label{id = gosterge, body = wf:to_binary(["Gosterge: ", wf:to_list(C), " ", wf:to_list(V)])});
  257. _ -> ok end,
  258. put(game_okey_tiles, TilesList),
  259. put(game_okey_pause, resume),
  260. redraw_istaka(TilesList);
  261. event({server, {game_event, _, okey_game_player_state, Args}}) ->
  262. wf:info(?MODULE,"Player State: ~p", [Args]),
  263. case lists:keyfind(whos_move, 1, Args) of
  264. {_, null} -> ok;
  265. {_, WhosMove} ->
  266. Players = get(okey_players),
  267. #player{label = X} = lists:keyfind(WhosMove, #player.id, Players),
  268. case X of
  269. null -> skip;
  270. false -> skip;
  271. X -> select(X), put(okey_turn_mark,X) end,
  272. case lists:keyfind(gosterge, 1, Args) of
  273. {_, {_, C, V}} ->
  274. okey:update(gosterge, #label{id = gosterge, body = wf:to_binary(["Gosterge: ", wf:to_list(C), " ", wf:to_list(V)])});
  275. _ -> ok end,
  276. {_, Tiles} = lists:keyfind(tiles, 1, Args),
  277. TilesList = [tash(C, V)|| {_, C, V} <- Tiles],
  278. wf:info(?MODULE,"Istaka on State"),
  279. redraw_istaka(TilesList),
  280. put(game_okey_tiles, TilesList),
  281. {_, Piles} = lists:keyfind(piles, 1, Args),
  282. UpdatedPlayers = [ begin
  283. Player = #player{discard = RightPileComboId}
  284. = lists:keyfind(PlayerId, #player.id, Players),
  285. ConvertedPile = [ tash(C, V) || {_, C, V} <- Pile],
  286. redraw_tiles(ConvertedPile, #dropdown{id = RightPileComboId}),
  287. Player#player{history = ConvertedPile}
  288. end || {PlayerId, Pile} <- Piles ],
  289. put(okey_players, lists:sort(fun(#player{label = E1}, #player{label = E2}) ->
  290. E1 < E2 end, UpdatedPlayers));
  291. _ -> ok end;
  292. event({server, {game_event, _, okey_tile_taken, Args}}) ->
  293. wf:info(?MODULE,"Taken: ~p", [Args]),
  294. Im = get(okey_im),
  295. {_, PlayerId} = lists:keyfind(player, 1, Args),
  296. case lists:keyfind(revealed, 1, Args) of
  297. {_, {_, C, V}} ->
  298. if Im == PlayerId ->
  299. TilesList = [ tash(C, V) | get(game_okey_tiles)],
  300. %%wf:info(?MODULE,"Tiles: ~p",[TilesList]),
  301. put(game_okey_tiles, TilesList),
  302. redraw_istaka(TilesList);
  303. true -> ok end,
  304. case lists:keyfind(pile, 1, Args) of
  305. {_, 1} -> %% have taken from left
  306. Players = get(okey_players),
  307. #player{take=From} = lists:keyfind(PlayerId,#player.id,Players),
  308. LeftPlayer = #player{discard=Combo,history=DiscardHistory}
  309. = lists:keyfind(From,#player.label,Players),
  310. History = lists:keydelete({C, V}, 2, DiscardHistory),
  311. redraw_tiles(History, #dropdown{id=Combo}),
  312. put(okey_players,update_players(LeftPlayer#player{history=History},Players));
  313. _ -> ok end;
  314. _ -> ok end;
  315. event({server, {game_event, _, okey_tile_discarded, Args}}) ->
  316. wf:info(?MODULE,"Discarded: ~p", [Args]),
  317. Im = get(okey_im),
  318. {_, PlayerId} = lists:keyfind(player, 1, Args),
  319. {_, {_, C, V}} = lists:keyfind(tile, 1, Args),
  320. if Im == PlayerId ->
  321. TilesListOld = get(game_okey_tiles),
  322. TilesList = lists:keydelete({C, V}, 2, TilesListOld),
  323. put(game_okey_tiles, TilesList),
  324. redraw_istaka(TilesList);
  325. true -> ok end,
  326. Players = get(okey_players),
  327. Player = #player{discard = RightPileComboId, history = OldRightPile}
  328. = lists:keyfind(PlayerId, #player.id, Players),
  329. NewRightPile = [ tash(C, V) | OldRightPile],
  330. redraw_tiles(NewRightPile, #dropdown{id = RightPileComboId}),
  331. UpdatedPlayer = Player#player{history = NewRightPile},
  332. UpdatedPlayers = update_players(UpdatedPlayer, Players),
  333. put(okey_players, UpdatedPlayers);
  334. event({server,{game_event, _Game, okey_turn_timeout, Args}}) ->
  335. wf:info(?MODULE,"Turn Timeout: ~p", [Args]);
  336. %%event({server, {game_paused, _, _Gameid, Action, Who, _}}) ->
  337. %% Im = get(okey_im),
  338. %%
  339. %% if Im =/= Who ->
  340. %% put(game_okey_pause, Action),
  341. %% okey:update(pause, [#button{id = pause, body = case Action of pause -> "Resume"; resume -> "Pause" end, postback = pause}]);
  342. %% true -> ok end;
  343. event({server, {game_event, _, okey_game_info, Args}}) ->
  344. wf:info(?MODULE,"Game Info: ~p", [Args]),
  345. {_, PlayersInfo} = lists:keyfind(players, 1, Args),
  346. [okey:update(ElementId, [Element]) || {ElementId, Element} <- ?RESET_ELEMENTS],
  347. PlayersTempl = [
  348. #player{label = player1, discard = h1, take = player4},
  349. #player{label = player2, discard = h2, take = player1},
  350. #player{label = player3, discard = h3, take = player2},
  351. #player{label = player4, discard = h4, take = player3}],
  352. Players = lists:zipwith(fun(Players, #'PlayerInfo'{id = Id} = PI) ->
  353. Players#player{id=Id,info=PI} end, PlayersTempl, PlayersInfo),
  354. put(okey_players, Players),
  355. redraw_players(Players);
  356. event({server,{game_event, _, player_left, Args}}) ->
  357. wf:info(?MODULE,"Player Left: ~p", [Args]),
  358. {_, OldPlayerId} = lists:keyfind(player, 1, Args),
  359. {_, PI} = lists:keyfind(replacement, 1, Args),
  360. #'PlayerInfo'{id = NewPlayerId} = PI,
  361. OldPlayers = get(okey_players),
  362. OldPlayer = lists:keyfind(OldPlayerId, #player.id, OldPlayers),
  363. NewPlayers = update_players(OldPlayer#player{id=NewPlayerId,info=PI}, OldPlayers),
  364. put(okey_players, NewPlayers),
  365. redraw_players(NewPlayers),
  366. case get(okey_turn_mark) of undefined -> ok; X -> select(X) end;
  367. event({server,{game_event, _, okey_next_turn, Args}}) ->
  368. wf:info(?MODULE,"Next Turn: ~p", [Args]),
  369. {player, PlayerId} = lists:keyfind(player, 1, Args),
  370. #player{label = LabelId} = lists:keyfind(PlayerId, #player.id, get(okey_players)),
  371. case get(okey_turn_mark) of
  372. undefined -> ok;
  373. OldLabelId -> unselect(OldLabelId) end,
  374. select(LabelId),
  375. put(okey_turn_mark, LabelId);
  376. event({server,{roster_group,List}}) -> skip;
  377. event({server,terminate}) -> event(terminate);
  378. event({server,{update_score,Score}}) ->
  379. User = user(),
  380. NewUser = User#user{tokens=game:plist_setkey(score,1,User#user.tokens,{score,Score})},
  381. gproc:set_value({p,l,broadcast},{wf:peer(?REQ),NewUser}),
  382. wf:info(?MODULE,"User Process Updated Score ~p ~p",[User#user.id,Score]),
  383. event({user_online,NewUser}),
  384. ok;
  385. event({register,User}) -> wf:info(?MODULE,"Register: ~p",[User]), kvs:add(User), wf:user(User);
  386. event({login,User}) -> wf:info(?MODULE,"Login: ~p",[User]), kvs:put(User), wf:user(User), event(init);
  387. event({counter,Res}) -> Pid = self(), spawn(fun() -> Pid ! {server,{online_number,length(game:online())}} end);
  388. event({user_online,User}) -> wf:info(?MODULE,"User ~p goes Online",[User#user.id]), self() ! {server,{online,User#user.id,User#user.names,User#user.surnames,score(User)}};
  389. event({user_offline,User}) -> self() ! {server,{offline,User#user.id,User#user.names,User#user.surnames,score(User)}};
  390. event(_Event) -> wf:info(?MODULE,"Unknown Event: ~p", [_Event]).
  391. %api_event(X,Y,Z) -> avz:api_event(X,Y,Z).