Introduce egs_universes for universe handling. Review and move send_021e and send_0222 to psu_proto.
This commit is contained in:
parent
a6563c7378
commit
c91880be1f
@ -77,7 +77,7 @@
|
||||
%% General information.
|
||||
id, lid, pid, time, character,
|
||||
%% Location/state related information.
|
||||
instancepid, partypid, areatype, area, entryid, pos=#pos{x=0.0, y=0.0, z=0.0, dir=0.0}, shopid,
|
||||
uni, instancepid, partypid, areatype, area, entryid, pos=#pos{x=0.0, y=0.0, z=0.0, dir=0.0}, shopid,
|
||||
prev_area=#psu_area{questid=0, zoneid=0, mapid=0}, prev_entryid=0, %% universeid
|
||||
%% To be moved or deleted later on.
|
||||
setid=0 %% @todo Current area's set number. Move that to psu_instance probably.
|
||||
|
@ -28,3 +28,8 @@
|
||||
%% They can be modified freely without problem.
|
||||
%% Note that the port should be available and above 1024.
|
||||
{game_server, {<< 127, 0, 0, 1 >>, 12061}}.
|
||||
|
||||
%% Caps and limitations.
|
||||
|
||||
%% @doc Maximum level players can reach.
|
||||
{level_cap, 200}.
|
||||
|
BIN
priv/universes/myroom.en_US.txt
Normal file
BIN
priv/universes/myroom.en_US.txt
Normal file
Binary file not shown.
BIN
priv/universes/universes.en_US.txt
Normal file
BIN
priv/universes/universes.en_US.txt
Normal file
Binary file not shown.
@ -64,7 +64,7 @@ event({char_select_create, Slot, CharBin}, #state{gid=GID}) ->
|
||||
file:write_file(File, CharBin),
|
||||
file:write_file(io_lib:format("~s.options", [File]), << 0:128, 4, 0:56 >>);
|
||||
|
||||
%% @doc Load the selected character into the game's universe.
|
||||
%% @doc Load the selected character into the game's default universe.
|
||||
event({char_select_enter, Slot, _BackToPreviousField}, State=#state{gid=GID}) ->
|
||||
{ok, User} = egs_user_model:read(GID),
|
||||
Folder = egs_accounts:get_folder(GID),
|
||||
@ -76,7 +76,9 @@ event({char_select_enter, Slot, _BackToPreviousField}, State=#state{gid=GID}) ->
|
||||
Appearance = psu_appearance:binary_to_tuple(Race, AppearanceBin),
|
||||
Options = psu_characters:options_binary_to_tuple(OptionsBin),
|
||||
Character = #characters{slot=Slot, name=Name, race=Race, gender=Gender, class=Class, appearance=Appearance, options=Options}, % TODO: temporary set the slot here, won't be needed later
|
||||
User2 = User#egs_user_model{character=Character, area=#psu_area{questid=1100000, zoneid=7, mapid=9202}, entryid=0},
|
||||
UniID = egs_universes:defaultid(),
|
||||
egs_universes:enter(UniID),
|
||||
User2 = User#egs_user_model{uni=UniID, character=Character, area=#psu_area{questid=1100000, zoneid=7, mapid=9202}, entryid=0},
|
||||
egs_user_model:write(User2),
|
||||
egs_user_model:item_add(GID, 16#11010000, #psu_special_item_variables{}),
|
||||
egs_user_model:item_add(GID, 16#11020000, #psu_special_item_variables{}),
|
||||
@ -87,7 +89,6 @@ event({char_select_enter, Slot, _BackToPreviousField}, State=#state{gid=GID}) ->
|
||||
egs_user_model:item_add(GID, 16#01010b00, #psu_striking_weapon_item_variables{current_pp=99, max_pp=100, element=#psu_element{type=3, percent=50}}),
|
||||
{ok, User3} = egs_user_model:read(GID),
|
||||
State2 = State#state{slot=Slot},
|
||||
mnesia:dirty_update_counter(counters, population, 1),
|
||||
psu_game:char_load(User3, State2),
|
||||
{ok, egs_game, State2}.
|
||||
|
||||
|
@ -199,7 +199,7 @@ raw(Command, _Data, State) ->
|
||||
|
||||
%% Events.
|
||||
|
||||
%% @todo When changing lobby to the room, 0230 must also be sent. Same when going from room to lobby.
|
||||
%% @todo When changing lobby to the room, or room to lobby, we must perform an universe change.
|
||||
%% @todo Probably move area_load inside the event and make other events call this one when needed.
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID}, State) ->
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID, 16#ffffffff}, State);
|
||||
@ -751,44 +751,37 @@ event(player_type_capabilities_request, _State) ->
|
||||
event(ppcube_request, _State) ->
|
||||
psu_game:send_1a04();
|
||||
|
||||
%% @doc Uni cube handler.
|
||||
event(unicube_request, _State) ->
|
||||
psu_game:send_021e();
|
||||
event(unicube_request, State) ->
|
||||
psu_proto:send_021e(egs_universes:all(), State);
|
||||
|
||||
%% @doc Uni selection handler. Selecting anything reloads the character which will then be sent to its associated area.
|
||||
%% @todo When selecting 'Your room', load a default room.
|
||||
%% @todo When selecting 'Reload', reload the character in the current lobby.
|
||||
%% @todo Delete NPC characters and stop the party on entering myroom too.
|
||||
%% @todo When selecting 'Your room', don't load a default room that's not yours.
|
||||
event({unicube_select, cancel, _EntryID}, _State) ->
|
||||
ignore;
|
||||
event({unicube_select, Selection, EntryID}, State=#state{gid=GID}) ->
|
||||
{ok, User} = egs_user_model:read(GID),
|
||||
case Selection of
|
||||
cancel -> ignore;
|
||||
16#ffffffff ->
|
||||
log("uni selection (my room)"),
|
||||
psu_proto:send_0230(State),
|
||||
% 0220
|
||||
{ok, User} = egs_user_model:read(GID),
|
||||
User2 = User#egs_user_model{area=#psu_area{questid=1120000, zoneid=0, mapid=100}, entryid=0},
|
||||
egs_user_model:write(User2),
|
||||
psu_game:char_load(User2, State);
|
||||
_UniID ->
|
||||
log("uni selection (reload)"),
|
||||
psu_proto:send_0230(State),
|
||||
% 0220
|
||||
{ok, User} = egs_user_model:read(GID),
|
||||
case User#egs_user_model.partypid of
|
||||
undefined ->
|
||||
ignore;
|
||||
PartyPid ->
|
||||
%% @todo Replace stop by leave when leaving stops the party correctly when nobody's there anymore.
|
||||
%~ psu_party:leave(User#egs_user_model.partypid, User#egs_user_model.id)
|
||||
{ok, NPCList} = psu_party:get_npc(PartyPid),
|
||||
[egs_user_model:delete(NPCGID) || {_Spot, NPCGID} <- NPCList],
|
||||
psu_party:stop(PartyPid)
|
||||
end,
|
||||
User2 = User#egs_user_model{entryid=EntryID},
|
||||
egs_user_model:write(User2),
|
||||
psu_game:char_load(User2, State)
|
||||
end.
|
||||
UniID = egs_universes:myroomid(),
|
||||
User2 = User#egs_user_model{uni=UniID, area=#psu_area{questid=1120000, zoneid=0, mapid=100}, entryid=0};
|
||||
_ ->
|
||||
UniID = Selection,
|
||||
User2 = User#egs_user_model{uni=UniID, entryid=EntryID}
|
||||
end,
|
||||
psu_proto:send_0230(State),
|
||||
%% 0220
|
||||
case User#egs_user_model.partypid of
|
||||
undefined -> ignore;
|
||||
PartyPid ->
|
||||
%% @todo Replace stop by leave when leaving stops the party correctly when nobody's there anymore.
|
||||
%~ psu_party:leave(User#egs_user_model.partypid, User#egs_user_model.id)
|
||||
{ok, NPCList} = psu_party:get_npc(PartyPid),
|
||||
[egs_user_model:delete(NPCGID) || {_Spot, NPCGID} <- NPCList],
|
||||
psu_party:stop(PartyPid)
|
||||
end,
|
||||
egs_user_model:write(User2),
|
||||
egs_universes:leave(User#egs_user_model.uni),
|
||||
egs_universes:enter(UniID),
|
||||
psu_game:char_load(User2, State).
|
||||
|
||||
%% Internal.
|
||||
|
||||
|
@ -37,7 +37,6 @@ start_link(Port) ->
|
||||
on_exit(Pid) ->
|
||||
case egs_user_model:read({pid, Pid}) of
|
||||
{ok, User} ->
|
||||
mnesia:dirty_update_counter(counters, population, -1),
|
||||
case User#egs_user_model.partypid of
|
||||
undefined ->
|
||||
ignore;
|
||||
@ -47,6 +46,7 @@ on_exit(Pid) ->
|
||||
psu_party:stop(PartyPid)
|
||||
end,
|
||||
egs_user_model:delete(User#egs_user_model.id),
|
||||
egs_universes:leave(User#egs_user_model.uni),
|
||||
{ok, List} = egs_user_model:select({neighbors, User}),
|
||||
lists:foreach(fun(Other) -> Other#egs_user_model.pid ! {egs, player_unspawn, User} end, List),
|
||||
io:format("game (~p): quit~n", [User#egs_user_model.id]);
|
||||
|
@ -60,6 +60,7 @@ init([]) ->
|
||||
{egs_shops_db, {egs_shops_db, start_link, []}, permanent, 5000, worker, dynamic},
|
||||
{egs_accounts, {egs_accounts, start_link, []}, permanent, 5000, worker, dynamic},
|
||||
{egs_counters, {egs_counters, start_link, []}, permanent, 5000, worker, dynamic},
|
||||
{egs_universes, {egs_universes, start_link, []}, permanent, 5000, worker, dynamic},
|
||||
{egs_user_model, {egs_user_model, start_link, []}, permanent, 5000, worker, dynamic},
|
||||
{egs_game_server, {egs_game_server, start_link, [GamePort]}, permanent, 5000, worker, dynamic}
|
||||
],
|
||||
|
133
src/egs_universes.erl
Normal file
133
src/egs_universes.erl
Normal file
@ -0,0 +1,133 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010 Loïc Hoguin.
|
||||
%% @doc EGS universes handler.
|
||||
%%
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_universes).
|
||||
-behavior(gen_server).
|
||||
-export([start_link/0, stop/0, all/0, defaultid/0, enter/1, leave/1, myroomid/0, read/1, reload/0]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
%% Use the module name for the server's name.
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%% Default universe IDs.
|
||||
-define(MYROOM_ID, 21).
|
||||
-define(DEFAULT_ID, 26).
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link() -> {ok,Pid::pid()}
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%% @spec stop() -> stopped
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @spec all() -> term()
|
||||
all() ->
|
||||
gen_server:call(?SERVER, all).
|
||||
|
||||
%% @spec defaultid() -> 26
|
||||
%% @doc Return the default universe, Uni 01, with ID 26.
|
||||
defaultid() ->
|
||||
?DEFAULT_ID.
|
||||
|
||||
%% @spec enter(UniID) -> term()
|
||||
enter(UniID) ->
|
||||
gen_server:cast(?SERVER, {enter, UniID}).
|
||||
|
||||
%% @spec leave(UniID) -> term()
|
||||
leave(UniID) ->
|
||||
gen_server:cast(?SERVER, {leave, UniID}).
|
||||
|
||||
%% @spec myroomid() -> 21
|
||||
%% @doc Return the ID for the myroom universe.
|
||||
myroomid() ->
|
||||
?MYROOM_ID.
|
||||
|
||||
%% @spec read(UniID) -> term()
|
||||
read(UniID) ->
|
||||
gen_server:call(?SERVER, {read, UniID}).
|
||||
|
||||
%% @spec reload() -> ok
|
||||
reload() ->
|
||||
gen_server:cast(?SERVER, reload).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
{ok, [create_myroom()|create_unis()]}.
|
||||
|
||||
handle_call(all, _From, State) ->
|
||||
{reply, State, State};
|
||||
|
||||
handle_call({read, UniID}, _From, State) ->
|
||||
{reply, proplists:get_value(UniID, State), State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast({enter, UniID}, State) ->
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State),
|
||||
State2 = proplists:delete(UniID, State),
|
||||
State3 = [{UniID, {Type, Name, NbPlayers + 1, MaxPlayers}}|State2],
|
||||
State4 = lists:keysort(1, State3),
|
||||
{noreply, State4};
|
||||
|
||||
handle_cast({leave, UniID}, State) ->
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State),
|
||||
State2 = proplists:delete(UniID, State),
|
||||
State3 = [{UniID, {Type, Name, NbPlayers - 1, MaxPlayers}}|State2],
|
||||
State4 = lists:keysort(1, State3),
|
||||
{noreply, State4};
|
||||
|
||||
handle_cast(reload, _State) ->
|
||||
{noreply, [create_myroom()|create_unis()]};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% Internal.
|
||||
|
||||
%% @doc Max players defaults to 5000 for now.
|
||||
create_myroom() ->
|
||||
{ok, << 16#fffe:16, MyRoomName/binary >>} = file:read_file("priv/universes/myroom.en_US.txt"),
|
||||
{?MYROOM_ID, {myroom, MyRoomName, 0, 5000}}.
|
||||
|
||||
%% @doc Max players defaults to 1000 for now.
|
||||
create_unis() ->
|
||||
{ok, << 16#fffe:16, Universes/binary >>} = file:read_file("priv/universes/universes.en_US.txt"),
|
||||
Universes2 = re:split(Universes, "\n."),
|
||||
create_unis(Universes2, ?DEFAULT_ID, []).
|
||||
create_unis([], _UniID, Acc) ->
|
||||
lists:reverse(Acc);
|
||||
create_unis([Name|Tail], UniID, Acc) ->
|
||||
create_unis(Tail, UniID + 2, [{UniID, {universe, Name, 0, 1000}}|Acc]).
|
@ -19,7 +19,7 @@
|
||||
|
||||
-module(egs_user_model).
|
||||
-behavior(gen_server).
|
||||
-export([start_link/0, stop/0, count/0, read/1, select/1, write/1, delete/1, item_nth/2, item_add/3, item_qty_add/3, shop_enter/2, shop_leave/1, shop_get/1, money_add/2]). %% API.
|
||||
-export([start_link/0, stop/0, read/1, select/1, write/1, delete/1, item_nth/2, item_add/3, item_qty_add/3, shop_enter/2, shop_leave/1, shop_get/1, money_add/2]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
%% Use the module name for the server's name and for the table name.
|
||||
@ -46,10 +46,6 @@ start_link() ->
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @spec count() -> {ok, Count}
|
||||
count() ->
|
||||
gen_server:call(?SERVER, count).
|
||||
|
||||
%% @spec read({pid, Pid}) -> {ok, User} | {error, badarg}
|
||||
%% @spec read(ID) -> {ok, User} | {error, badarg}
|
||||
read(ID) ->
|
||||
@ -95,10 +91,6 @@ init([]) ->
|
||||
error_logger:info_report("egs_user_model started"),
|
||||
{ok, undefined}.
|
||||
|
||||
handle_call(count, _From, State) ->
|
||||
Count = mnesia:dirty_update_counter(counters, population, 0),
|
||||
{reply, {ok, Count}, State};
|
||||
|
||||
handle_call({read, {pid, Pid}}, _From, State) ->
|
||||
List = do(qlc:q([X || X <- mnesia:table(?TABLE), X#?TABLE.pid =:= Pid])),
|
||||
case List of
|
||||
|
@ -35,7 +35,7 @@ char_load(User, State) ->
|
||||
send_1005((User#egs_user_model.character)#characters.name),
|
||||
psu_proto:send_1006(12, State),
|
||||
psu_proto:send_0210(State),
|
||||
send_0222(),
|
||||
psu_proto:send_0222(User#egs_user_model.uni, State),
|
||||
send_1500(User),
|
||||
send_1501(),
|
||||
send_1512(),
|
||||
@ -255,36 +255,6 @@ send_0204(DestUser, TargetUser, Action) ->
|
||||
16#00011300:32, DestGID:32/little-unsigned-integer, 0:64, TargetGID:32/little-unsigned-integer,
|
||||
TargetLID:32/little-unsigned-integer, Action:32/little-unsigned-integer >>).
|
||||
|
||||
%% @doc Send the list of available universes.
|
||||
send_021e() ->
|
||||
{ok, Count} = egs_user_model:count(),
|
||||
[StrCount] = io_lib:format("~b", [Count]),
|
||||
Unis = [{16#ffffffff, center, "Your Room", ""}, {1, justify, "Reload", " "}, {2, justify, "EGS Test", StrCount}],
|
||||
NbUnis = length(Unis),
|
||||
Bin = send_021e_build(Unis, []),
|
||||
send(<< 16#021e0300:32, 0:288, NbUnis:32/little-unsigned-integer, Bin/binary >>).
|
||||
|
||||
send_021e_build([], Acc) ->
|
||||
iolist_to_binary(lists:reverse(Acc));
|
||||
send_021e_build([{ID, Align, Name, Pop}|Tail], Acc) ->
|
||||
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
|
||||
UCS2Pop = << << X:8, 0:8 >> || X <- Pop >>,
|
||||
NamePadding = 8 * (32 - byte_size(UCS2Name)),
|
||||
PopPadding = 8 * (12 - byte_size(UCS2Pop)),
|
||||
IntAlign = case Align of justify -> 643; center -> 0 end,
|
||||
send_021e_build(Tail, [<< ID:32/little-unsigned-integer, 0:16, IntAlign:16/little-unsigned-integer, UCS2Name/binary, 0:NamePadding, UCS2Pop/binary, 0:PopPadding >>|Acc]).
|
||||
|
||||
%% @doc Send the current universe name and number.
|
||||
%% @todo Currently only have universe number 2, named EGS Test.
|
||||
%% @todo We must have a parameter indicating whether this is a room or a normal universe.
|
||||
send_0222() ->
|
||||
UCS2Name = << << X:8, 0:8 >> || X <- "EGS Test" >>,
|
||||
Padding = 8 * (44 - byte_size(UCS2Name)),
|
||||
UniID = 2,
|
||||
GID = get(gid),
|
||||
send(<< 16#02220300:32, 16#ffff:16, 0:16, 16#00001200:32, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
UniID:32/little, 16#01009e02:32, UCS2Name/binary, 0:Padding, 16#aa000000:32 >>).
|
||||
|
||||
%% @todo No idea!
|
||||
send_022c(A, B) ->
|
||||
send(<< (header(16#022c))/binary, A:16/little-unsigned-integer, B:16/little-unsigned-integer >>).
|
||||
|
@ -1301,6 +1301,33 @@ send_0216(IP, Port, #state{socket=Socket, gid=DestGID, lid=DestLID}) ->
|
||||
send_021b(#state{socket=Socket, gid=DestGID, lid=DestLID}) ->
|
||||
packet_send(Socket, << 16#021b0300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64 >>).
|
||||
|
||||
%% @doc Send the list of available universes.
|
||||
send_021e(Universes, #state{socket=Socket}) ->
|
||||
NbUnis = length(Universes),
|
||||
UnisBin = build_021e_uni(Universes, []),
|
||||
packet_send(Socket, << 16#021e0300:32, 0:288, NbUnis:32/little, UnisBin/binary >>).
|
||||
|
||||
build_021e_uni([], Acc) ->
|
||||
iolist_to_binary(lists:reverse(Acc));
|
||||
build_021e_uni([{_UniID, {myroom, Name, NbPlayers, _MaxPlayers}}|Tail], Acc) ->
|
||||
Padding = 8 * (44 - byte_size(Name)),
|
||||
Bin = << 16#ffffffff:32, NbPlayers:16/little, 0:16, Name/binary, 0:Padding >>,
|
||||
build_021e_uni(Tail, [Bin|Acc]);
|
||||
build_021e_uni([{UniID, {universe, Name, NbPlayers, _MaxPlayers}}|Tail], Acc) ->
|
||||
Padding = 8 * (32 - byte_size(Name)),
|
||||
PopString = lists:flatten(io_lib:format("~5b", [NbPlayers])),
|
||||
PopString2 = << << X:8, 0:8 >> || X <- PopString >>,
|
||||
Bin = << UniID:32/little, NbPlayers:16/little, 643:16/little, Name/binary, 0:Padding, PopString2/binary, 0:16 >>,
|
||||
build_021e_uni(Tail, [Bin|Acc]).
|
||||
|
||||
%% @doc Send the current universe info along with the current level cap.
|
||||
send_0222(UniID, #state{socket=Socket, gid=DestGID}) ->
|
||||
{_Type, Name, NbPlayers, MaxPlayers} = egs_universes:read(UniID),
|
||||
Padding = 8 * (44 - byte_size(Name)),
|
||||
LevelCap = egs_conf:read(level_cap),
|
||||
packet_send(Socket, << 16#02220300:32, 16#ffff:16, 0:16, 16#00001200:32, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
|
||||
UniID:32/little, NbPlayers:16/little, MaxPlayers:16/little, Name/binary, 0:Padding, LevelCap:32/little >>).
|
||||
|
||||
%% @doc Send the auth key, or, in case of failure, a related error message.
|
||||
send_0223(AuthGID, AuthKey, #state{socket=Socket, gid=DestGID}) ->
|
||||
packet_send(Socket, << 16#02230300:32, 0:160, 16#00000f00:32, DestGID:32/little, 0:64, AuthGID:32/little, AuthKey:32/bits >>).
|
||||
|
Loading…
Reference in New Issue
Block a user