Permalink
Browse files

Introduce egs_universes for universe handling. Review and move send_0…

…21e and send_0222 to psu_proto.
  • Loading branch information...
Loïc Hoguin
Loïc Hoguin committed Oct 21, 2010
1 parent a6563c7 commit c91880be1f6073cb2b0c5ec1f51b01e60c53a5a1
View
@@ -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.
View
@@ -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}.
Binary file not shown.
Binary file not shown.
View
@@ -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}.
View
@@ -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.
View
@@ -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]);
View
@@ -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}
],
View
@@ -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]).
View
@@ -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
View
@@ -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 >>).
View
@@ -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 >>).

0 comments on commit c91880b

Please sign in to comment.