Work in progress on egs_net (will be amended)

This commit is contained in:
Loïc Hoguin 2012-05-16 12:33:12 +02:00
parent 9adab0ea87
commit ceea04c3b4
27 changed files with 2079 additions and 1935 deletions

View File

@ -20,12 +20,15 @@
%% Records.
%% @doc Client state. One per connected client.
-record(client, {
-record(egs_net, {
socket :: ssl:sslsocket(),
transport :: module(),
handler :: module(),
buffer = <<>> :: binary(),
keepalive = false :: boolean(),
gid = 0 :: egs:gid(),
slot = 0 :: egs:character_slot(), %% @todo Probably should remove this one from the state.
lid = 16#ffff :: egs:lid(),
slot = 0 :: 0..3,
areanb = 0 :: non_neg_integer()
}).
@ -70,7 +73,8 @@
prev_area = {0, 0, 0} :: egs:area(),
prev_entryid = 0 :: egs:entryid(),
%% To be moved or deleted later on.
instancepid :: pid()
instancepid :: pid(),
char
}).
%% Past this point needs to be reviewed.

View File

@ -1,4 +1,6 @@
{deps, [
{erlson, ".*", {git, "https://github.com/alavrik/erlson.git", "HEAD"}},
{ex_reloader, ".*", {git, "git://github.com/extend/ex_reloader.git", "HEAD"}},
{cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "HEAD"}}
]}.
{plugins, [erlson_rebar_plugin]}.

View File

@ -46,7 +46,6 @@ start() ->
ensure_started(crypto),
ensure_started(public_key),
ensure_started(ssl),
ssl:seed(crypto:rand_bytes(256)),
ensure_started(cowboy),
application:start(egs).

View File

@ -34,6 +34,7 @@ start(_Type, _StartArgs) ->
{ok, Pid} = egs_sup:start_link(),
application:set_env(egs_patch, patch_ports, egs_conf:read(patch_ports)),
application:start(egs_patch),
application:start(egs_store),
start_login_listeners(egs_conf:read(login_ports)),
{_ServerIP, GamePort} = egs_conf:read(game_server),
{ok, _GamePid} = cowboy:start_listener({game, GamePort}, 10,

64
apps/egs/src/egs_char.erl Normal file
View File

@ -0,0 +1,64 @@
-module(egs_char).
-export([new/6]).
-include_lib("erlson/include/erlson.hrl").
%% @todo Add the current location for backtopreviousfield
new(Slot, Name, Race, Gender, Class, Appearance) ->
#{
type=player,
slot=Slot,
name=Name,
race=Race,
gender=Gender,
class=Class,
appearance=Appearance,
level=1,
exp=0,
hunter_level=1,
hunter_exp=0,
ranger_level=1,
ranger_exp=0,
force_level=1,
force_exp=0,
acro_level=1,
acro_level=0,
blast_bar=0,
luck=3,
playtime=0,
%% current_uni,
%% current location + entryid
%% previous location + entryid
%% pids for such
money=1000000,
inventory=[],
card_comment= <<>>,
options=#{
brightness=4,
buttonhelp=0,
cam1stx=0,
cam1sty=0,
cam3rdx=0,
cam3rdy=0,
controller=0,
cursorpos=0,
cutin=0,
fnkeys=0,
lockon=0,
musicvolume=0,
radarmap=0,
sfxvolume=0,
sound=0,
textspeed=0,
vibration=0,
weaponswap=0
}}.

View File

