Work in progress on egs_net (will be amended)
This commit is contained in:
parent
9adab0ea87
commit
ceea04c3b4
@ -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.
|
||||
|
@ -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]}.
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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
64
apps/egs/src/egs_char.erl
Normal 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
|
||||
}}.
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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).
|
||||
|
@ -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
@ -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).
|
||||
|
@ -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);
|
||||
|
@ -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.
|
@ -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) ->
|
||||
|
4
apps/egs_net/rebar.config
Normal file
4
apps/egs_net/rebar.config
Normal file
@ -0,0 +1,4 @@
|
||||
{deps, [
|
||||
{erlson, ".*", {git, "https://github.com/alavrik/erlson.git", "HEAD"}}
|
||||
]}.
|
||||
{plugins, [erlson_rebar_plugin]}.
|
12
apps/egs_net/src/egs_net.app.src
Normal file
12
apps/egs_net/src/egs_net.app.src
Normal 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
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
0
apps/egs_store/priv/.gitignore
vendored
Normal file
13
apps/egs_store/src/egs_store.app.src
Normal file
13
apps/egs_store/src/egs_store.app.src
Normal 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, []}
|
||||
]}.
|
92
apps/egs_store/src/egs_store.erl
Normal file
92
apps/egs_store/src/egs_store.erl
Normal 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}.
|
31
apps/egs_store/src/egs_store_app.erl
Normal file
31
apps/egs_store/src/egs_store_app.erl
Normal 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.
|
31
apps/egs_store/src/egs_store_sup.erl
Normal file
31
apps/egs_store/src/egs_store_sup.erl
Normal 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]}
|
||||
]}}.
|
BIN
p/flags.bin
BIN
p/flags.bin
Binary file not shown.
@ -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"
|
||||
]}.
|
||||
|
@ -1,6 +1,8 @@
|
||||
{sub_dirs, [
|
||||
"apps/egs",
|
||||
"apps/egs_net",
|
||||
"apps/egs_patch",
|
||||
"apps/egs_store",
|
||||
"apps/prs"
|
||||
]}.
|
||||
{dialyzer_opts, [src, {warnings, [
|
||||
|
Loading…
Reference in New Issue
Block a user