game: Fix permanent ghosts. Supervisor cleans up after crashes and normal exits.

This commit is contained in:
Loïc Hoguin 2010-06-13 19:05:28 +02:00
parent 3d9c91852a
commit dd26d4f1e5
2 changed files with 145 additions and 127 deletions

View File

@ -60,6 +60,12 @@ users_select(GID) ->
Val Val
end. end.
%% @doc Select exactly one user by its Pid. Return an #users record.
users_select_by_pid(Pid) ->
[User] = do(qlc:q([X || X <- mnesia:table(users), X#users.pid =:= Pid])),
User.
%% @doc Select all users. Return a list of #users records. %% @doc Select all users. Return a list of #users records.
users_select_all() -> users_select_all() ->

View File

@ -18,7 +18,7 @@
-module(egs_game). -module(egs_game).
-export([start/0]). % external -export([start/0]). % external
-export([listen/0, accept/1, process/2, char_select/3, area_load/6, loop/3, loop/4]). % internal -export([supervisor_init/0, supervisor/0, listen/1, accept/2, process/2, char_select/3, area_load/6, loop/3, loop/4]). % internal
-include("include/records.hrl"). -include("include/records.hrl").
-include("include/network.hrl"). -include("include/network.hrl").
@ -27,25 +27,55 @@
%% @doc Start the game server. %% @doc Start the game server.
start() -> start() ->
Pid = spawn_link(?MODULE, listen, []), SPid = spawn(?MODULE, supervisor_init, []),
Pid. LPid = spawn(?MODULE, listen, [SPid]),
[{listener, LPid}, {supervisor, SPid}].
%% @doc Game processes supervisor initialization.
supervisor_init() ->
process_flag(trap_exit, true),
supervisor().
%% @doc Game processes supervisor. Make sure everything is cleaned up when an unexpected error occurs.
supervisor() ->
receive
{link, Pid} ->
link(Pid);
{'EXIT', Pid, _} ->
supervisor_close(Pid);
_ ->
reload
after 5000 ->
reload
end,
?MODULE:supervisor().
%% @doc Close the connection for the given user and cleanup.
supervisor_close(Pid) ->
User = egs_db:users_select_by_pid(Pid),
log(User#users.gid, "quit"),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(User)),
egs_db:users_delete(User#users.gid).
%% @doc Listen for connections. %% @doc Listen for connections.
listen() -> listen(SPid) ->
process_flag(trap_exit, true),
{ok, LSocket} = ssl:listen(?GAME_PORT, ?GAME_LISTEN_OPTIONS), {ok, LSocket} = ssl:listen(?GAME_PORT, ?GAME_LISTEN_OPTIONS),
?MODULE:accept(LSocket). ?MODULE:accept(LSocket, SPid).
%% @doc Accept connections. %% @doc Accept connections.
accept(LSocket) -> accept(LSocket, SPid) ->
case ssl:transport_accept(LSocket, 5000) of case ssl:transport_accept(LSocket, 5000) of
{ok, CSocket} -> {ok, CSocket} ->
ssl:ssl_accept(CSocket), ssl:ssl_accept(CSocket),
try try
send_0202(CSocket), send_0202(CSocket),
Pid = spawn_link(?MODULE, process, [CSocket, 0]), Pid = spawn(?MODULE, process, [CSocket, 0]),
SPid ! {link, Pid},
ssl:controlling_process(CSocket, Pid) ssl:controlling_process(CSocket, Pid)
catch catch
_:_ -> _:_ ->
@ -54,7 +84,7 @@ accept(LSocket) ->
_ -> _ ->
reload reload
end, end,
?MODULE:accept(LSocket). ?MODULE:accept(LSocket, SPid).
%% @doc Process the new connections. %% @doc Process the new connections.
%% Send an hello packet, authenticate the user and send him to character select. %% Send an hello packet, authenticate the user and send him to character select.
@ -218,33 +248,29 @@ counter_load(CSocket, GID, QuestID, ZoneID, MapID, EntryID) ->
AreaName = "Mission counter", AreaName = "Mission counter",
QuestFile = "data/lobby/counter.quest.nbl", QuestFile = "data/lobby/counter.quest.nbl",
ZoneFile = "data/lobby/counter.zone.nbl", ZoneFile = "data/lobby/counter.zone.nbl",
try % broadcast unspawn to other people
% broadcast unspawn to other people lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)), % load counter
send_0c00(CSocket, GID, 16#7fffffff), send_0c00(CSocket, GID, 16#7fffffff),
send_020e(CSocket, QuestFile), send_020e(CSocket, QuestFile),
send_0a05(CSocket, GID), send_0a05(CSocket, GID),
% 010d % 010d
send_0200(CSocket, GID, mission), send_0200(CSocket, GID, mission),
send_020f(CSocket, ZoneFile), send_020f(CSocket, ZoneFile),
send_0205(CSocket, 0, 0, 0), send_0205(CSocket, 0, 0, 0),
send_100e(CSocket, GID, 16#7fffffff, 0, 0, AreaName, EntryID), send_100e(CSocket, GID, 16#7fffffff, 0, 0, AreaName, EntryID),
send_0215(CSocket, GID, 0), send_0215(CSocket, GID, 0),
send_0215(CSocket, GID, 0), send_0215(CSocket, GID, 0),
send_020c(CSocket), send_020c(CSocket),
send_1202(CSocket, GID), send_1202(CSocket, GID),
send_1204(CSocket, GID), send_1204(CSocket, GID),
send_1206(CSocket, GID), send_1206(CSocket, GID),
send_1207(CSocket, GID), send_1207(CSocket, GID),
send_1212(CSocket, GID), send_1212(CSocket, GID),
send_0201(CSocket, GID, User, Char), send_0201(CSocket, GID, User, Char),
send_0a06(CSocket, GID), send_0a06(CSocket, GID),
send_0208(CSocket, GID), send_0208(CSocket, GID),
send_0236(CSocket, GID) send_0236(CSocket, GID).
catch
_:_ ->
close(CSocket, GID)
end.
%% @doc Load the given map as a standard lobby. %% @doc Load the given map as a standard lobby.
%% @todo Probably save the map type in the users table. %% @todo Probably save the map type in the users table.
@ -285,86 +311,81 @@ area_load(CSocket, GID, AreaType, IsStart, OldUser, User, QuestFile, ZoneFile, A
true -> true ->
ZoneChange = if OldUser#users.questid =:= User#users.questid, OldUser#users.zoneid =:= User#users.zoneid -> false; true -> true end ZoneChange = if OldUser#users.questid =:= User#users.questid, OldUser#users.zoneid =:= User#users.zoneid -> false; true -> true end
end, end,
try % broadcast spawn and unspawn to other people
% broadcast spawn and unspawn to other people lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)), if AreaType =:= lobby ->
if AreaType =:= lobby -> lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_spawn, User} end, egs_db:users_select_others_in_area(User));
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_spawn, User} end, egs_db:users_select_others_in_area(User)); true -> ignore
true -> ignore end,
end, % load area
% load area if QuestChange =:= true ->
if QuestChange =:= true -> % reload the character if entering or leaving the room quest
% reload the character if entering or leaving the room quest if OldUser#users.questid =:= 1120000; User#users.questid =:= 1120000 ->
if OldUser#users.questid =:= 1120000; User#users.questid =:= 1120000 -> char_load(CSocket, GID, Char, Options, User#users.charnumber);
char_load(CSocket, GID, Char, Options, User#users.charnumber); true -> ignore
true -> ignore end,
end, % load new quest
% load new quest send_0c00(CSocket, GID, User#users.questid),
send_0c00(CSocket, GID, User#users.questid), send_020e(CSocket, QuestFile);
send_020e(CSocket, QuestFile); true -> ignore
true -> ignore end,
end, if IsStart =:= true ->
if IsStart =:= true -> send_0215(CSocket, GID, 16#ffffffff);
send_0215(CSocket, GID, 16#ffffffff); true -> ignore
true -> ignore end,
end, if ZoneChange =:= true ->
if ZoneChange =:= true -> % load new zone
% load new zone send_0a05(CSocket, GID),
send_0a05(CSocket, GID), if AreaType =:= lobby ->
if AreaType =:= lobby -> send_0111(CSocket, GID);
send_0111(CSocket, GID); true -> ignore
true -> ignore end,
end, % 010d
% 010d send_0200(CSocket, GID, AreaType),
send_0200(CSocket, GID, AreaType), send_020f(CSocket, ZoneFile);
send_020f(CSocket, ZoneFile); true -> ignore
true -> ignore end,
end, send_0205(CSocket, User#users.zoneid, User#users.mapid, User#users.entryid),
send_0205(CSocket, User#users.zoneid, User#users.mapid, User#users.entryid), send_100e(CSocket, GID, User#users.questid, User#users.zoneid, User#users.mapid, AreaName, 16#ffffffff),
send_100e(CSocket, GID, User#users.questid, User#users.zoneid, User#users.mapid, AreaName, 16#ffffffff), if AreaType =:= mission ->
if AreaType =:= mission -> send_0215(CSocket, GID, 0),
send_0215(CSocket, GID, 0), if IsStart =:= true ->
if IsStart =:= true -> send_0215(CSocket, GID, 0),
send_0215(CSocket, GID, 0), send_0c09(CSocket, GID);
send_0c09(CSocket, GID); true -> ignore
true -> ignore end;
end; true ->
true -> send_020c(CSocket)
send_020c(CSocket) end,
end, case AreaType of
case AreaType of myroom ->
myroom -> myroom_send_packet(CSocket, "p/packet1332.bin"),
myroom_send_packet(CSocket, "p/packet1332.bin"), send_1202(CSocket, GID),
send_1202(CSocket, GID), send_1204(CSocket, GID),
send_1204(CSocket, GID), send_1206(CSocket, GID);
send_1206(CSocket, GID); mission ->
mission -> send_1202(CSocket, GID),
send_1202(CSocket, GID), send_1204(CSocket, GID),
send_1204(CSocket, GID), send_1206(CSocket, GID),
send_1206(CSocket, GID), send_1207(CSocket, GID);
send_1207(CSocket, GID); _ -> ignore
_ -> ignore end,
end, if AreaType /= spaceport ->
if AreaType /= spaceport -> send_1212(CSocket, GID);
send_1212(CSocket, GID); true -> ignore
true -> ignore end,
end, if AreaType =:= myroom ->
if AreaType =:= myroom -> myroom_send_packet(CSocket, "p/packet1309.bin");
myroom_send_packet(CSocket, "p/packet1309.bin"); true -> ignore
true -> ignore end,
end, send_0201(CSocket, GID, User, Char),
send_0201(CSocket, GID, User, Char), if ZoneChange =:= true ->
if ZoneChange =:= true -> send_0a06(CSocket, GID);
send_0a06(CSocket, GID); true -> ignore
true -> ignore end,
end, send_0233(CSocket, GID, egs_db:users_select_others_in_area(User)),
send_0233(CSocket, GID, egs_db:users_select_others_in_area(User)), send_0208(CSocket, GID),
send_0208(CSocket, GID), send_0236(CSocket, GID).
send_0236(CSocket, GID)
catch
_:_ ->
close(CSocket, GID)
end.
myroom_send_packet(CSocket, Filename) -> myroom_send_packet(CSocket, Filename) ->
{ok, << _:32, File/bits >>} = file:read_file(Filename), {ok, << _:32, File/bits >>} = file:read_file(Filename),
@ -401,9 +422,9 @@ loop(CSocket, GID, Version, SoFar) ->
[dispatch(CSocket, GID, Version, Orig) || Orig <- Packets], [dispatch(CSocket, GID, Version, Orig) || Orig <- Packets],
?MODULE:loop(CSocket, GID, Version, Rest); ?MODULE:loop(CSocket, GID, Version, Rest);
{ssl_closed, _} -> {ssl_closed, _} ->
close(CSocket, GID); exit(ssl_closed);
{ssl_error, _, _} -> {ssl_error, _, _} ->
close(CSocket, GID); exit(ssl_error);
_ -> _ ->
?MODULE:loop(CSocket, GID, Version, SoFar) ?MODULE:loop(CSocket, GID, Version, SoFar)
after 1000 -> after 1000 ->
@ -411,15 +432,6 @@ loop(CSocket, GID, Version, SoFar) ->
?MODULE:loop(CSocket, GID, Version, SoFar) ?MODULE:loop(CSocket, GID, Version, SoFar)
end. end.
%% @doc Close the connection for the given user.
close(CSocket, GID) ->
log(GID, "quit"),
User = egs_db:users_select(GID),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(User)),
egs_db:users_delete(GID),
ssl:close(CSocket).
%% @doc Dispatch the command to the right handler. %% @doc Dispatch the command to the right handler.
%% Command 0b05 uses the channel for something else. Conflicts could occur. Better to just ignore it anyway. %% Command 0b05 uses the channel for something else. Conflicts could occur. Better to just ignore it anyway.