@ -18,14 +18,11 @@
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
-module(egs_char_select).
-export([keepalive/1, info/2, cast/3, raw/3, event/2]).
-export([info/2, cast/3, event/2]).
-include_lib("erlson/include/erlson.hrl").
-include("include/records.hrl").
%% @doc Send a keepalive.
keepalive(Client) ->
egs_proto:send_keepalive(Client).
%% @doc We don't expect any message here.
info(_Msg, _Client) ->
ok.
@ -34,75 +31,56 @@ info(_Msg, _Client) ->
cast(_Command, _Data, _Client) ->
ok.
%% @doc Dismiss all raw commands with a log notice.
%% @todo Have a log event handler instead.
raw(Command, _Data, Client) ->
io:format("~p (~p): dismissed command ~4.16.0b~n", [?MODULE, Client#client.gid, Command]).
%% Events.
%% @doc Character screen selection request and delivery.
event(char_select_request, Client=#client{gid=GID}) ->
Folder = egs_accounts:get_folder(GID),
egs_proto:send_0d03(data_load(Folder, 0), data_load(Folder, 1), data_load(Folder, 2), data_load(Folder, 3), Client);
event(account_characters_request, Client) ->
{ok, Characters}
= egs_store:load_characters(egs_net:get_gid(Client), [0, 1, 2, 3]),
egs_net:account_characters_response(Characters, Client);
%% @doc The options default to 0 for everything except brightness to 4.
%% @todo Don't forget to check for the character's name.
%% @todo Don't forget to check for the character's name (in egs_net).
%% 00F7 is the RGBA color control character.
%% 03F7 is the RGB color control character.
event({char_select_create, Slot, CharBin}, #client{gid=GID}) ->
%% check for valid character appearance
%~ << _Name:512, RaceID:8, GenderID:8, _TypeID:8, AppearanceBin:776/bits, _/bits >> = CharBin,
%~ Race = proplists:get_value(RaceID, [{0, human}, {1, newman}, {2, cast}, {3, beast}]),
%~ Gender = proplists:get_value(GenderID, [{0, male}, {1, female}]),
%~ Appearance = psu_appearance:binary_to_tuple(Race, AppearanceBin),
%~ psu_appearance:validate_char_create(Race, Gender, Appearance),
%% end of check, continue doing it wrong past that point for now
Folder = egs_accounts:get_folder(GID),
Dir = io_lib:format("save/~s", [Folder]),
File = io_lib:format("~s/~b-character", [Dir, Slot]),
_ = file:make_dir(Dir),
file:write_file(File, CharBin),
file:write_file(io_lib:format("~s.options", [File]), << 0:128, 4, 0:56 >>);
event({account_create_character, Slot, Name, Race, Gender, Class, Appearance},
Client) ->
Character = egs_char:new(Slot, Name, Race, Gender, Class, Appearance),
egs_store:save_character(egs_net:get_gid(Client), Slot, Character);
%% @doc Load the selected character into the game's default universe.
%% @todo Isn't very pretty to call egs_game from here but that will do for now.
event({char_select_enter, Slot, _BackToPreviousField}, Client=#client{gid=GID}) ->
Folder = egs_accounts:get_folder(GID),
[{status, 1}, {char, CharBin}, {options, OptionsBin}] = data_load(Folder, Slot),
<< Name:512/bits, RaceBin:8, GenderBin:8, ClassBin:8, AppearanceBin:776/bits, _/bits >> = CharBin,
Race = psu_characters:race_binary_to_atom(RaceBin),
Gender = psu_characters:gender_binary_to_atom(GenderBin),
Class = psu_characters:class_binary_to_atom(ClassBin),
Appearance = psu_appearance:binary_to_tuple(Race, AppearanceBin),
Options = psu_characters:options_binary_to_tuple(OptionsBin),
event({system_character_select, Slot, _BackToPreviousField}, Client) ->
GID = egs_net:get_gid(Client),
{ok, 1, Char} = egs_store:load_character(GID, Slot),
UniID = egs_universes:defaultid(),
egs_universes:enter(UniID),
User = #users{gid=GID, pid=self(), uni=UniID, slot=Slot, name=Name, race=Race, gender=Gender,
class=Class, appearance=Appearance, options=Options, area={1100000, 0, 4}, entryid=0},
%% @todo Handle users properly, just giving Character directly.
Name = Char.name,
NameBin = << Name/binary, 0:(512 - bit_size(Name)) >>,
Race = Char.race,
Gender = Char.gender,
Class = Char.class,
Appearance = Char.appearance,
Options = Char.options,
User = #users{gid=GID, pid=self(), uni=UniID, slot=Slot,
name=NameBin, race=Race, gender=Gender, class=Class,
appearance=Appearance, options=Options,
area={1100000, 0, 4}, entryid=0, char=Char},
egs_users:write(User),
egs_users:item_add(GID, 16#11010000, #psu_special_item_variables{}),
egs_users:item_add(GID, 16#11020000, #psu_special_item_variables{}),
egs_users:item_add(GID, 16#11020100, #psu_special_item_variables{}),
egs_users:item_add(GID, 16#11020200, #psu_special_item_variables{}),
egs_users:item_add(GID, 16#01010900, #psu_striking_weapon_item_variables{current_pp=99, max_pp=100, element=#psu_element{type=1, percent=50}}),
egs_users:item_add(GID, 16#01010a00, #psu_striking_weapon_item_variables{current_pp=99, max_pp=100, element=#psu_element{type=2, percent=50}}),
egs_users:item_add(GID, 16#01010b00, #psu_striking_weapon_item_variables{current_pp=99, max_pp=100, element=#psu_element{type=3, percent=50}}),
egs_users:item_add(GID, 16#01010900, #psu_striking_weapon_item_variables{
current_pp=99, max_pp=100, element=#psu_element{type=1, percent=50}}),
egs_users:item_add(GID, 16#01010a00, #psu_striking_weapon_item_variables{
current_pp=99, max_pp=100, element=#psu_element{type=2, percent=50}}),
egs_users:item_add(GID, 16#01010b00, #psu_striking_weapon_item_variables{
current_pp=99, max_pp=100, element=#psu_element{type=3, percent=50}}),
{ok, User2} = egs_users:read(GID),
Client2 = Client#client{slot=Slot},
egs_game:char_load(User2, Client2),
{ok, egs_game, Client2}.
egs_game:char_load(User2, Client),
Client2 = egs_net:set_handler(egs_game, Client),
{ok, Client2};
%% Internal.
%% @doc Load the given character's data.
%% @todo This function is temporary until we get permanent accounts.
data_load(Folder, Number) ->
Filename = io_lib:format("save/~s/~b-character", [Folder, Number]),
case file:read_file(Filename) of
{ok, Char} ->
{ok, Options} = file:read_file(io_lib:format("~s.options", [Filename])),
[{status, 1}, {char, Char}, {options, Options}];
{error, _Reason} ->
[{status, 0}, {char, << 0:2208 >>}]
end.
event({client_hardware, GPU, CPU}, Client) ->
ok.

View File

@ -18,17 +18,13 @@
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
-module(egs_game).
-export([keepalive/1, info/2, cast/3, raw/3, event/2]).
-export([info/2, cast/2, raw/3, event/2]).
-export([char_load/2]). %% Hopefully temporary export.
-include("include/records.hrl").
%% @doc Send a keepalive.
keepalive(Client) ->
egs_proto:send_keepalive(Client).
%% @doc Forward the broadcasted command to the client.
info({egs, cast, Command}, Client=#client{gid=GID}) ->
info({egs, cast, Command}, Client=#egs_net{gid=GID}) ->
<< A:64/bits, _:32, B:96/bits, _:64, C/bits >> = Command,
egs_proto:packet_send(Client, << A/binary, 16#00011300:32, B/binary, 16#00011300:32, GID:32/little, C/binary >>);
@ -60,30 +56,30 @@ info({egs, warp, QuestID, ZoneID, MapID, EntryID}, Client) ->
%% @todo Handle broadcasting better than that. Review the commands at the same time.
%% @doc Position change. Save the position and then dispatch it.
cast(16#0503, Data, Client=#client{gid=GID}) ->
cast({16#0503, Data}, Client=#egs_net{gid=GID}) ->
<< _:424, Dir:24/little, _PrevCoords:96, X:32/little-float, Y:32/little-float, Z:32/little-float,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, _:32 >> = Data,
FloatDir = Dir / 46603.375,
{ok, User} = egs_users:read(GID),
NewUser = User#users{pos={X, Y, Z, FloatDir}, area={QuestID, ZoneID, MapID}, entryid=EntryID},
egs_users:write(NewUser),
cast(valid, Data, Client);
cast({valid, Data}, Client);
%% @doc Stand still. Save the position and then dispatch it.
cast(16#0514, Data, Client=#client{gid=GID}) ->
cast({16#0514, Data}, Client=#egs_net{gid=GID}) ->
<< _:424, Dir:24/little, X:32/little-float, Y:32/little-float, Z:32/little-float,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, _/bits >> = Data,
FloatDir = Dir / 46603.375,
{ok, User} = egs_users:read(GID),
NewUser = User#users{pos={X, Y, Z, FloatDir}, area={QuestID, ZoneID, MapID}, entryid=EntryID},
egs_users:write(NewUser),
cast(valid, Data, Client);
cast({valid, Data}, Client);
%% @doc Default broadcast handler. Dispatch the command to everyone.
%% We clean up the command and use the real GID and LID of the user, disregarding what was sent and possibly tampered with.
%% Only a handful of commands are allowed to broadcast. An user tampering with it would get disconnected instantly.
%% @todo Don't query the user data everytime! Keep the needed information in the Client.
cast(Command, Data, #client{gid=GID, lid=LID})
cast({Command, Data}, #egs_net{gid=GID, lid=LID})
when Command =:= 16#0101;
Command =:= 16#0102;
Command =:= 16#0104;
@ -101,7 +97,7 @@ cast(Command, Data, #client{gid=GID, lid=LID})
%% @todo Handle this packet properly.
%% @todo Spawn cleared response event shouldn't be handled following this packet but when we see the spawn actually dead HP-wise.
%% @todo Type shouldn't be :32 but it seems when the later 16 have something it's not a spawn event.
raw(16#0402, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
raw(16#0402, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
<< SpawnID:32/little, _:64, Type:32/little, _:64 >> = Data,
case Type of
7 -> % spawn cleared @todo 1201 sent back with same values apparently, but not always
@ -119,23 +115,23 @@ raw(16#0402, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
%% @todo 3rd Unsafe Passage C, EventID 10 BlockID 2 = mission cleared?
raw(16#0404, << _:352, Data/bits >>, Client) ->
<< EventID:8, BlockID:8, _:16, Value:8, _/bits >> = Data,
io:format("~p: unknown command 0404: eventid ~b blockid ~b value ~b~n", [Client#client.gid, EventID, BlockID, Value]),
io:format("~p: unknown command 0404: eventid ~b blockid ~b value ~b~n", [Client#egs_net.gid, EventID, BlockID, Value]),
egs_proto:send_1205(EventID, BlockID, Value, Client);
%% @todo Used in the tutorial. Not sure what it does. Give an item (the PA) maybe?
%% @todo Probably should ignore that until more is known.
raw(16#0a09, _Data, Client=#client{gid=GID}) ->
raw(16#0a09, _Data, Client=#egs_net{gid=GID}) ->
egs_proto:packet_send(Client, << 16#0a090300:32, 0:32, 16#00011300:32, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, 16#00003300:32, 0:32 >>);
%% @todo Figure out this command.
raw(16#0c11, << _:352, A:32/little, B:32/little >>, Client=#client{gid=GID}) ->
raw(16#0c11, << _:352, A:32/little, B:32/little >>, Client=#egs_net{gid=GID}) ->
io:format("~p: 0c11 ~p ~p~n", [GID, A, B]),
egs_proto:packet_send(Client, << 16#0c120300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, A:32/little, 1:32/little >>);
%% @doc Set flag handler. Associate a new flag with the character.
%% Just reply with a success value for now.
%% @todo God save the flags.
raw(16#0d04, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
raw(16#0d04, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
<< Flag:128/bits, A:16/bits, _:8, B/bits >> = Data,
io:format("~p: flag handler for ~s~n", [GID, re:replace(Flag, "\\0+", "", [global, {return, binary}])]),
egs_proto:packet_send(Client, << 16#0d040300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, Flag/binary, A/binary, 1, B/binary >>);
@ -143,7 +139,7 @@ raw(16#0d04, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
%% @doc Initialize a vehicle object.
%% @todo Find what are the many values, including the odd Whut value (and whether it's used in the reply).
%% @todo Separate the reply.
raw(16#0f00, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
raw(16#0f00, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
<< A:32/little, 0:16, B:16/little, 0:16, C:16/little, 0, Whut:8, D:16/little, 0:16,
E:16/little, 0:16, F:16/little, G:16/little, H:16/little, I:32/little >> = Data,
io:format("~p: init vehicle: ~b ~b ~b ~b ~b ~b ~b ~b ~b ~b~n", [GID, A, B, C, Whut, D, E, F, G, H, I]),
@ -154,7 +150,7 @@ raw(16#0f00, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
%% @doc Enter vehicle.
%% @todo Separate the reply.
raw(16#0f02, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
raw(16#0f02, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
<< A:32/little, B:32/little, C:32/little >> = Data,
io:format("~p: enter vehicle: ~b ~b ~b~n", [GID, A, B, C]),
HP = 100,
@ -162,7 +158,7 @@ raw(16#0f02, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
%% @doc Sent right after entering the vehicle. Can't move without it.
%% @todo Separate the reply.
raw(16#0f07, << _:352, Data/bits >>, Client=#client{gid=GID}) ->
raw(16#0f07, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
<< A:32/little, B:32/little >> = Data,
io:format("~p: after enter vehicle: ~b ~b~n", [GID, A, B]),
egs_proto:packet_send(Client, << 16#120f0300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, A:32/little, B:32/little >>);
@ -183,13 +179,13 @@ raw(16#1112, << _:352, Data/bits >>, Client) ->
%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race.
raw(16#1216, << _:352, Data/bits >>, Client) ->
<< Value:32/little >> = Data,
io:format("~p: command 1216 with value ~b~n", [Client#client.gid, Value]),
io:format("~p: command 1216 with value ~b~n", [Client#egs_net.gid, Value]),
egs_proto:send_1216(Value, Client);
%% @doc Dismiss all unknown raw commands with a log notice.
%% @todo Have a log event handler instead.
raw(Command, _Data, Client) ->
io:format("~p (~p): dismissed command ~4.16.0b~n", [?MODULE, Client#client.gid, Command]).
io:format("~p (~p): dismissed command ~4.16.0b~n", [?MODULE, Client#egs_net.gid, Command]).
%% Events.
@ -199,8 +195,8 @@ raw(Command, _Data, Client) ->
event({area_change, QuestID, ZoneID, MapID, EntryID}, Client) ->
event({area_change, QuestID, ZoneID, MapID, EntryID, 16#ffffffff}, Client);
event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos=16#ffffffff}, Client) ->
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#client.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
{ok, OldUser} = egs_users:read(Client#client.gid),
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#egs_net.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
{ok, OldUser} = egs_users:read(Client#egs_net.gid),
{OldQuestID, OldZoneID, _OldMapID} = OldUser#users.area,
QuestChange = OldQuestID /= QuestID,
ZoneChange = if OldQuestID =:= QuestID, OldZoneID =:= ZoneID -> false; true -> true end,
@ -221,7 +217,7 @@ event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos=16#ffffffff}, Clie
ZonePid = egs_quests:zone_pid(User2#users.questpid, ZoneID),
egs_zones:leave(User2#users.zonepid, User2#users.gid),
NewLID = egs_zones:enter(ZonePid, User2#users.gid),
NewClient = Client#client{lid=NewLID},
NewClient = Client#egs_net{lid=NewLID},
{ok, User3} = egs_users:read(User2#users.gid),
egs_proto:send_0a05(NewClient),
egs_proto:send_0111(User3, 6, NewClient),
@ -236,7 +232,7 @@ event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos=16#ffffffff}, Clie
%% Save the user.
egs_users:write(User3),
%% Load the player location.
Client2 = Client1#client{areanb=Client#client.areanb + 1},
Client2 = Client1#egs_net{areanb=Client#egs_net.areanb + 1},
egs_proto:send_0205(User3, IsSeasonal, Client2),
egs_proto:send_100e(User3#users.area, User3#users.entryid, AreaShortName, Client2),
%% Load the zone objects.
@ -258,19 +254,19 @@ event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos=16#ffffffff}, Clie
true -> ignore
end,
%% End of loading.
Client3 = Client2#client{areanb=Client2#client.areanb + 1},
Client3 = Client2#egs_net{areanb=Client2#egs_net.areanb + 1},
egs_proto:send_0208(Client3),
egs_proto:send_0236(Client3),
%% @todo Load APC characters.
{ok, Client3};
event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos}, Client) ->
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#client.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#egs_net.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
ignore;
%% @doc After the character has been (re)loaded, change the area he's in.
%% @todo The area_change event should probably not change the user's values.
%% @todo Remove that ugly code when the above is done.
event(char_load_complete, Client=#client{gid=GID}) ->
event(system_character_load_complete, Client=#egs_net{gid=GID}) ->
{ok, User=#users{area={QuestID, ZoneID, MapID}, entryid=EntryID}} = egs_users:read(GID),
egs_users:write(User#users{area={0, 0, 0}, entryid=0}),
event({area_change, QuestID, ZoneID, MapID, EntryID}, Client);
@ -281,7 +277,7 @@ event(char_load_complete, Client=#client{gid=GID}) ->
%% @todo In the case of NPC characters, when FromTypeID is 00001d00, check that the NPC is in the party and broadcast only to the party (probably).
%% @todo When the game doesn't find an NPC (probably) and forces it to talk like in the tutorial mission it seems FromTypeID, FromGID and Name are all 0.
%% @todo Make sure modifiers have correct values.
event({chat, _FromTypeID, FromGID, _FromName, Modifiers, ChatMsg}, #client{gid=UserGID}) ->
event({chat, _, FromGID, _, Modifiers, _, ChatMsg}, #egs_net{gid=UserGID}) ->
[BcastTypeID, BcastGID, BcastName] = case FromGID of
0 -> %% This probably shouldn't happen. Just make it crash on purpose.
io:format("~p: chat FromGID=0~n", [UserGID]),
@ -307,7 +303,7 @@ event(counter_background_locations_request, Client) ->
%% @todo Make sure non-mission counters follow the same loading process.
%% @todo Probably validate the From* values, to not send the player back inside a mission.
%% @todo Handle the LID change when entering counters.
event({counter_enter, CounterID, FromZoneID, FromMapID, FromEntryID}, Client=#client{gid=GID}) ->
event({counter_enter, CounterID, FromZoneID, FromMapID, FromEntryID}, Client=#egs_net{gid=GID}) ->
io:format("~p: counter load ~b~n", [GID, CounterID]),
{ok, OldUser} = egs_users:read(GID),
FromArea = {element(1, OldUser#users.area), FromZoneID, FromMapID},
@ -324,7 +320,7 @@ event({counter_enter, CounterID, FromZoneID, FromMapID, FromEntryID}, Client=#cl
egs_proto:send_010d(User, Client),
egs_proto:send_0200(0, mission, Client),
egs_proto:send_020f(ZoneData, 0, 255, Client),
Client2 = Client#client{areanb=Client#client.areanb + 1},
Client2 = Client#egs_net{areanb=Client#egs_net.areanb + 1},
egs_proto:send_0205(User, 0, Client2),
egs_proto:send_100e(CounterID, "Counter", Client2),
egs_proto:send_0215(0, Client2),
@ -341,7 +337,7 @@ event({counter_enter, CounterID, FromZoneID, FromMapID, FromEntryID}, Client=#cl
undefined -> ignore;
_ -> egs_proto:send_022c(0, 16#12, Client)
end,
Client3 = Client2#client{areanb=Client2#client.areanb + 1},
Client3 = Client2#egs_net{areanb=Client2#egs_net.areanb + 1},
egs_proto:send_0208(Client3),
egs_proto:send_0236(Client3),
{ok, Client3};
@ -351,7 +347,7 @@ event(counter_join_party_request, Client) ->
egs_proto:send_1701(Client);
%% @doc Leave mission counter handler.
event(counter_leave, Client=#client{gid=GID}) ->
event(counter_leave, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
PrevArea = User#users.prev_area,
event({area_change, element(1, PrevArea), element(2, PrevArea), element(3, PrevArea), User#users.prev_entryid}, Client);
@ -360,11 +356,11 @@ event(counter_leave, Client=#client{gid=GID}) ->
%% @todo Apparently background values 1 2 3 are never used on official servers. Find out why.
%% @todo Rename to counter_bg_request.
event({counter_options_request, CounterID}, Client) ->
io:format("~p: counter options request ~p~n", [Client#client.gid, CounterID]),
io:format("~p: counter options request ~p~n", [Client#egs_net.gid, CounterID]),
egs_proto:send_1711(egs_counters_db:bg(CounterID), Client);
%% @todo Handle when the party already exists! And stop doing it wrong.
event(counter_party_info_request, Client=#client{gid=GID}) ->
event(counter_party_info_request, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
egs_proto:send_1706(User#users.name, Client);
@ -374,16 +370,16 @@ event(counter_party_options_request, Client) ->
%% @doc Request the counter's quest files.
event({counter_quest_files_request, CounterID}, Client) ->
io:format("~p: counter quest files request ~p~n", [Client#client.gid, CounterID]),
io:format("~p: counter quest files request ~p~n", [Client#egs_net.gid, CounterID]),
egs_proto:send_0c06(egs_counters_db:pack(CounterID), Client);
%% @doc Counter available mission list request handler.
event({counter_quest_options_request, CounterID}, Client) ->
io:format("~p: counter quest options request ~p~n", [Client#client.gid, CounterID]),
io:format("~p: counter quest options request ~p~n", [Client#egs_net.gid, CounterID]),
egs_proto:send_0c10(egs_counters_db:opts(CounterID), Client);
%% @todo A and B are mostly unknown. Like most of everything else from the command 0e00...
event({hit, FromTargetID, ToTargetID, A, B}, Client=#client{gid=GID}) ->
event({hit, FromTargetID, ToTargetID, A, B}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
%% hit!
#hit_response{type=Type, user=NewUser, exp=HasEXP, damage=Damage, targethp=TargetHP, targetse=TargetSE, events=Events} = psu_instance:hit(User, FromTargetID, ToTargetID),
@ -427,7 +423,7 @@ event({item_description_request, ItemID}, Client) ->
%% @todo Currently use a separate file for the data sent for the weapons.
%% @todo TargetGID and TargetLID must be validated, they're either the player's or his NPC characters.
%% @todo Handle NPC characters properly.
event({item_equip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid=GID}) ->
event({item_equip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
case egs_users:item_nth(GID, ItemIndex) of
{ItemID, Variables} when element(1, Variables) =:= psu_special_item_variables ->
<< Category:8, _:24 >> = << ItemID:32 >>,
@ -462,7 +458,7 @@ event({item_equip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid=GI
ignore
end;
event({item_set_trap, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid=GID}) ->
event({item_set_trap, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
{ItemID, _Variables} = egs_users:item_nth(GID, ItemIndex),
egs_users:item_qty_add(GID, ItemIndex, -1),
<< Category:8, _:24 >> = << ItemID:32 >>,
@ -471,7 +467,7 @@ event({item_set_trap, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid
%% @todo A and B are unknown.
%% @see item_equip
event({item_unequip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid=GID}) ->
event({item_unequip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
Category = case ItemIndex of
% units would be 8, traps would be 12
19 -> 2; % armor
@ -485,12 +481,12 @@ event({item_unequip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#client{gid=
event(lobby_transport_request, Client) ->
egs_proto:send_0c08(Client);
event(lumilass_options_request, Client=#client{gid=GID}) ->
event(lumilass_options_request, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
egs_proto:send_1a03(User, Client);
%% @todo Probably replenish the player HP when entering a non-mission area rather than when aborting the mission?
event(mission_abort, Client=#client{gid=GID}) ->
event(mission_abort, Client=#egs_net{gid=GID}) ->
egs_proto:send_1006(11, Client),
{ok, User} = egs_users:read(GID),
%% delete the mission
@ -509,14 +505,14 @@ event(mission_abort, Client=#client{gid=GID}) ->
%% @todo Forward the mission start to other players of the same party, whatever their location is.
event({mission_start, QuestID}, Client) ->
io:format("~p: mission start ~b~n", [Client#client.gid, QuestID]),
io:format("~p: mission start ~b~n", [Client#egs_net.gid, QuestID]),
egs_proto:send_1020(Client),
egs_proto:send_1015(QuestID, Client),
egs_proto:send_0c02(Client);
%% @doc Force the invite of an NPC character while inside a mission. Mostly used by story missions.
%% Note that the NPC is often removed and reinvited between block/cutscenes.
event({npc_force_invite, NPCid}, Client=#client{gid=GID}) ->
event({npc_force_invite, NPCid}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
%% Create NPC.
io:format("~p: npc force invite ~p~n", [GID, NPCid]),
@ -544,7 +540,7 @@ event({npc_force_invite, NPCid}, Client=#client{gid=GID}) ->
egs_proto:send_1601(PartyPos, Client);
%% @todo Also at the end send a 101a (NPC:16, PartyPos:16, ffffffff). Not sure about PartyPos.
event({npc_invite, NPCid}, Client=#client{gid=GID}) ->
event({npc_invite, NPCid}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
%% Create NPC.
io:format("~p: invited npcid ~b~n", [GID, NPCid]),
@ -566,7 +562,7 @@ event({npc_invite, NPCid}, Client=#client{gid=GID}) ->
egs_proto:send_101a(NPCid, PartyPos, Client);
%% @todo Should be 0115(money) 010a03(confirm sale).
event({npc_shop_buy, ShopItemIndex, QuantityOrColor}, Client=#client{gid=GID}) ->
event({npc_shop_buy, ShopItemIndex, QuantityOrColor}, Client=#egs_net{gid=GID}) ->
ShopID = egs_users:shop_get(GID),
ItemID = egs_shops_db:nth(ShopID, ShopItemIndex + 1),
io:format("~p: npc shop ~p buy itemid ~8.16.0b quantity/color+1 ~p~n", [GID, ShopID, ItemID, QuantityOrColor]),
@ -602,12 +598,12 @@ event({npc_shop_buy, ShopItemIndex, QuantityOrColor}, Client=#client{gid=GID}) -
UCS2Name/binary, 0:NamePadding, RarityInt:8, Category:8, SellPrice:32/little, (egs_proto:build_item_constants(Constants))/binary >>);
%% @todo Currently send the normal items shop for all shops, differentiate.
event({npc_shop_enter, ShopID}, Client=#client{gid=GID}) ->
event({npc_shop_enter, ShopID}, Client=#egs_net{gid=GID}) ->
io:format("~p: npc shop enter ~p~n", [GID, ShopID]),
egs_users:shop_enter(GID, ShopID),
egs_proto:send_010a(egs_shops_db:read(ShopID), Client);
event({npc_shop_leave, ShopID}, Client=#client{gid=GID}) ->
event({npc_shop_leave, ShopID}, Client=#egs_net{gid=GID}) ->
io:format("~p: npc shop leave ~p~n", [GID, ShopID]),
egs_users:shop_leave(GID),
egs_proto:packet_send(Client, << 16#010a0300:32, 0:64, GID:32/little, 0:64, 16#00011300:32,
@ -615,13 +611,13 @@ event({npc_shop_leave, ShopID}, Client=#client{gid=GID}) ->
%% @todo Should be 0115(money) 010a03(confirm sale).
event({npc_shop_sell, InventoryItemIndex, Quantity}, Client) ->
io:format("~p: npc shop sell itemindex ~p quantity ~p~n", [Client#client.gid, InventoryItemIndex, Quantity]);
io:format("~p: npc shop sell itemindex ~p quantity ~p~n", [Client#egs_net.gid, InventoryItemIndex, Quantity]);
%% @todo First 1a02 value should be non-0.
%% @todo Could the 2nd 1a02 parameter simply be the shop type or something?
%% @todo Although the values replied should be right, they seem mostly ignored by the client.
event({npc_shop_request, ShopID}, Client) ->
io:format("~p: npc shop request ~p~n", [Client#client.gid, ShopID]),
io:format("~p: npc shop request ~p~n", [Client#egs_net.gid, ShopID]),
case ShopID of
80 -> egs_proto:send_1a02(17, 17, 3, 9, Client); %% lumilass
90 -> egs_proto:send_1a02(5, 1, 4, 5, Client); %% parum weapon grinding
@ -665,14 +661,14 @@ event({object_crystal_activate, ObjectID}, Client) ->
event({object_event_trigger, BlockID, EventID}, Client) ->
egs_proto:send_1205(EventID, BlockID, 0, Client);
event({object_goggle_target_activate, ObjectID}, Client=#client{gid=GID}) ->
event({object_goggle_target_activate, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client),
egs_proto:send_1213(ObjectID, 8, Client);
%% @todo Make NPC characters heal too.
event({object_healing_pad_tick, [_PartyPos]}, Client=#client{gid=GID}) ->
event({object_healing_pad_tick, [_PartyPos]}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
if User#users.currenthp =:= User#users.maxhp -> ignore;
true ->
@ -684,38 +680,38 @@ event({object_healing_pad_tick, [_PartyPos]}, Client=#client{gid=GID}) ->
egs_proto:send_0111(User2, 4, Client)
end;
event({object_key_console_enable, ObjectID}, Client=#client{gid=GID}) ->
event({object_key_console_enable, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, [EventID|_]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client),
egs_proto:send_1213(ObjectID, 1, Client);
event({object_key_console_init, ObjectID}, Client=#client{gid=GID}) ->
event({object_key_console_init, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, [_, EventID, _]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client);
event({object_key_console_open_gate, ObjectID}, Client=#client{gid=GID}) ->
event({object_key_console_open_gate, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, [_, _, EventID]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client),
egs_proto:send_1213(ObjectID, 1, Client);
%% @todo Now that it's separate from object_key_console_enable, handle it better than that, don't need a list of events.
event({object_key_enable, ObjectID}, Client=#client{gid=GID}) ->
event({object_key_enable, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, [EventID|_]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client),
egs_proto:send_1213(ObjectID, 1, Client);
%% @todo Some switch objects apparently work differently, like the light switch in Mines in MAG'.
event({object_switch_off, ObjectID}, Client=#client{gid=GID}) ->
event({object_switch_off, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 1, Client),
egs_proto:send_1213(ObjectID, 0, Client);
event({object_switch_on, ObjectID}, Client=#client{gid=GID}) ->
event({object_switch_on, ObjectID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
egs_proto:send_1205(EventID, BlockID, 0, Client),
@ -728,7 +724,7 @@ event({object_vehicle_boost_respawn, ObjectID}, Client) ->
egs_proto:send_1213(ObjectID, 0, Client);
%% @todo Second send_1211 argument should be User#users.lid. Fix when it's correctly handled.
event({object_warp_take, BlockID, ListNb, ObjectNb}, Client=#client{gid=GID}) ->
event({object_warp_take, BlockID, ListNb, ObjectNb}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
Pos = psu_instance:warp_event(User#users.instancepid, element(2, User#users.area), BlockID, ListNb, ObjectNb),
NewUser = User#users{pos=Pos},
@ -737,7 +733,7 @@ event({object_warp_take, BlockID, ListNb, ObjectNb}, Client=#client{gid=GID}) ->
egs_proto:send_1211(16#ffffffff, 0, 14, 0, Client);
%% @todo Don't send_0204 if the player is removed from the party while in the lobby I guess.
event({party_remove_member, PartyPos}, Client=#client{gid=GID}) ->
event({party_remove_member, PartyPos}, Client=#egs_net{gid=GID}) ->
io:format("~p: party remove member ~b~n", [GID, PartyPos]),
{ok, DestUser} = egs_users:read(GID),
{ok, RemovedGID} = psu_party:get_member(DestUser#users.partypid, PartyPos),
@ -751,13 +747,13 @@ event({party_remove_member, PartyPos}, Client=#client{gid=GID}) ->
egs_proto:send_0204(RemovedUser, Client),
egs_proto:send_0215(0, Client);
event({player_options_change, Options}, #client{gid=GID, slot=Slot}) ->
event({player_options_change, Options}, #egs_net{gid=GID, slot=Slot}) ->
Folder = egs_accounts:get_folder(GID),
file:write_file(io_lib:format("save/~s/~b-character.options", [Folder, Slot]), Options);
%% @todo If the player has a scape, use it! Otherwise red screen.
%% @todo Right now we force revive with a dummy HP value.
event(player_death, Client=#client{gid=GID}) ->
event(player_death, Client=#egs_net{gid=GID}) ->
% @todo send_0115(GID, 16#ffffffff, LV=1, EXP=idk, Money=1000), % apparently sent everytime you die...
%% use scape:
NewHP = 10,
@ -770,7 +766,7 @@ event(player_death, Client=#client{gid=GID}) ->
%~ egs_proto:send_0111(User2, 3, 1, Client);
%% @todo Refill the player's HP to maximum, remove SEs etc.
event(player_death_return_to_lobby, Client=#client{gid=GID}) ->
event(player_death_return_to_lobby, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
PrevArea = User#users.prev_area,
event({area_change, element(1, PrevArea), element(2, PrevArea), element(3, PrevArea), User#users.prev_entryid}, Client);
@ -778,7 +774,7 @@ event(player_death_return_to_lobby, Client=#client{gid=GID}) ->
event(player_type_availability_request, Client) ->
egs_proto:send_1a07(Client);
event(player_type_capabilities_request, Client) ->
event(character_type_capabilities_request, Client) ->
egs_proto:send_0113(Client);
event(ppcube_request, Client) ->
@ -790,7 +786,7 @@ event(unicube_request, Client) ->
%% @todo When selecting 'Your room', don't load a default room that's not yours.
event({unicube_select, cancel, _EntryID}, _Client) ->
ignore;
event({unicube_select, Selection, EntryID}, Client=#client{gid=GID}) ->
event({unicube_select, Selection, EntryID}, Client=#egs_net{gid=GID}) ->
{ok, User} = egs_users:read(GID),
case Selection of
16#ffffffff ->
@ -825,7 +821,7 @@ events(Events, Client) ->
%% @doc Load and send the character information to the client.
char_load(User, Client) ->
egs_proto:send_0d01(User, Client),
egs_net:account_character(User#users.char, Client),
%% 0246
egs_proto:send_0a0a(User#users.inventory, Client),
egs_proto:send_1006(5, 0, Client), %% @todo The 0 here is PartyPos, save it in User.
@ -833,7 +829,7 @@ char_load(User, Client) ->
egs_proto:send_1006(12, Client),
egs_proto:send_0210(Client),
egs_proto:send_0222(User#users.uni, Client),
egs_proto:send_1500(User, Client),
egs_net:comm_own_card(User#users.char, Client),
egs_proto:send_1501(Client),
egs_proto:send_1512(Client),
%% 0303

View File

@ -31,16 +31,14 @@ start_link(_ListenerPid, Socket, Transport, []) ->
%% @todo Handle keepalive messages globally?
init(Socket, Transport) ->
{ok, _TRef} = timer:send_interval(5000, {egs, keepalive}),
Client = #client{socket=Socket, transport=Transport,
gid=egs_accounts:tmp_gid()},
egs_proto:send_0202(Client),
try
egs_network:recv(<<>>, egs_login, Client)
after
terminate()
end.
Client = egs_net:init(Socket, Transport, egs_login,
egs_accounts:tmp_gid()),
egs_net:system_hello(Client),
catch egs_net:loop(Client),
terminate().
-spec terminate() -> ok.
%% @todo Just use monitors to handle cleanups.
%% @todo Cleanup the instance process if there's nobody in it anymore.
%% @todo Leave party instead of stopping it.
%% @todo Fix the crash when user isn't in egs_users yet.

View File

@ -18,14 +18,10 @@
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
-module(egs_login).
-export([keepalive/1, info/2, cast/3, raw/3, event/2]).
-export([info/2, cast/3, event/2]).
-include("include/records.hrl").
%% @doc Don't keep alive here, authentication should go fast.
keepalive(_Client) ->
ok.
%% @doc We don't expect any message here.
info(_Msg, _Client) ->
ok.
@ -34,55 +30,52 @@ info(_Msg, _Client) ->
cast(_Command, _Data, _Client) ->
ok.
%% Raw commands.
%% @doc Dismiss all raw commands with a log notice.
%% @todo Have a log event handler instead.
raw(Command, _Data, _Client) ->
io:format("~p: dismissed command ~4.16.0b~n", [?MODULE, Command]).
%% Events.
%% @doc Reject version < 2.0009.2.
%% @todo Reject wrong platforms too.
event({system_client_version_info, _Entrance, _Language, _Platform, Version},
Client=#client{socket=Socket, transport=Transport})
%% @todo Put the URL in a configuration file.
event({client_version, _Entrance, _Language, _Platform, Version}, Client)
when Version < 2009002 ->
egs_proto:send_0231("http://psumods.co.uk/forums/comments.php?DiscussionID=40#Item_1", Client),
{ok, ErrorMsg} = file:read_file("priv/login/error_version.txt"),
egs_proto:send_0223(ErrorMsg, Client),
Transport:close(Socket),
egs_net:system_open_url(<<"http://psumods.co.uk/forums/comments.php?DiscussionID=40#Item_1">>, Client),
{ok, Error} = file:read_file("priv/login/error_version.txt"),
egs_net:system_auth_error(Error, Client),
egs_net:terminate(Client),
closed;
event({system_client_version_info, _Entrance, _Language, _Platform, _Version}, _Client) ->
event({client_version, _Entrance, _Language, _Platform, _Version}, _Client) ->
ok;
%% @doc Game server info request handler.
event(system_game_server_request,
Client=#client{socket=Socket, transport=Transport}) ->
event(system_game_server_request, Client) ->
{ServerIP, ServerPort} = egs_conf:read(game_server),
egs_proto:send_0216(ServerIP, ServerPort, Client),
Transport:close(Socket),
egs_net:system_game_server_response(ServerIP, ServerPort, Client),
egs_net:terminate(Client),
closed;
%% @doc Authenticate the user by pattern matching its saved state against the key received.
%% If the user is authenticated, send him the character flags list.
event({system_key_auth_request, AuthGID, AuthKey}, Client) ->
event({system_key_auth, AuthGID, AuthKey}, Client) ->
egs_accounts:key_auth(AuthGID, AuthKey),
Client2 = Client#client{gid=AuthGID},
egs_proto:send_0d05(Client2),
{ok, egs_char_select, Client2};
Client2 = egs_net:set_gid(AuthGID, Client),
ValueFlags = egs_conf:read(value_flags),
BoolFlags = egs_conf:read(bool_flags),
TempFlags = egs_conf:read(temp_flags),
egs_net:account_flags(ValueFlags, BoolFlags, TempFlags, Client2),
Client3 = egs_net:set_handler(egs_char_select, Client2),
Client4 = egs_net:set_keepalive(Client3),
{ok, Client4};
%% @doc Authentication request handler. Currently always succeed.
%% @todo Handle real GIDs whenever there's real authentication. GID is the second SessionID in the reply.
%% @todo Apparently it's possible to ask a question in the reply here. Used for free course on JP.
event({system_login_auth_request, Username, Password}, Client) ->
{ok, GID} = egs_accounts:login_auth(Username, Password),
{ok, AuthKey} = egs_accounts:key_auth_init(GID),
event({system_login_auth, Username, Password}, Client) ->
{ok, AuthGID} = egs_accounts:login_auth(Username, Password),
{ok, AuthKey} = egs_accounts:key_auth_init(AuthGID),
io:format("auth success for ~s ~s~n", [Username, Password]),
egs_proto:send_0223(GID, AuthKey, Client);
egs_net:system_key_auth_info(AuthGID, AuthKey, Client);
%% @doc MOTD request handler. Page number starts at 0.
%% @todo Currently ignore the language and send the same MOTD file to everyone.
event({system_motd_request, Page, _Language}, Client) ->
{ok, MOTD} = file:read_file("priv/login/motd.txt"),
egs_proto:send_0225(MOTD, Page, Client).
egs_net:system_motd_response(MOTD, Page, Client).

View File

@ -29,7 +29,7 @@ start_link(_ListenerPid, Socket, Transport, []) ->
-spec init(ssl:sslsocket(), module()) -> ok | closed.
init(Socket, Transport) ->
Client = #client{socket=Socket, transport=Transport,
gid=egs_accounts:tmp_gid()},
egs_proto:send_0202(Client),
egs_network:recv(<<>>, egs_login, Client).
Client = egs_net:init(Socket, Transport, egs_login,
egs_accounts:tmp_gid()),
egs_net:system_hello(Client),
egs_net:loop(Client).

View File

@ -1,86 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Login and game servers low-level network handling.
%%
%% 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_network).
-export([recv/3]). %% API.
-include("include/records.hrl").
%% API.
%% @doc Main loop for the network stack. Receive and handle messages.
recv(SoFar, CallbackMod, Client=#client{socket=Socket, transport=Transport}) ->
Transport:setopts(Socket, [{active, once}]),
{OK, Closed, Error} = Transport:messages(),
receive
{OK, _Any, Data} ->
{Commands, Rest} = split(<< SoFar/bits, Data/bits >>, []),
case dispatch(Commands, CallbackMod, CallbackMod, Client) of
{ok, NextCallbackMod, NewClient} ->
?MODULE:recv(Rest, NextCallbackMod, NewClient);
closed -> closed
end;
{Closed, _} -> closed;
{Error, _, _} -> closed;
{egs, keepalive} ->
CallbackMod:keepalive(Client),
?MODULE:recv(SoFar, CallbackMod, Client);
Tuple when element(1, Tuple) =:= egs ->
case CallbackMod:info(Tuple, Client) of
{ok, NewClient} -> ?MODULE:recv(SoFar, CallbackMod, NewClient);
_Any -> ?MODULE:recv(SoFar, CallbackMod, Client)
end
end.
%% Internal.
%% @doc Dispatch the commands received to the right handler.
dispatch([], _CallbackMod, NextMod, Client) ->
{ok, NextMod, Client};
dispatch([Data|Tail], CallbackMod, NextMod, Client) ->
Ret = case egs_proto:parse(Data) of
{command, Command, Channel} ->
case Channel of
1 -> CallbackMod:cast(Command, Data, Client);
_ -> CallbackMod:raw(Command, Data, Client)
end;
ignore ->
ignore;
Event ->
CallbackMod:event(Event, Client)
end,
case Ret of
{ok, NewMod, NewClient} ->
dispatch(Tail, CallbackMod, NewMod, NewClient);
{ok, NewClient} ->
dispatch(Tail, CallbackMod, NextMod, NewClient);
closed ->
closed;
_Any ->
dispatch(Tail, CallbackMod, NextMod, Client)
end.
%% @doc Split the network data received into commands.
split(Data, Acc) when byte_size(Data) < 4 ->
{lists:reverse(Acc), Data};
split(<< Size:32/little, _/bits >> = Data, Acc) when Size > byte_size(Data) ->
{lists:reverse(Acc), Data};
split(<< Size:32/little, _/bits >> = Data, Acc) ->
<< Split:Size/binary, Rest/bits >> = Data,
split(Rest, [Split|Acc]).

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
-file("/usr/lib/erlang/lib/parsetools-2.0.6/include/leexinc.hrl", 0).
-file("/usr/lib/erlang/lib/parsetools-2.0.7/include/leexinc.hrl", 0).
%% The source of this file is part of leex distribution, as such it
%% has the same Copyright as the other files in the leex
%% distribution. The Copyright is defined in the accompanying file
@ -477,7 +477,7 @@ escape_char($s) -> $\s; %% \s = SPC
escape_char($d) -> $\d; %% \d = DEL
escape_char(C) -> C.
-file("/usr/lib/erlang/lib/parsetools-2.0.6/include/leexinc.hrl", 14).
-file("/usr/lib/erlang/lib/parsetools-2.0.7/include/leexinc.hrl", 14).
format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)];
format_error({user,S}) -> S.
@ -1073,4 +1073,4 @@ yyaction_6(TokenLine) ->
yyaction_7() ->
skip_token .
-file("/usr/lib/erlang/lib/parsetools-2.0.6/include/leexinc.hrl", 282).
-file("/usr/lib/erlang/lib/parsetools-2.0.7/include/leexinc.hrl", 282).

View File

@ -4,11 +4,11 @@
unwrap({_,_,V}) -> V.
-file("/usr/lib/erlang/lib/parsetools-2.0.6/include/yeccpre.hrl", 0).
-file("/usr/lib/erlang/lib/parsetools-2.0.7/include/yeccpre.hrl", 0).
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@ -35,10 +35,11 @@ parse(Tokens) ->
-spec parse_and_scan({function() | {atom(), atom()}, [_]}
| {atom(), atom(), [_]}) -> yecc_ret().
parse_and_scan({F, A}) -> % Fun or {M, F}
parse_and_scan({F, A}) ->
yeccpars0([], {{F, A}, no_line}, 0, [], []);
parse_and_scan({M, F, A}) ->
yeccpars0([], {{{M, F}, A}, no_line}, 0, [], []).
Arity = length(A),
yeccpars0([], {{fun M:F/Arity, A}, no_line}, 0, [], []).
-spec format_error(any()) -> [char() | list()].
format_error(Message) ->
@ -74,7 +75,7 @@ yeccpars0(Tokens, Tzr, State, States, Vstack) ->
Error
end.
yecc_error_type(function_clause, [{?MODULE,F,ArityOrArgs} | _]) ->
yecc_error_type(function_clause, [{?MODULE,F,ArityOrArgs,_} | _]) ->
case atom_to_list(F) of
"yeccgoto_" ++ SymbolL ->
{ok,[{atom,_,Symbol}],_} = erl_scan:string(SymbolL),
@ -187,7 +188,7 @@ yecctoken2string(Other) ->
-file("src/egs_script_parser.erl", 190).
-file("src/egs_script_parser.erl", 191).
yeccpars2(0=S, Cat, Ss, Stack, T, Ts, Tzr) ->
yeccpars2_0(S, Cat, Ss, Stack, T, Ts, Tzr);

View File

@ -1,254 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Character appearance functions.
%%
%% 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(psu_appearance).
-export([binary_to_tuple/2, tuple_to_binary/2, validate_char_create/3]).
-include("include/records.hrl").
%% @doc Convert the binary character creation appearance data into a tuple.
%% The lineshield color is ignored and set to 0 (neutral) by default instead.
%% The badge is always set to 0 (none). Only beasts can later change it.
%% The lips color and intensity is ignored and set to the default values {32767, 32767, 0} (flesh races only).
binary_to_tuple(cast, Binary) ->
<< VoiceType:8, VoicePitch:8, _:24, Torso:32/unsigned-integer, Legs:32/unsigned-integer, Arms:32/unsigned-integer,
Ears:32/unsigned-integer, Face:32/unsigned-integer, HeadType:32/unsigned-integer, MainColor:8, _:24,
_:8, Eyebrows:8, Eyelashes:8, EyesGroup:8, Eyes:8, _:24, EyesColorY:32/little, EyesColorX:32/little,
_:96, BodyColor:32/little, SubColor:32/little, HairstyleColorY:32/little,
HairstyleColorX:32/little, Proportion:32/little, ProportionBoxX:32/little,
ProportionBoxY:32/little, FaceBoxX:32/little, FaceBoxY:32/little >> = Binary,
{metal_appearance, VoiceType, VoicePitch, Torso, Legs, Arms, Ears, Face, HeadType, MainColor, 0,
Eyebrows, Eyelashes, EyesGroup, Eyes, EyesColorY, EyesColorX, BodyColor, SubColor, HairstyleColorY, HairstyleColorX,
Proportion, ProportionBoxX, ProportionBoxY, FaceBoxX, FaceBoxY};
binary_to_tuple(_, Binary) ->
<< VoiceType:8, VoicePitch:8, _:24, Jacket:32/unsigned-integer, Pants:32/unsigned-integer, Shoes:32/unsigned-integer,
Ears:32/unsigned-integer, Face:32/unsigned-integer, Hairstyle:32/unsigned-integer, JacketColor:8, PantsColor:8, ShoesColor:8, _:8,
_:8, Eyebrows:8, Eyelashes:8, EyesGroup:8, Eyes:8, BodySuit:8, _:16, EyesColorY:32/little, EyesColorX:32/little,
_:96, SkinColor:32/little, _:32, HairstyleColorY:32/little,
HairstyleColorX:32/little, Proportion:32/little, ProportionBoxX:32/little,
ProportionBoxY:32/little, FaceBoxX:32/little, FaceBoxY:32/little >> = Binary,
{flesh_appearance, VoiceType, VoicePitch, Jacket, Pants, Shoes, Ears, Face, Hairstyle, JacketColor, PantsColor, ShoesColor,
0, 0, Eyebrows, Eyelashes, EyesGroup, Eyes, BodySuit, EyesColorY, EyesColorX, 32767, 32767, 0, SkinColor, HairstyleColorY,
HairstyleColorX, Proportion, ProportionBoxX, ProportionBoxY, FaceBoxX, FaceBoxY}.
%% @doc Convert a tuple of appearance data into a binary to be sent to clients.
tuple_to_binary(cast, Tuple) ->
{metal_appearance, VoiceType, VoicePitch, Torso, Legs, Arms, Ears, Face, HeadType, MainColor, LineshieldColor,
Eyebrows, Eyelashes, EyesGroup, Eyes, EyesColorY, EyesColorX, BodyColor, SubColor, HairstyleColorY, HairstyleColorX,
Proportion, ProportionBoxX, ProportionBoxY, FaceBoxX, FaceBoxY} = Tuple,
<< VoiceType:8, VoicePitch:8, 0:24, Torso:32/unsigned-integer, Legs:32/unsigned-integer, Arms:32/unsigned-integer,
Ears:32/unsigned-integer, Face:32/unsigned-integer, HeadType:32/unsigned-integer, MainColor:8, 0:16, LineshieldColor:8,
0:8, Eyebrows:8, Eyelashes:8, EyesGroup:8, Eyes:8, 0:24, EyesColorY:32/little, EyesColorX:32/little,
16#ff7f0000:32, 16#ff7f0000:32, 0:32, BodyColor:32/little, SubColor:32/little, HairstyleColorY:32/little,
HairstyleColorX:32/little, Proportion:32/little, ProportionBoxX:32/little,
ProportionBoxY:32/little, FaceBoxX:32/little, FaceBoxY:32/little >>;
tuple_to_binary(_, Tuple) ->
{flesh_appearance, VoiceType, VoicePitch, Jacket, Pants, Shoes, Ears, Face, Hairstyle, JacketColor, PantsColor, ShoesColor,
LineshieldColor, Badge, Eyebrows, Eyelashes, EyesGroup, Eyes, BodySuit, EyesColorY, EyesColorX, LipsIntensity, LipsColorY, LipsColorX,
SkinColor, HairstyleColorY, HairstyleColorX, Proportion, ProportionBoxX, ProportionBoxY, FaceBoxX, FaceBoxY} = Tuple,
<< VoiceType:8, VoicePitch:8, 0:24, Jacket:32/unsigned-integer, Pants:32/unsigned-integer, Shoes:32/unsigned-integer,
Ears:32/unsigned-integer, Face:32/unsigned-integer, Hairstyle:32/unsigned-integer, JacketColor:8, PantsColor:8, ShoesColor:8, LineshieldColor:8,
Badge:8, Eyebrows:8, Eyelashes:8, EyesGroup:8, Eyes:8, BodySuit:8, 0:16, EyesColorY:32/little, EyesColorX:32/little,
LipsIntensity:32/little, LipsColorY:32/little, LipsColorX:32/little,
SkinColor:32/little, 16#ffff0200:32, HairstyleColorY:32/little,
HairstyleColorX:32/little, Proportion:32/little, ProportionBoxX:32/little,
ProportionBoxY:32/little, FaceBoxX:32/little, FaceBoxY:32/little >>.
%% @doc Validate the character creation appearance data.
%% Trigger an exception rather than handling errors.
validate_char_create(cast, male, Tuple) ->
#metal_appearance{voicetype=VoiceType, torso=Torso, legs=Legs, arms=Arms, ears=Ears, face=Face, headtype=HeadType, eyelashes=Eyelashes, eyesgroup=EyesGroup, eyescolorx=EyesColorX} = Tuple,
validate_char_create_common_metal(Tuple),
true = (VoiceType >= 27 andalso VoiceType =< 38) orelse (VoiceType >= 89 andalso VoiceType =< 96),
true = Torso =:= 16#00F70100 orelse Torso =:= 16#00F90100 orelse Torso =:= 16#00FC0100,
true = Legs =:= 16#00F70101 orelse Legs =:= 16#00F90101 orelse Legs =:= 16#00FC0101,
true = Arms =:= 16#00F70102 orelse Arms =:= 16#00F90102 orelse Arms =:= 16#00FC0102,
if Face =:= 16#00040004 orelse Face =:= 16#0A040004 orelse Face =:= 16#14040004 orelse Face =:= 16#1E040004 orelse Face =:= 16#28040004 orelse Face =:= 16#000E0004 ->
true = Ears =:= 16#001E0003 orelse Ears =:= 16#001F0003 orelse Ears =:= 16#00200003 orelse Ears =:= 16#00210003 orelse Ears =:= 16#00220003,
true = EyesColorX =< 327679,
validate_char_create_male_hairstyle(HeadType);
Face =:= 16#00F40104 orelse Face =:= 16#00F50104 orelse Face =:= 16#00F60104 orelse Face =:= 16#00F70104 orelse Face =:= 16#00F80104 orelse Face =:= 16#00F90104 orelse
Face =:= 16#00FA0104 orelse Face =:= 16#00FD0104 orelse Face =:= 16#00020204 orelse Face =:= 16#00030204 orelse Face =:= 16#00040204 orelse Face =:= 16#00060204 orelse Face =:= 16#00070204 ->
Ears = 16#FFFFFFFF,
true = EyesColorX =< 458751,
true = HeadType =:= 16#00F40105 orelse HeadType =:= 16#00F50105 orelse HeadType =:= 16#00F60105 orelse HeadType =:= 16#00F70105 orelse HeadType =:= 16#00F80105 orelse
HeadType =:= 16#00F90105 orelse HeadType =:= 16#00FA0105 orelse HeadType =:= 16#00FB0105 orelse HeadType =:= 16#00FD0105 orelse HeadType =:= 16#00020205 orelse
HeadType =:= 16#00030205 orelse HeadType =:= 16#00040205 orelse HeadType =:= 16#00060205 orelse HeadType =:= 16#00070205
end,
true = Eyelashes =< 2,
EyesGroup = 4;
validate_char_create(cast, female, Tuple) ->
#metal_appearance{voicetype=VoiceType, torso=Torso, legs=Legs, arms=Arms, ears=Ears, face=Face, headtype=HeadType, eyelashes=Eyelashes, eyesgroup=EyesGroup, eyescolorx=EyesColorX} = Tuple,
validate_char_create_common_metal(Tuple),
true = (VoiceType >= 39 andalso VoiceType =< 50) orelse (VoiceType >= 97 andalso VoiceType =< 101),
true = Torso =:= 16#00F51100 orelse Torso =:= 16#00F91100 orelse Torso =:= 16#00FA1100,
true = Legs =:= 16#00F51101 orelse Legs =:= 16#00F91101 orelse Legs =:= 16#00FA1101,
true = Arms =:= 16#00F51102 orelse Arms =:= 16#00F91102 orelse Arms =:= 16#00F61102,
if Face =:= 16#00041004 orelse Face =:= 16#0A041004 orelse Face =:= 16#14041004 orelse Face =:= 16#1E041004 orelse Face =:= 16#3C041004 ->
true = Ears =:= 16#001E1003 orelse Ears =:= 16#001F1003 orelse Ears =:= 16#00201003 orelse Ears =:= 16#00211003 orelse Ears =:= 16#00221003,
true = EyesColorX =< 327679,
validate_char_create_female_hairstyle(HeadType);
Face =:= 16#00F41104 orelse Face =:= 16#00F51104 orelse Face =:= 16#00F61104 orelse Face =:= 16#00F71104 orelse Face =:= 16#00F81104 orelse Face =:= 16#00F91104 orelse
Face =:= 16#00FA1104 orelse Face =:= 16#00FD1104 orelse Face =:= 16#00031204 orelse Face =:= 16#00041204 orelse Face =:= 16#00051204 orelse Face =:= 16#00061204 orelse Face =:= 16#00081204 ->
Ears = 16#FFFFFFFF,
true = EyesColorX =< 458751,
true = HeadType =:= 16#00F41105 orelse HeadType =:= 16#00F51105 orelse HeadType =:= 16#00F61105 orelse HeadType =:= 16#00F71105 orelse HeadType =:= 16#00F81105 orelse
HeadType =:= 16#00F91105 orelse HeadType =:= 16#00FA1105 orelse HeadType =:= 16#00FB1105 orelse HeadType =:= 16#00FD1105 orelse HeadType =:= 16#00031205 orelse
HeadType =:= 16#00041205 orelse HeadType =:= 16#00051205 orelse HeadType =:= 16#00061205 orelse HeadType =:= 16#00081205
end,
true = Eyelashes =< 12,
EyesGroup = 5;
validate_char_create(human, male, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_male_flesh(Tuple),
true = Ears =:= 16#00000003 orelse Ears =:= 16#00010003,
true = Face =:= 16#00010004 orelse Face =:= 16#01010004 orelse Face =:= 16#14010004 orelse Face =:= 16#15010004 orelse Face =:= 16#16010004 orelse Face =:= 16#17010004 orelse
Face =:= 16#18010004 orelse Face =:= 16#19010004 orelse Face =:= 16#1A010004 orelse Face =:= 16#1E010004 orelse Face =:= 16#1F010004 orelse Face =:= 16#20010004 orelse
Face =:= 16#21010004 orelse Face =:= 16#22010004 orelse Face =:= 16#23010004 orelse Face =:= 16#24010004 orelse Face =:= 16#28010004 orelse Face =:= 16#29010004 orelse
Face =:= 16#2A010004 orelse Face =:= 16#2B010004 orelse Face =:= 16#2C010004 orelse Face =:= 16#2D010004 orelse Face =:= 16#2E010004 orelse Face =:= 16#000B0004,
EyesGroup = 0,
true = Eyes =< 5;
validate_char_create(newman, male, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_male_flesh(Tuple),
true = Ears =:= 16#00030003 orelse Ears =:= 16#00650003 orelse Ears =:= 16#00660003,
true = Face =:= 16#00020004 orelse Face =:= 16#01020004 orelse Face =:= 16#14020004 orelse Face =:= 16#15020004 orelse Face =:= 16#16020004 orelse Face =:= 16#17020004 orelse
Face =:= 16#18020004 orelse Face =:= 16#19020004 orelse Face =:= 16#1A020004 orelse Face =:= 16#1E020004 orelse Face =:= 16#1F020004 orelse Face =:= 16#20020004 orelse
Face =:= 16#21020004 orelse Face =:= 16#22020004 orelse Face =:= 16#23020004 orelse Face =:= 16#24020004 orelse Face =:= 16#28020004 orelse Face =:= 16#29020004 orelse
Face =:= 16#2A020004 orelse Face =:= 16#2B020004 orelse Face =:= 16#2C020004 orelse Face =:= 16#2D020004 orelse Face =:= 16#2E020004 orelse Face =:= 16#000C0004,
EyesGroup = 2,
true = Eyes =< 5;
validate_char_create(beast, male, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_male_flesh(Tuple),
true = Ears =:= 16#00020003 orelse Ears =:= 16#00CD0003 orelse Ears =:= 16#00CE0003,
true = Face =:= 16#00030004 orelse Face =:= 16#0A030004 orelse Face =:= 16#14030004 orelse Face =:= 16#15030004 orelse Face =:= 16#16030004 orelse Face =:= 16#17030004 orelse
Face =:= 16#18030004 orelse Face =:= 16#19030004 orelse Face =:= 16#1A030004 orelse Face =:= 16#1E030004 orelse Face =:= 16#1F030004 orelse Face =:= 16#20030004 orelse
Face =:= 16#21030004 orelse Face =:= 16#22030004 orelse Face =:= 16#23030004 orelse Face =:= 16#24030004 orelse Face =:= 16#28030004 orelse Face =:= 16#29030004 orelse
Face =:= 16#2A030004 orelse Face =:= 16#2B030004 orelse Face =:= 16#2C030004 orelse Face =:= 16#2D030004 orelse Face =:= 16#2E030004 orelse Face =:= 16#000D0004,
EyesGroup = 6,
true = Eyes =< 6;
validate_char_create(human, female, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_female_flesh(Tuple),
true = Ears =:= 16#00001003 orelse Ears =:= 16#00011003,
true = Face =:= 16#00011004 orelse Face =:= 16#0A011004 orelse Face =:= 16#14011004 orelse Face =:= 16#1E011004 orelse Face =:= 16#3C011004,
EyesGroup = 1,
true = Eyes =< 5;
validate_char_create(newman, female, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_female_flesh(Tuple),
true = Ears =:= 16#00031003 orelse Ears =:= 16#00651003 orelse Ears =:= 16#00661003,
true = Face =:= 16#00021004 orelse Face =:= 16#0A021004 orelse Face =:= 16#14021004 orelse Face =:= 16#1E021004 orelse Face =:= 16#3C021004,
EyesGroup = 3,
true = Eyes =< 5;
validate_char_create(beast, female, Tuple) ->
#flesh_appearance{ears=Ears, face=Face, eyesgroup=EyesGroup, eyes=Eyes} = Tuple,
validate_char_create_common_flesh(Tuple),
validate_char_create_common_female_flesh(Tuple),
true = Ears =:= 16#00021003 orelse Ears =:= 16#00CD1003 orelse Ears =:= 16#00CE1003 orelse Ears =:= 16#00CF1003,
true = Face =:= 16#00031004 orelse Face =:= 16#0A031004 orelse Face =:= 16#14031004 orelse Face =:= 16#1E031004 orelse Face =:= 16#3C031004,
EyesGroup = 7,
true = Eyes =< 6.
%% @doc Validate the common settings for all metal characters.
validate_char_create_common_metal(Tuple) ->
#metal_appearance{maincolor=MainColor, eyebrows=Eyebrows, eyes=Eyes, eyescolory=EyesColorY, bodycolor=BodyColor, subcolor=SubColor,
hairstylecolory=HairstyleColorY, hairstylecolorx=HairstyleColorX, proportion=Proportion, proportionboxx=ProportionBoxX, proportionboxy=ProportionBoxY,
faceboxx=FaceBoxX, faceboxy=FaceBoxY} = Tuple,
true = MainColor =< 7,
true = Eyebrows =< 18,
true = Eyes =< 2,
true = EyesColorY =< 65535,
true = BodyColor =< 131071,
true = SubColor =< 393215,
true = HairstyleColorY =< 65535,
true = HairstyleColorX =< 327679,
true = Proportion =< 131071,
true = ProportionBoxX =< 131071,
true = ProportionBoxY =< 131071,
true = FaceBoxX =< 131071,
true = FaceBoxY =< 131071.
%% @doc Validate the common settings for all flesh characters.
validate_char_create_common_flesh(Tuple) ->
#flesh_appearance{jacketcolor=JacketColor, pantscolor=PantsColor, shoescolor=ShoesColor, eyebrows=Eyebrows, bodysuit=BodySuit,
eyescolory=EyesColorY, eyescolorx=EyesColorX, skincolor=SkinColor, hairstylecolory=HairstyleColorY, hairstylecolorx=HairstyleColorX,
proportion=Proportion, proportionboxx=ProportionBoxX, proportionboxy=ProportionBoxY, faceboxx=FaceBoxX, faceboxy=FaceBoxY} = Tuple,
true = JacketColor =< 4,
true = PantsColor =< 4,
true = ShoesColor =< 4,
true = Eyebrows =< 18,
true = BodySuit =< 4,
true = EyesColorY =< 65535,
true = EyesColorX =< 327679,
true = SkinColor =< 131071,
true = HairstyleColorY =< 65535,
true = HairstyleColorX =< 327679,
true = Proportion =< 131071,
true = ProportionBoxX =< 131071,
true = ProportionBoxY =< 131071,
true = FaceBoxX =< 131071,
true = FaceBoxY =< 131071.
%% @doc Validate the common settings for all male flesh characters.
validate_char_create_common_male_flesh(Tuple) ->
#flesh_appearance{voicetype=VoiceType, jacket=Jacket, pants=Pants, shoes=Shoes, hairstyle=Hairstyle, eyelashes=Eyelashes} = Tuple,
true = (VoiceType >= 1 andalso VoiceType =< 14) orelse (VoiceType >= 76 andalso VoiceType =< 83),
true = Jacket =:= 16#00060000 orelse Jacket =:= 16#00020000 orelse Jacket =:= 16#00030000,
true = Pants =:= 16#00060001 orelse Pants =:= 16#000B0001 orelse Pants =:= 16#00030001,
true = Shoes =:= 16#00060002 orelse Shoes =:= 16#00020002 orelse Shoes =:= 16#00040002,
validate_char_create_male_hairstyle(Hairstyle),
true = Eyelashes =< 2.
%% @doc Validate the common settings for all female flesh characters.
validate_char_create_common_female_flesh(Tuple) ->
#flesh_appearance{voicetype=VoiceType, jacket=Jacket, pants=Pants, shoes=Shoes, hairstyle=Hairstyle, eyelashes=Eyelashes} = Tuple,
true = (VoiceType >= 15 andalso VoiceType =< 26) orelse (VoiceType >= 84 andalso VoiceType =< 88),
true = Jacket =:= 16#00011000 orelse Jacket =:= 16#00021000 orelse Jacket =:= 16#00031000,
true = Pants =:= 16#00011001 orelse Pants =:= 16#00021001 orelse Pants =:= 16#00031001,
true = Shoes =:= 16#00091002 orelse Shoes =:= 16#00071002 orelse Shoes =:= 16#00031002,
validate_char_create_female_hairstyle(Hairstyle),
true = Eyelashes =< 12.
%% @doc Validate the hairstyle for all male characters.
validate_char_create_male_hairstyle(Hairstyle) ->
true = Hairstyle =:= 16#00000005 orelse Hairstyle =:= 16#000A0005 orelse Hairstyle =:= 16#00140005 orelse Hairstyle =:= 16#001E0005 orelse Hairstyle =:= 16#00280005 orelse Hairstyle =:= 16#00320005 orelse
Hairstyle =:= 16#003C0005 orelse Hairstyle =:= 16#00460005 orelse Hairstyle =:= 16#00500005 orelse Hairstyle =:= 16#005A0005 orelse Hairstyle =:= 16#00640005 orelse Hairstyle =:= 16#006E0005 orelse
Hairstyle =:= 16#00780005 orelse Hairstyle =:= 16#00820005 orelse Hairstyle =:= 16#008C0005 orelse Hairstyle =:= 16#00960005 orelse Hairstyle =:= 16#00A00005 orelse Hairstyle =:= 16#00AA0005.
%% @doc Validate the hairstyle for all female characters.
validate_char_create_female_hairstyle(Hairstyle) ->
true = Hairstyle =:= 16#00001005 orelse Hairstyle =:= 16#000A1005 orelse Hairstyle =:= 16#00141005 orelse Hairstyle =:= 16#001E1005 orelse Hairstyle =:= 16#00281005 orelse Hairstyle =:= 16#00321005 orelse
Hairstyle =:= 16#003C1005 orelse Hairstyle =:= 16#00461005 orelse Hairstyle =:= 16#00501005 orelse Hairstyle =:= 16#005A1005 orelse Hairstyle =:= 16#00641005 orelse Hairstyle =:= 16#006E1005 orelse
Hairstyle =:= 16#00781005 orelse Hairstyle =:= 16#00821005 orelse Hairstyle =:= 16#008C1005 orelse Hairstyle =:= 16#00961005 orelse Hairstyle =:= 16#00A01005.

View File

@ -19,9 +19,7 @@
-module(psu_characters).
-export([
character_tuple_to_binary/1, character_user_to_binary/1, class_atom_to_binary/1, class_binary_to_atom/1,
gender_atom_to_binary/1, gender_binary_to_atom/1, options_binary_to_tuple/1, options_tuple_to_binary/1,
race_atom_to_binary/1, race_binary_to_atom/1, stats_tuple_to_binary/1
character_user_to_binary/1
]).
-include("include/records.hrl").
@ -34,7 +32,7 @@ character_tuple_to_binary(Tuple) ->
RaceBin = race_atom_to_binary(Race),
GenderBin = gender_atom_to_binary(Gender),
ClassBin = class_atom_to_binary(Class),
AppearanceBin = psu_appearance:tuple_to_binary(Race, Appearance),
AppearanceBin = egs_net:character_appearance_to_binary(Race, Appearance),
LevelsBin = egs_proto:build_char_level(Tuple),
<< Name/binary, RaceBin:8, GenderBin:8, ClassBin:8, AppearanceBin/binary, LevelsBin/binary >>.
@ -47,8 +45,8 @@ character_tuple_to_binary(Tuple) ->
character_user_to_binary(User) ->
#users{gid=CharGID, lid=CharLID, npcid=NPCid, type=Type, level=Level, stats=Stats, currenthp=CurrentHP, maxhp=MaxHP,
pos={X, Y, Z, Dir}, area={QuestID, ZoneID, MapID}, entryid=EntryID, prev_area={PrevQuestID, PrevZoneID, PrevMapID}, prev_entryid=PrevEntryID} = User,
CharBin = psu_characters:character_tuple_to_binary(User),
StatsBin = psu_characters:stats_tuple_to_binary(Stats),
CharBin = character_tuple_to_binary(User),
StatsBin = stats_tuple_to_binary(Stats),
EXPNextLevel = 100,
EXPCurrentLevel = 0,
IntDir = trunc(Dir * 182.0416),
@ -66,45 +64,10 @@ character_user_to_binary(User) ->
class_atom_to_binary(Class) ->
case Class of
hunter -> 0;
ranger -> 1;
force -> 2;
fighgunner -> 3;
guntecher -> 4;
wartecher -> 5;
fortefighter -> 6;
fortegunner -> 7;
fortetecher -> 8;
protranser -> 9;
acrofighter -> 10;
acrotecher -> 11;
fighmaster -> 12;
gunmaster -> 13;
masterforce -> 14;
acromaster -> 15
end.
%% @doc Convert the binary class to an atom.
%% @todo Probably can make a list and use that list for both functions.
class_binary_to_atom(ClassBin) ->
case ClassBin of
0 -> hunter;
1 -> ranger;
2 -> force;
3 -> fighgunner;
4 -> guntecher;
5 -> wartecher;
6 -> fortefighter;
7 -> fortegunner;
8 -> fortetecher;
9 -> protranser;
10 -> acrofighter;
11 -> acrotecher;
12 -> fighmaster;
13 -> gunmaster;
14 -> masterforce;
15 -> acromaster
hunter -> 12;
ranger -> 13;
force -> 14;
acro -> 15
end.
%% @doc Convert a gender atom into a binary to be sent to clients.
@ -115,35 +78,6 @@ gender_atom_to_binary(Gender) ->
female -> 1
end.
%% @doc Convert the binary gender into an atom.
gender_binary_to_atom(GenderBin) ->
case GenderBin of
0 -> male;
1 -> female
end.
%% @doc Convert the binary options data into a tuple.
%% The few unknown values are probably PS2 or 360 only.
options_binary_to_tuple(Binary) ->
<< TextDisplaySpeed:8, Sound:8, MusicVolume:8, SoundEffectVolume:8, Vibration:8, RadarMapDisplay:8,
CutInDisplay:8, MainMenuCursorPosition:8, _:8, Camera3rdY:8, Camera3rdX:8, Camera1stY:8, Camera1stX:8,
Controller:8, WeaponSwap:8, LockOn:8, Brightness:8, FunctionKeySetting:8, _:8, ButtonDetailDisplay:8, _:32 >> = Binary,
{options, TextDisplaySpeed, Sound, MusicVolume, SoundEffectVolume, Vibration, RadarMapDisplay,
CutInDisplay, MainMenuCursorPosition, Camera3rdY, Camera3rdX, Camera1stY, Camera1stX,
Controller, WeaponSwap, LockOn, Brightness, FunctionKeySetting, ButtonDetailDisplay}.
%% @doc Convert a tuple of options data into a binary to be sent to clients.
options_tuple_to_binary(Tuple) ->
{options, TextDisplaySpeed, Sound, MusicVolume, SoundEffectVolume, Vibration, RadarMapDisplay,
CutInDisplay, MainMenuCursorPosition, Camera3rdY, Camera3rdX, Camera1stY, Camera1stX,
Controller, WeaponSwap, LockOn, Brightness, FunctionKeySetting, ButtonDetailDisplay} = Tuple,
<< TextDisplaySpeed, Sound, MusicVolume, SoundEffectVolume, Vibration, RadarMapDisplay,
CutInDisplay, MainMenuCursorPosition, 0, Camera3rdY, Camera3rdX, Camera1stY, Camera1stX,
Controller, WeaponSwap, LockOn, Brightness, FunctionKeySetting, 0, ButtonDetailDisplay, 0:32 >>.
%% @doc Convert a race atom into a binary to be sent to clients.
race_atom_to_binary(Race) ->
@ -154,16 +88,6 @@ race_atom_to_binary(Race) ->
beast -> 3
end.
%% @doc Convert the binary race into an atom.
race_binary_to_atom(RaceBin) ->
case RaceBin of
0 -> human;
1 -> newman;
2 -> cast;
3 -> beast
end.
%% @doc Convert the tuple of stats data into a binary to be sent to clients.
stats_tuple_to_binary(Tuple) ->

View File

@ -0,0 +1,4 @@
{deps, [
{erlson, ".*", {git, "https://github.com/alavrik/erlson.git", "HEAD"}}
]}.
{plugins, [erlson_rebar_plugin]}.

View File

@ -0,0 +1,12 @@
%%-*- mode: erlang -*-
{application, egs_net, [
{description, "EGS network layer."},
{vsn, "0.1.0"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib,
cowboy
]}
]}.

1576
apps/egs_net/src/egs_net.erl Normal file

File diff suppressed because it is too large Load Diff

0
apps/egs_store/priv/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,13 @@
%%-*- mode: erlang -*-
{application, egs_store, [
{description, "EGS storage layer."},
{vsn, "0.1.0"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {egs_store_app, []}},
{env, []}
]}.

View File

@ -0,0 +1,92 @@
%% 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_store).
-behaviour(gen_server).
-export([start_link/0,
load_character/2, load_characters/2, save_character/3]). %% API.
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). %% gen_server.
-define(SERVER, ?MODULE).
-define(ACCOUNTS_TBL, accounts_tbl).
-define(ACCOUNTS_VSN, 1).
-define(CHARACTERS_TBL, characters_tbl).
-define(CHARACTERS_VSN, 1).
%% API.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
load_character(GID, Slot) ->
gen_server:call(?SERVER, {load_character, GID, Slot}).
load_characters(GID, Slots) ->
gen_server:call(?SERVER, {load_characters, GID, Slots}).
save_character(GID, Slot, Data) ->
gen_server:call(?SERVER, {save_character, GID, Slot, Data}).
%% gen_server.
init([]) ->
{ok, App} = application:get_application(),
PrivDir = code:priv_dir(App),
AccountsFile = PrivDir ++ "/accounts.tbl",
CharactersFile = PrivDir ++ "/characters.tbl",
{ok, ?ACCOUNTS_TBL} = dets:open_file(?ACCOUNTS_TBL,
[{file, AccountsFile}]),
io:format("accounts tbl:~n~p~n~n", [dets:info(?ACCOUNTS_TBL)]),
{ok, ?CHARACTERS_TBL} = dets:open_file(?CHARACTERS_TBL,
[{file, CharactersFile}]),
io:format("characters tbl:~n~p~n~n", [dets:info(?CHARACTERS_TBL)]),
{ok, undefined}.
handle_call({load_character, GID, Slot}, _From, State) ->
case dets:lookup(?CHARACTERS_TBL, {GID, Slot}) of
[{{GID, Slot}, Version, Data}] ->
{reply, {ok, Version, Data}, State};
[] ->
{reply, {error, notfound}, State}
end;
handle_call({load_characters, GID, Slots}, _From, State) ->
Characters = lists:map(fun(Slot) ->
case dets:lookup(?CHARACTERS_TBL, {GID, Slot}) of
[{{GID, Slot}, Version, Data}] ->
{Version, Data};
[] ->
notfound
end
end, Slots),
{reply, {ok, Characters}, State};
handle_call({save_character, GID, Slot, Data}, _From, State) ->
ok = dets:insert(?CHARACTERS_TBL, {{GID, Slot}, ?CHARACTERS_VSN, Data}),
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

View File

@ -0,0 +1,31 @@
%% 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_store_app).
-behaviour(application).
-export([start/2, stop/1]). %% API.
-type application_start_type()
:: normal | {takeover, node()} | {failover, node()}.
%% API.
-spec start(application_start_type(), any()) -> {ok, pid()}.
start(_Type, _StartArgs) ->
egs_store_sup:start_link().
-spec stop(any()) -> ok.
stop(_State) ->
ok.

View File

@ -0,0 +1,31 @@
%% 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_store_sup).
-behaviour(supervisor).
-export([start_link/0]). %% API.
-export([init/1]). %% Supervisor.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([]) -> {ok, {{one_for_one, 10, 10}, [supervisor:child_spec(), ...]}}.
init([]) ->
{ok, {{one_for_one, 10, 10}, [
{egs_store, {egs_store, start_link, []},
permanent, 5000, worker, [egs_store]}
]}}.

Binary file not shown.

View File

@ -27,9 +27,26 @@
%% @doc Game server IP address and port.
%% They can be modified freely without problem.
%% Note that the port should be available and above 1024.
{game_server, {<< 127, 0, 0, 1 >>, 12061}}.
{game_server, {<< 91, 121, 75, 204 >>, 12061}}.
%% Caps and limitations.
%% @doc Maximum level players can reach.
{level_cap, 200}.
%% Flags.
%% @todo doc
{value_flags, [
"EGS_VALUE_FLAG"
]}.
%% @todo doc
{bool_flags, [
"EGS_BOOL_FLAG"
]}.
%% @todo doc
{temp_flags, [
"EGS_TEMP_FLAG"
]}.

View File

@ -1,6 +1,8 @@
{sub_dirs, [
"apps/egs",
"apps/egs_net",
"apps/egs_patch",
"apps/egs_store",
"apps/prs"
]}.
{dialyzer_opts, [src, {warnings, [