Compare commits

...

90 Commits

Author SHA1 Message Date
Loïc Hoguin
ceea04c3b4 Work in progress on egs_net (will be amended) 2012-05-16 13:31:37 +02:00
Loïc Hoguin
9adab0ea87 Move the PRS compression library into a separate application 2012-01-04 01:56:38 +01:00
Loïc Hoguin
6579c26f98 egs_patch: Rename the folder containing the patch files 2012-01-04 00:56:20 +01:00
Loïc Hoguin
368bb0f7b4 egs_patch: Use code:priv_dir/1 to get the location of the configuration 2012-01-04 00:52:41 +01:00
Loïc Hoguin
807510f669 Add custom crc.bin and SmutFilter_J.bin files to the patch server
These are safe to use and distribute as they were rebuilt from scratch.
2011-12-15 19:22:37 +01:00
Loïc Hoguin
c69513073d Move the patch server into its own separate application 2011-12-12 10:46:27 +01:00
Loïc Hoguin
35b86b58ba Fix paths temporarily to fix the compilation 2011-12-12 10:41:16 +01:00
Loïc Hoguin
ac0c3c551b Initial release layout
Moved files around to generate releases later on, but also to
separate the code into many different applications each with their
own purpose (patch server, game server, data storage, script compiler,
files library, and so on).
2011-12-12 10:02:34 +01:00
Loïc Hoguin
289ce855ad Use supervisor:child_spec() type where appropriate. 2011-06-08 15:36:14 +02:00
Loïc Hoguin
bc47fcb049 Fix a dialyzer warning in egs_app. 2011-06-08 12:30:13 +02:00
Loïc Hoguin
408e7e99e6 Convert egs_zones_sup to a simple_one_for_one supervisor. 2011-06-08 12:01:39 +02:00
Loïc Hoguin
c42b1a85f8 Convert egs_quests_sup to a simple_one_for_one supervisor. 2011-06-08 11:30:28 +02:00
Loïc Hoguin
e2274666b8 Rename Makefile target server into app. 2011-06-08 02:18:10 +02:00
Loïc Hoguin
6fd1119777 Add type specs and simplifications to egs_conf. 2011-06-08 02:17:49 +02:00
Loïc Hoguin
eab53bc3a7 Add type specs and simplifications to egs_sup.erl. 2011-06-08 02:17:24 +02:00
Loïc Hoguin
2325c7cf63 Move type definitions from an include file to egs.erl. 2011-06-08 01:44:31 +02:00
Loïc Hoguin
8889ca8332 Simplify the code returning the current unix time. 2011-06-08 01:23:32 +02:00
Loïc Hoguin
23e781f498 Add type specs to egs.erl. 2011-06-08 00:13:48 +02:00
Loïc Hoguin
60b8009382 Use a try .. after construct for handling disconnects. 2011-05-18 21:38:18 +02:00
Loïc Hoguin
325c1a4c10 Use Cowboy as a pool of acceptors. 2011-05-17 17:15:17 +02:00
Loïc Hoguin
8f3db6480a Only enable zone 0 on colony by default.
Will prevent the issue with other zones not being defined yet.
2011-04-19 13:00:06 +02:00
Loïc Hoguin
8363741b3a Initial work on 5th floor on colony zone 0. 2011-04-19 12:57:31 +02:00
Loïc Hoguin
935b490461 Update the copyright to 2010-2011 in a few files. 2011-04-19 12:47:08 +02:00
Loïc Hoguin
948873ddec Add dialyzer and compiler options to rebar.config. 2011-04-19 12:45:17 +02:00
Loïc Hoguin
72726bdf6c Remove rebar from the repository. Use the $PATH one by default. 2011-04-19 12:44:46 +02:00
Loïc Hoguin
0a399238f4 Fix a small issue in the patch server that could lead to a crash. 2011-03-29 01:58:26 +02:00
Loïc Hoguin
c957d9a8b9 Separate type definitions in their own header.
Also convert source files to utf8, update the copyright info and
other minor changes.
2011-02-27 23:13:09 +01:00
Loïc Hoguin
d9cde30b0b Remove the #level record. 2011-02-27 20:12:43 +01:00
Loïc Hoguin
c7cb5ab589 Remove the unused #users.se. 2011-02-27 19:49:27 +01:00
Loïc Hoguin
dc26bb82f7 Remove the unused #users.classlevels. 2011-02-27 19:46:37 +01:00
Loïc Hoguin
d8906226a5 Remove the unused #users.playtime. 2011-02-27 19:44:05 +01:00
Loïc Hoguin
730f47d837 Remove the unused #users.time. 2011-02-27 19:42:27 +01:00
Loïc Hoguin
d2f7c9e83f psu_characters: Remove validate_name as it's not doing anything yet. 2011-02-27 19:26:59 +01:00
Loïc Hoguin
6466f05728 psu_characters: Remove se_list_to_binary, it's doing it wrong. 2011-02-27 19:25:06 +01:00
Loïc Hoguin
3821a1e7bf egs_proto: Move the build_char_level function where it belongs. 2011-02-27 19:19:12 +01:00
Loïc Hoguin
a1bf3e43f3 Remove #characters and merge the data into #users directly. 2011-02-27 19:14:03 +01:00
Loïc Hoguin
3290aba95d egs: Use egs_users:broadcast to send the warp message to a single player too. 2011-02-27 14:27:58 +01:00
Loïc Hoguin
72989f5332 egs_game: Forgot a function export. 2011-02-27 14:06:31 +01:00
Loïc Hoguin
6cbb987ab0 Rename psu_proto into egs_proto since this module isn't temporary. 2011-02-27 14:03:04 +01:00
Loïc Hoguin
9c8ad80a07 egs_game: Move psu_game:char_load into egs_game and delete psu_game. 2011-02-27 13:48:10 +01:00
Loïc Hoguin
b5d6b3934b egs_game: Move psu_game:npc_load into egs_game. 2011-02-27 13:36:36 +01:00
Loïc Hoguin
cdc2c56d5c egs_game: Move the code from area_load inside the area_change event. 2011-02-27 13:33:18 +01:00
Loïc Hoguin
5b255b211b egs_login: Don't use the process dictionary anymore. 2011-02-27 02:26:37 +01:00
Loïc Hoguin
edb061662f psu_proto: Convert log calls to io:format to get rid of get(gid). 2011-02-27 02:24:49 +01:00
Loïc Hoguin
9268f3f7ae egs_game: Convert log calls to io:format to get rid of get(gid). 2011-02-27 02:16:15 +01:00
Loïc Hoguin
dd4a228b01 egs_login: Remove a warning on unused Socket variable. 2011-02-27 02:14:57 +01:00
Loïc Hoguin
34b4a21ce7 egs_game: Remove a few commented get(gid) that are polluting the search. 2011-02-27 00:28:57 +01:00
Loïc Hoguin
2a7383b9a1 psu_game: Remove psu_game:send in favor of psu_proto:packet_send.
This definitely gets rid of the process dictionary for storing the socket.
2011-02-27 00:10:28 +01:00
Loïc Hoguin
3f1d1d2fb0 psu_proto: Move send_0a0a to psu_proto without reviewing it. 2011-02-27 00:01:25 +01:00
Loïc Hoguin
18429b8d76 psu_proto: Move build_item_variables to psu_proto. 2011-02-26 23:56:51 +01:00
Loïc Hoguin
82aca844f2 psu_proto: Move build_item_constants to psu_proto. 2011-02-26 23:54:22 +01:00
Loïc Hoguin
1b36ff2589 psu_proto: Move send_010a to psu_proto without reviewing it. 2011-02-26 23:49:56 +01:00
Loïc Hoguin
4ff28e0939 psu_proto: Move send_1004 to psu_proto without reviewing it. 2011-02-26 23:32:43 +01:00
Loïc Hoguin
f21f23a92f psu_game: Delete send_1309 and its binary packet, now unused. 2011-02-26 23:21:15 +01:00
Loïc Hoguin
979e99f6a5 psu_game: Delete send_1332 and its binary packet, now unused. 2011-02-26 23:20:26 +01:00
Loïc Hoguin
58cfb8a61e psu_proto: Move send_0113 to psu_proto without reviewing it. 2011-02-26 23:16:31 +01:00
Loïc Hoguin
9aca48a697 psu_proto: Move send_0503 to psu_proto without reviewing it. 2011-02-26 23:14:12 +01:00
Loïc Hoguin
722bcf7c9e psu_proto: Move send_0a04 to psu_proto without reviewing it. 2011-02-26 23:11:31 +01:00
Loïc Hoguin
dc77b8e804 psu_proto: Move send_0d03 to psu_proto without reviewing it. 2011-02-26 23:07:52 +01:00
Loïc Hoguin
eaafdc213f psu_proto: Move send_1016 to psu_proto without reviewing it. 2011-02-26 23:03:19 +01:00
Loïc Hoguin
f8524ca9c4 psu_proto: Move send_1216 to psu_proto without reviewing it. 2011-02-26 23:01:08 +01:00
Loïc Hoguin
8c95aab709 psu_proto: Move send_1501 to psu_proto without reviewing it. 2011-02-26 22:58:23 +01:00
Loïc Hoguin
87c256edba psu_proto: Move send_1512 to psu_proto without reviewing it. 2011-02-26 22:55:55 +01:00
Loïc Hoguin
9c7f8f6eaa psu_proto: Move send_1602 to psu_proto without reviewing it. 2011-02-26 22:44:51 +01:00
Loïc Hoguin
5c234257dc psu_game: Remove the header function now unused. 2011-02-26 22:34:37 +01:00
Loïc Hoguin
a571b9a56b psu_proto: Move send_1a04 to psu_proto without reviewing it. 2011-02-26 22:34:05 +01:00
Loïc Hoguin
dfa5634adb psu_proto: Move send_170c to psu_proto without reviewing it. 2011-02-26 22:31:01 +01:00
Loïc Hoguin
53a4b3dbbe psu_proto: Move send_170a to psu_proto without reviewing it. 2011-02-26 22:27:47 +01:00
Loïc Hoguin
4faabbda8f psu_proto: Move send_1706 to psu_proto without reviewing it. 2011-02-26 22:25:06 +01:00
Loïc Hoguin
023214793c psu_proto: Move send_1601 to psu_proto without reviewing it. 2011-02-26 22:18:19 +01:00
Loïc Hoguin
250a22dea8 psu_proto: Move send_1215 to psu_proto without reviewing it. 2011-02-26 22:15:20 +01:00
Loïc Hoguin
f40ba44364 psu_proto: Move send_1213 to psu_proto without reviewing it. 2011-02-26 22:09:09 +01:00
Loïc Hoguin
d0e15316b8 psu_proto: Move send_1212 to psu_proto without reviewing it. 2011-02-26 22:00:08 +01:00
Loïc Hoguin
4848b3c218 psu_proto: Move send_1211 to psu_proto without reviewing it. 2011-02-26 21:57:30 +01:00
Loïc Hoguin
7415da1c89 psu_proto: Move send_1207 to psu_proto without reviewing it. 2011-02-26 21:32:35 +01:00
Loïc Hoguin
e8c1c98824 psu_proto: Move send_1206 to psu_proto without reviewing it. 2011-02-26 21:29:32 +01:00
Loïc Hoguin
fde0f8b3fe psu_proto: Move send_1205 to psu_proto without reviewing it. 2011-02-26 18:44:42 +01:00
Loïc Hoguin
82e82503fa psu_proto: Move send_1202 to psu_proto without reviewing it. 2011-02-26 18:39:42 +01:00
Loïc Hoguin
fde008c7a8 psu_proto: Move send_1113 to psu_proto without reviewing it. 2011-02-26 18:36:28 +01:00
Loïc Hoguin
564ab8749f psu_proto: Move send_110e to psu_proto without reviewing it. 2011-02-26 18:30:40 +01:00
Loïc Hoguin
6dc9fb52dc psu_proto: Move send_101a to psu_proto without reviewing it. 2011-02-26 18:27:31 +01:00
Loïc Hoguin
0b02718faa psu_proto: Move send_1015 to psu_proto without reviewing it. 2011-02-26 18:25:04 +01:00
Loïc Hoguin
333e898bb4 psu_proto: Move send_100f to psu_proto without reviewing it. 2011-02-26 18:15:53 +01:00
Loïc Hoguin
64d8bf7c25 psu_proto: Move send_0c09 to psu_proto without reviewing it. 2011-02-26 18:09:07 +01:00
Loïc Hoguin
a44fc4274f psu_proto: Move send_0c02 to psu_proto without reviewing it. 2011-02-26 18:03:55 +01:00
Loïc Hoguin
5f0bd73303 psu_proto: Move send_022c to psu_proto without reviewing it. 2011-02-26 17:38:07 +01:00
Loïc Hoguin
19350ba1ff egs_game: Stop using psu_game:header. 2011-02-26 17:25:31 +01:00
Loïc Hoguin
998263b417 egs_login: Use pattern matching for system_client_version_info. 2011-02-26 17:16:42 +01:00
Loïc Hoguin
b6c1bf277d egs_users: Rename stateu into state. 2011-02-26 17:03:25 +01:00
Loïc Hoguin
86bb5c81b3 Rename the client state record #state into #client for clarity. 2011-02-26 17:00:41 +01:00
98 changed files with 6531 additions and 3924 deletions

10
.gitignore vendored
View File

@ -1,6 +1,6 @@
c_src/*.o
apps/*/c_src/*.o
apps/*/ebin
apps/*/priv/*.so
apps/egs/src/egs_script_lexer.erl
apps/egs/src/egs_script_parser.erl
deps
ebin
priv/*.so
src/egs_script_lexer.erl
src/egs_script_parser.erl

View File

@ -1,5 +1,5 @@
# EGS: Erlang Game Server
# Copyright (C) 2010 Loic Hoguin
# Copyright (C) 2010-2011 Loic Hoguin
#
# This file is part of EGS.
#
@ -16,14 +16,23 @@
# You should have received a copy of the GNU Affero General Public License
# along with EGS. If not, see <http://www.gnu.org/licenses/>.
all: server
REBAR = rebar
server: deps
@./rebar compile
all: app
app: deps
@$(REBAR) compile
deps:
@./rebar get-deps
@$(REBAR) get-deps
clean:
@./rebar clean
@$(REBAR) clean
rm -f erl_crash.dump
tests:
@$(REBAR) eunit
@$(REBAR) ct
dialyze:
@$(REBAR) dialyze

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Project-wide Erlang records.
%%
%% This file is part of EGS.
@ -17,28 +17,18 @@
%% You should have received a copy of the GNU Affero General Public License
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
%% Standard library types.
-opaque sslsocket() :: any().
%% EGS types.
-type questid() :: 0..16#ffffffff. %% @todo What's the real max?
-type zoneid() :: 0..16#ffff. %% @todo What's the real max?
-type mapid() :: 0..9999.
-type entryid() :: 0..16#ffff. %% @todo What's the real max?
-type area() :: {questid(), zoneid(), mapid()}.
-type position() :: {X :: float(), Y :: float(), Z :: float(), Dir :: float()}.
%% Records.
%% @doc Per-process state used by the various EGS modules.
-record(state, {
socket :: sslsocket(),
gid :: integer(),
slot :: 0..3,
lid = 16#ffff :: 0..16#ffff,
%% @doc Client state. One per connected client.
-record(egs_net, {
socket :: ssl:sslsocket(),
transport :: module(),
handler :: module(),
buffer = <<>> :: binary(),
keepalive = false :: boolean(),
gid = 0 :: egs:gid(),
lid = 16#ffff :: egs:lid(),
slot = 0 :: 0..3,
areanb = 0 :: non_neg_integer()
}).
@ -46,25 +36,45 @@
%% @todo Probably can use a "param" or "extra" field to store the game-specific information (for things that don't need to be queried).
-record(users, {
%% General information.
gid :: integer(),
lid = 16#ffff :: 0..16#ffff,
gid :: egs:gid(),
lid = 16#ffff :: egs:lid(),
pid :: pid(),
time :: integer(),
character :: tuple(), %% @todo Details.
%% Character information.
%% @todo Specs it.
type = white,
slot,
npcid = 16#ffff,
name,
race,
gender,
class,
level = 1,
exp = 0,
currenthp = 100,
maxhp = 100,
stats = {stats, 1000, 2000, 3000, 4000, 5000, 6000, 7000},
money = 1000000,
blastbar = 0,
luck = 3,
appearance,
onlinestatus = 0,
options = {options, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0},
inventory = [],
%% Location/state related information.
uni :: integer(),
questpid :: pid(),
zonepid :: pid(),
partypid :: pid(),
areatype :: counter | mission | lobby | myroom,
area :: area(),
entryid :: entryid(),
pos = {0.0, 0.0, 0.0, 0.0} :: position(),
area :: egs:area(),
entryid :: egs:entryid(),
pos = {0.0, 0.0, 0.0, 0.0} :: egs:position(),
shopid :: integer(),
prev_area = {0, 0, 0} :: area(),
prev_entryid = 0 :: entryid(),
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.
@ -101,10 +111,6 @@
faceboxx=65535, faceboxy=65535
}).
%% @doc Character main or class level data structure.
-record(level, {number, exp}).
%% @doc Character stats data structure.
-record(stats, {atp, ata, tp, dfp, evp, mst, sta}).
@ -115,34 +121,6 @@
cutindisplay, mainmenucursorposition, camera3y, camera3x, camera1y, camera1x, controller, weaponswap,
lockon, brightness, functionkeysetting, buttondetaildisplay}).
%% @doc Characters data structure.
%% @todo Make a disk table for storing characters permanently. Also keep the current character in #users.
-record(characters, {
gid,
type=white,
slot,
npcid=16#ffff,
name,
race,
gender,
class,
mainlevel={level, 1, 0},
classlevels,
currenthp=100,
maxhp=100,
stats={stats, 1000, 2000, 3000, 4000, 5000, 6000, 7000},
se=[],
money=1000000,
blastbar=0,
luck=3,
playtime=0,
appearance,
onlinestatus=0,
options={options, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0},
inventory=[]
}). % also: shortcuts partnercards blacklist npcs flags...
%% @doc Hit response data.
-record(hit_response, {type, user, exp, damage, targethp, targetse, events}).

6
apps/egs/rebar.config Normal file
View File

@ -0,0 +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

@ -1,14 +1,15 @@
%%-*- mode: erlang -*-
{application, egs, [
{description, "EGS online action-RPG game server"},
{vsn, "0.9"},
{vsn, "0.14"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib,
crypto,
ssl
ssl,
cowboy
]},
{mod, {egs_app, []}},
{env, []}

85
apps/egs/src/egs.erl Normal file
View File

@ -0,0 +1,85 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS startup code and utility 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(egs).
-export([start/0, stop/0, global/1, warp/4, warp/5]). %% API.
%% Player and account-related types.
-type gid() :: 0..16#ffffffff.
-type lid() :: 0..1023 | 16#ffff.
-type character_slot() :: 0..3.
-export_type([gid/0, lid/0, character_slot/0]).
%% Location related types.
-type uniid() :: 21 | 26..254 | 16#ffffffff.
-type questid() :: 0..16#ffffffff. %% @todo What's the real max?
-type zoneid() :: 0..16#ffff. %% @todo What's the real max?
-type mapid() :: 0..9999.
-type entryid() :: 0..16#ffff. %% @todo What's the real max?
-type area() :: {questid(), zoneid(), mapid()}. %% @todo Probably remove later.
-type position() :: {X::float(), Y::float(), Z::float(), Dir::float()}.
-export_type([uniid/0, questid/0, zoneid/0, mapid/0, entryid/0,
area/0, position/0]).
%% API.
-spec start() -> ok.
start() ->
ensure_started(crypto),
ensure_started(public_key),
ensure_started(ssl),
ensure_started(cowboy),
application:start(egs).
-spec stop() -> ok.
stop() ->
Res = application:stop(egs),
ok = application:stop(cowboy),
ok = application:stop(ssl),
ok = application:stop(public_key),
ok = application:stop(crypto),
Res.
%% @doc Send a global message.
-spec global(string()) -> ok.
global(Message) when length(Message) > 511 ->
io:format("global: message too long~n");
global(Message) ->
egs_users:broadcast_all({egs, notice, top, Message}).
%% @doc Warp all players to a new map.
-spec warp(questid(), zoneid(), mapid(), entryid()) -> ok.
warp(QuestID, ZoneID, MapID, EntryID) ->
egs_users:broadcast_all({egs, warp, QuestID, ZoneID, MapID, EntryID}).
%% @doc Warp one player to a new map.
-spec warp(gid(), questid(), zoneid(), mapid(), entryid()) -> ok.
warp(GID, QuestID, ZoneID, MapID, EntryID) ->
egs_users:broadcast({egs, warp, QuestID, ZoneID, MapID, EntryID}, [GID]).
%% Internal.
-spec ensure_started(module()) -> ok.
ensure_started(App) ->
case application:start(App) of
ok -> ok;
{error, {already_started, App}} -> ok
end.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Accounts handling.
%%
%% This file is part of EGS.
@ -29,14 +29,14 @@
%% @todo Hash the password.
%% @todo Add email, password_salt, is_ingame, register_time, last_login_time, etc.
-record(accounts, {
gid :: integer(),
gid :: egs:gid(),
username :: string(),
password :: string(),
auth_state :: undefined | {wait_for_authentication, binary(), any()}
}).
-record(state, {
accounts = [] :: list({GID::integer(), #accounts{}}),
accounts = [] :: list({egs:gid(), #accounts{}}),
next_gid = 10000001 :: integer(),
tmp_gid = 16#ff000001 :: integer()
}).
@ -55,17 +55,17 @@ stop() ->
get_folder(GID) ->
gen_server:call(?SERVER, {get_folder, GID}).
-spec key_auth(GID::integer(), AuthKey::binary()) -> ok | {error, badarg}.
-spec key_auth(egs:gid(), AuthKey::binary()) -> ok | {error, badarg}.
%% @doc Authenticate using the given key.
key_auth(GID, AuthKey) ->
gen_server:call(?SERVER, {key_auth, GID, AuthKey}).
-spec key_auth_init(GID::integer()) -> {ok, AuthKey::binary()}.
-spec key_auth_init(egs:gid()) -> {ok, AuthKey::binary()}.
%% @doc Initialize key authentication. Obtain a key for a subsequent re-authentication on a different connection.
key_auth_init(GID) ->
gen_server:call(?SERVER, {key_auth_init, GID}).
-spec key_auth_timeout(GID::integer()) -> ok.
-spec key_auth_timeout(egs:gid()) -> ok.
%% @doc Key authentication timeout handling.
%% @todo Probably handle the authentication in a gen_fsm properly.
key_auth_timeout(GID) ->
@ -77,7 +77,7 @@ key_auth_timeout(GID) ->
login_auth(Username, Password) ->
gen_server:call(?SERVER, {login_auth, Username, Password}).
-spec tmp_gid() -> GID::integer().
-spec tmp_gid() -> egs:gid().
%% @doc Return an unused temporary GID for initial connection and APC characters.
tmp_gid() ->
gen_server:call(?SERVER, tmp_gid).

58
apps/egs/src/egs_app.erl Normal file
View File

@ -0,0 +1,58 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Callbacks for the egs application.
%%
%% 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_app).
-behaviour(application).
-export([start/2, stop/1]). %% API.
-type application_start_type()
:: normal | {takeover, node()} | {failover, node()}.
-define(SSL_OPTIONS, [{certfile, "priv/ssl/servercert.pem"},
{keyfile, "priv/ssl/serverkey.pem"}, {password, "alpha"}]).
%% API.
-spec start(application_start_type(), term()) -> {ok, pid()}.
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,
cowboy_ssl_transport, [{port, GamePort}] ++ ?SSL_OPTIONS,
egs_game_protocol, []),
{ok, Pid}.
-spec stop(term()) -> ok.
stop(_State) ->
ok.
%% Internal.
-spec start_login_listeners([inet:ip_port()]) -> ok.
start_login_listeners([]) ->
ok;
start_login_listeners([Port|Tail]) ->
{ok, _Pid} = cowboy:start_listener({login, Port}, 10,
cowboy_ssl_transport, [{port, Port}] ++ ?SSL_OPTIONS,
egs_login_protocol, []),
start_login_listeners(Tail).

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

@ -0,0 +1,86 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Character selection callback module.
%%
%% 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_char_select).
-export([info/2, cast/3, event/2]).
-include_lib("erlson/include/erlson.hrl").
-include("include/records.hrl").
%% @doc We don't expect any message here.
info(_Msg, _Client) ->
ok.
%% @doc Nothing to broadcast.
cast(_Command, _Data, _Client) ->
ok.
%% Events.
%% @doc Character screen selection request and delivery.
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);
%% @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({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({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),
%% @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}}),
{ok, User2} = egs_users:read(GID),
egs_game:char_load(User2, Client),
Client2 = egs_net:set_handler(egs_game, Client),
{ok, Client2};
event({client_hardware, GPU, CPU}, Client) ->
ok.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS configuration gen_server.
%%
%% This file is part of EGS.
@ -19,41 +19,35 @@
-module(egs_conf).
-behavior(gen_server).
-export([start_link/0, stop/0, read/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
%% Use the module name for the server's name.
-export([start_link/0, stop/0, read/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). %% gen_server.
-define(SERVER, ?MODULE).
%% API.
%% @spec start_link() -> {ok,Pid::pid()}
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @spec stop() -> stopped
-spec stop() -> stopped.
stop() ->
gen_server:call(?SERVER, stop).
%% @spec read(Key) -> Value | undefined
-spec read(atom()) -> undefined | any().
read(Key) ->
gen_server:call(?SERVER, {read, Key}).
%% @spec reload() -> ok
-spec reload() -> ok.
reload() ->
gen_server:cast(?SERVER, reload).
%% gen_server.
init([]) ->
case file:consult("priv/egs.conf") of
{ok, Terms} ->
error_logger:info_report("egs_conf started"),
{ok, Terms};
Error ->
error_logger:error_report(["An error occurred when trying to load the configuration file:", Error]),
Error
end.
{ok, _Terms} = file:consult("priv/egs.conf").
handle_call({read, Key}, _From, State) ->
{reply, proplists:get_value(Key, State), State};

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS counters database and cache manager.
%%
%% This file is part of EGS.
@ -19,6 +19,7 @@
-module(egs_counters_db).
-behavior(gen_server).
-export([start_link/0, stop/0, bg/1, opts/1, pack/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS file creation functions.
%%
%% This file is part of EGS.
@ -506,7 +506,7 @@ nbl_pack_files([], {AccH, AccD, AccP, _FilePos, _PtrIndex}) ->
{BinD3, CompressedDataSize} = if BinDSize < 16#800 ->
{BinD, 0};
true ->
BinD2 = egs_prs:compress(BinD),
BinD2 = prs:compress(BinD),
BinD2Size = byte_size(BinD2),
{BinD2, BinD2Size}
end,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Cowboy protocol module for the game server.
%%
%% 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_game_protocol).
-export([start_link/4, init/2]).
-include("include/records.hrl").
-spec start_link(pid(), ssl:sslsocket(), module(), []) -> {ok, pid()}.
start_link(_ListenerPid, Socket, Transport, []) ->
Pid = spawn_link(?MODULE, init, [Socket, Transport]),
{ok, Pid}.
-spec init(ssl:sslsocket(), module()) -> ok.
%% @todo Handle keepalive messages globally?
init(Socket, Transport) ->
{ok, _TRef} = timer:send_interval(5000, {egs, keepalive}),
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.
terminate() ->
case egs_users:find_by_pid(self()) of
undefined -> ok;
User ->
case User#users.partypid of
undefined ->
ignore;
PartyPid ->
{ok, NPCList} = psu_party:get_npc(PartyPid),
lists:foreach(fun({_Spot, NPCGID}) ->
egs_users:delete(NPCGID) end, NPCList),
psu_party:stop(PartyPid)
end,
egs_zones:leave(User#users.zonepid, User#users.gid),
egs_universes:leave(User#users.uni),
egs_users:delete(User#users.gid),
io:format("game (~p): quit~n", [User#users.gid])
end.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS items database.
%%
%% This file is part of EGS.
@ -19,6 +19,7 @@
-module(egs_items_db).
-behavior(gen_server).
-export([start_link/0, stop/0, desc/1, read/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
@ -26,7 +27,7 @@
-define(SERVER, ?MODULE).
-include("include/records.hrl").
-include("priv/items.hrl").
-include("../../priv/items.hrl").
%% API.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Log in and authentication callback module.
%%
%% This file is part of EGS.
@ -18,71 +18,64 @@
%% 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(_State) ->
ok.
%% @doc We don't expect any message here.
info(_Msg, _State) ->
info(_Msg, _Client) ->
ok.
%% @doc Nothing to broadcast.
cast(_Command, _Data, _State) ->
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, _State) ->
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}, State=#state{socket=Socket}) ->
if Version >= 2009002 -> ignore; true ->
psu_proto:send_0231("http://psumods.co.uk/forums/comments.php?DiscussionID=40#Item_1", State),
{ok, ErrorMsg} = file:read_file("priv/login/error_version.txt"),
psu_proto:send_0223(ErrorMsg, State),
ssl:close(Socket),
closed
end;
%% @todo Put the URL in a configuration file.
event({client_version, _Entrance, _Language, _Platform, Version}, Client)
when Version < 2009002 ->
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({client_version, _Entrance, _Language, _Platform, _Version}, _Client) ->
ok;
%% @doc Game server info request handler.
event(system_game_server_request, State=#state{socket=Socket}) ->
event(system_game_server_request, Client) ->
{ServerIP, ServerPort} = egs_conf:read(game_server),
psu_proto:send_0216(ServerIP, ServerPort, State),
ssl: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.
%% @todo Remove the put calls when all the send_xxxx are moved out of psu_game and into psu_proto.
event({system_key_auth_request, AuthGID, AuthKey}, State=#state{socket=Socket}) ->
event({system_key_auth, AuthGID, AuthKey}, Client) ->
egs_accounts:key_auth(AuthGID, AuthKey),
put(socket, Socket),
put(gid, AuthGID),
State2 = State#state{gid=AuthGID},
psu_proto:send_0d05(State2),
{ok, egs_char_select, State2};
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}, State) ->
{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]),
psu_proto:send_0223(GID, AuthKey, State);
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}, State) ->
event({system_motd_request, Page, _Language}, Client) ->
{ok, MOTD} = file:read_file("priv/login/motd.txt"),
psu_proto:send_0225(MOTD, Page, State).
egs_net:system_motd_response(MOTD, Page, Client).

View File

@ -1,6 +1,6 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Login server module.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Cowboy protocol module for the login server.
%%
%% This file is part of EGS.
%%
@ -17,19 +17,19 @@
%% 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_login_server).
-export([start_link/1, init/1]).
-module(egs_login_protocol).
-export([start_link/4, init/2]).
-include("include/records.hrl").
%% @spec start_link(Port) -> {ok,Pid::pid()}
%% @doc Start the login server.
start_link(Port) ->
Pid = spawn(egs_network, listen, [Port, ?MODULE]),
-spec start_link(pid(), ssl:sslsocket(), module(), []) -> {ok, pid()}.
start_link(_ListenerPid, Socket, Transport, []) ->
Pid = spawn_link(?MODULE, init, [Socket, Transport]),
{ok, Pid}.
%% @doc Initialize the game state and start receiving messages.
init(Socket) ->
State = #state{socket=Socket, gid=egs_accounts:tmp_gid()},
psu_proto:send_0202(State),
egs_network:recv(<< >>, egs_login, State).
-spec init(ssl:sslsocket(), module()) -> ok | closed.
init(Socket, Transport) ->
Client = egs_net:init(Socket, Transport, egs_login,
egs_accounts:tmp_gid()),
egs_net:system_hello(Client),
egs_net:loop(Client).

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS NPC database.
%%
%% This file is part of EGS.
@ -19,6 +19,7 @@
-module(egs_npc_db).
-behavior(gen_server).
-export([start_link/0, stop/0, all/0, create/2, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
@ -26,7 +27,7 @@
-define(SERVER, ?MODULE).
-include("include/records.hrl").
-include("priv/npc.hrl").
-include("../../priv/npc.hrl").
%% API.
@ -66,9 +67,10 @@ handle_call({create, NPCid, BaseLevel}, _From, State) ->
TmpUCS2Name = << << X:8, 0:8 >> || X <- Name >>,
Padding = 8 * (64 - byte_size(TmpUCS2Name)),
UCS2Name = << TmpUCS2Name/binary, 0:Padding >>,
Character = #characters{gid=NPCGID, slot=0, type=npc, npcid=NPCid, name=UCS2Name, race=Race, gender=Gender, class=Class, appearance=Appearance,
mainlevel={level, calc_level(BaseLevel, LevelDiff), 0}, blastbar=0, luck=2, money=0, playtime=0, stats={stats, 0, 0, 0, 0, 0, 0, 0}, se=[], currenthp=100, maxhp=100},
User = #users{gid=NPCGID, character=Character, areatype=lobby, area={0, 0, 0}, entryid=0},
User = #users{gid=NPCGID, slot=0, type=npc, npcid=NPCid, name=UCS2Name, race=Race, gender=Gender, class=Class, appearance=Appearance,
level=calc_level(BaseLevel, LevelDiff), blastbar=0, luck=2, money=0,
stats={stats, 0, 0, 0, 0, 0, 0, 0}, currenthp=100, maxhp=100,
areatype=lobby, area={0, 0, 0}, entryid=0},
{reply, User, State};
handle_call(stop, _From, State) ->

726
apps/egs/src/egs_proto.erl Normal file
View File

@ -0,0 +1,726 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Independent implementation of the PSU protocol.
%%
%% 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_proto).
-compile(export_all).
-include("include/records.hrl").
%% @spec assert() -> ok
%% @doc Log a detailed message when the function is called.
-define(ASSERT(), io:format("assert error in module ~p on line ~p~n", [?MODULE, ?LINE])).
%% @spec assert(A, B) -> ok
%% @doc Log a detailed message when the assertion A =:= B fails.
-define(ASSERT_EQ(A, B), if A =:= B -> ok; true -> io:format("assert error in module ~p on line ~p~n", [?MODULE, ?LINE]) end).
%% @doc Send a shop listing.
%% @todo This packet (and its build_010a_list function) hasn't been reviewed at all yet.
send_010a(ItemsList, Client=#egs_net{gid=DestGID}) ->
NbItems = length(ItemsList),
ItemsBin = build_010a_list(ItemsList, []),
packet_send(Client, << 16#010a0300:32, 0:64, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
DestGID:32/little, 0:32, 1:16/little, NbItems:8, 2:8, 0:32, ItemsBin/binary >>).
%% @todo The values set to 0 are unknown.
build_010a_list([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
build_010a_list([ItemID|Tail], Acc) ->
#psu_item{name=Name, rarity=Rarity, buy_price=SellPrice, data=Data} = egs_items_db:read(ItemID),
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
NamePadding = 8 * (46 - byte_size(UCS2Name)),
RarityBin = Rarity - 1,
DataBin = build_item_constants(Data),
BinItemID = case element(1, Data) of
psu_clothing_item -> %% Change the ItemID to enable all colors.
<< A:8, _:4, B:12, _:8 >> = << ItemID:32 >>,
<< A:8, 3:4, B:12, 16#ff:8 >>;
_Any ->
<< ItemID:32 >>
end,
Bin = << UCS2Name/binary, 0:NamePadding, RarityBin:8, 0:8, BinItemID/binary, SellPrice:32/little, DataBin/binary >>,
build_010a_list(Tail, [Bin|Acc]).
%% @doc Send character appearance and other information.
%% @todo Probably don't pattern match the data like this...
send_010d(CharUser, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
CharGID = CharUser#users.gid,
CharLID = CharUser#users.lid,
<< _:640, CharBin/bits >> = psu_characters:character_user_to_binary(CharUser),
packet_send(Client, << 16#010d0300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little,
0:64, 1:32/little, 0:32, 16#00000300:32, 16#ffff0000:32, 0:32, CharGID:32/little,
0:192, CharGID:32/little, CharLID:32/little, 16#ffffffff:32, CharBin/binary >>).
%% @doc Trigger a character-related event.
send_0111(CharUser, EventID, Client) ->
send_0111(CharUser, EventID, 0, Client).
send_0111(#users{gid=CharGID, lid=CharLID}, EventID, Param, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#01110300:32, DestLID:16/little, 0:48, CharGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
CharGID:32/little, CharLID:32/little, EventID:32/little, Param:32/little >>).
%% @todo Types capability list.
%% @todo This packet hasn't been reviewed at all yet.
send_0113(Client=#egs_net{gid=DestGID}) ->
{ok, File} = file:read_file("p/typesinfo.bin"),
packet_send(Client, << 16#01130300:32, 0:64, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64, DestGID:32/little, File/binary >>).
%% @doc Update the character level, blastbar, luck and money information.
send_0115(User, Client) ->
send_0115(User, 16#ffffffff, Client).
send_0115(User=#users{gid=CharGID, lid=CharLID}, EnemyTargetID, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#01150300:32, DestLID:16/little, 0:48, CharGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
CharGID:32/little, CharLID:32/little, EnemyTargetID:32/little, (build_char_level(User))/binary >>).
%% @doc Revive player with optional SEs.
%% @todo SEs.
send_0117(#users{gid=CharGID, lid=CharLID, currenthp=HP}, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
SE = << 0:64 >>,
packet_send(Client, << 16#01170300:32, DestLID:16/little, 0:48, CharGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
CharGID:32/little, CharLID:32/little, SE/binary, HP:32/little, 0:32 >>).
%% @doc Send the zone initialization command.
%% @todo Handle NbPlayers properly. There's more than 1 player!
send_0200(ZoneID, ZoneType, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
Var = case ZoneType of
mission -> << 16#06000500:32, 16#01000000:32, 0:64, 16#00040000:32, 16#00010000:32, 16#00140000:32 >>;
myroom -> << 16#06000000:32, 16#02000000:32, 0:64, 16#40000000:32, 16#00010000:32, 16#00010000:32 >>;
_ -> << 16#00040000:32, 0:160, 16#00140000:32 >>
end,
packet_send(Client, << 16#02000300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
DestLID:16/little, ZoneID:16/little, 1:32/little, 16#ffffffff:32, Var/binary, 16#ffffffff:32, 16#ffffffff:32 >>).
%% @doc Send character location, appearance and other information.
send_0201(CharUser, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
[CharTypeID, GameVersion] = case CharUser#users.type of
npc -> [16#00001d00, 255];
_ -> [16#00001200, 0]
end,
CharGID = CharUser#users.gid,
CharBin = psu_characters:character_user_to_binary(CharUser),
IsGM = 0,
OnlineStatus = 0,
packet_send(Client, << 16#02010300:32, DestLID:16/little, 0:16, CharTypeID:32, CharGID:32/little,
0:64, 16#00011300:32, DestGID:32/little, 0:64, CharBin/binary, IsGM:8, 0:8, OnlineStatus:8, GameVersion:8, 0:608 >>).
%% @doc Spawn a player with the given GID and LID.
send_0203(#users{gid=CharGID, lid=CharLID}, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#02030300:32, DestLID:16/little, 0:144, 16#00011300:32,
DestGID:32/little, 0:64, CharGID:32/little, CharLID:32/little >>).
%% @doc Unspawn the given character.
%% @todo The last 4 bytes are probably the number of players remaining in the zone.
send_0204(User, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
CharTypeID = case User#users.type of
npc -> 16#00001d00;
_ -> 16#00001200
end,
#users{gid=CharGID, lid=CharLID} = User,
packet_send(Client, << 16#02040300:32, DestLID:16/little, 0:16, CharTypeID:32, CharGID:32/little, 0:64,
16#00011300:32, DestGID:32/little, 0:64, CharGID:32/little, CharLID:32/little, 100:32/little >>).
%% @doc Make the client load a new map.
send_0205(CharUser, IsSeasonal, Client=#egs_net{gid=DestGID, lid=DestLID, areanb=AreaNb}) ->
#users{lid=CharLID, area={_QuestID, ZoneID, MapID}, entryid=EntryID} = CharUser,
packet_send(Client, << 16#02050300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
16#ffffffff:32, ZoneID:32/little, MapID:32/little, EntryID:32/little, AreaNb:32/little, CharLID:16/little, 0:8, IsSeasonal:8 >>).
%% @doc Indicate to the client that loading should finish.
send_0208(Client=#egs_net{gid=DestGID, lid=DestLID, areanb=AreaNb}) ->
packet_send(Client, << 16#02080300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64, AreaNb:32/little >>).
%% @todo No idea what this one does. For unknown reasons it uses channel 2.
%% @todo Handle the DestLID properly?
send_020c(Client) ->
packet_send(Client, << 16#020c0200:32, 16#ffff0000:32, 0:256 >>).
%% @doc Send the quest file to be loaded by the client.
%% @todo Handle the DestLID properly?
send_020e(QuestData, Client) ->
Size = byte_size(QuestData),
packet_send(Client, << 16#020e0300:32, 16#ffff:16, 0:272, Size:32/little, 0:32, QuestData/binary, 0:32 >>).
%% @doc Send the zone file to be loaded.
send_020f(ZoneData, SetID, SeasonID, Client) ->
Size = byte_size(ZoneData),
packet_send(Client, << 16#020f0300:32, 16#ffff:16, 0:272, SetID, SeasonID, 0:16, Size:32/little, ZoneData/binary >>).
%% @doc Send the current UNIX time.
send_0210(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
{M, S, _} = erlang:now(),
UnixTime = M * 1000000 + S,
packet_send(Client, << 16#02100300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:96, UnixTime:32/little >>).
%% @todo No idea what this is doing.
send_0215(UnknownValue, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#02150300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64, UnknownValue:32/little >>).
%% @doc End of character loading.
send_021b(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#021b0300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64 >>).
%% @doc Send the list of available universes.
send_021e(Universes, Client) ->
NbUnis = length(Universes),
UnisBin = build_021e_uni(Universes, []),
packet_send(Client, << 16#021e0300:32, 0:288, NbUnis:32/little, UnisBin/binary >>).
build_021e_uni([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
build_021e_uni([{_UniID, {myroom, Name, NbPlayers, _MaxPlayers}}|Tail], Acc) ->
Padding = 8 * (44 - byte_size(Name)),
Bin = << 16#ffffffff:32, NbPlayers:16/little, 0:16, Name/binary, 0:Padding >>,
build_021e_uni(Tail, [Bin|Acc]);
build_021e_uni([{UniID, {universe, Name, NbPlayers, _MaxPlayers}}|Tail], Acc) ->
Padding = 8 * (32 - byte_size(Name)),
PopString = lists:flatten(io_lib:format("~5b", [NbPlayers])),
PopString2 = << << X:8, 0:8 >> || X <- PopString >>,
Bin = << UniID:32/little, NbPlayers:16/little, 643:16/little, Name/binary, 0:Padding, PopString2/binary, 0:16 >>,
build_021e_uni(Tail, [Bin|Acc]).
%% @doc Send the current universe info along with the current level cap.
send_0222(UniID, Client=#egs_net{gid=DestGID}) ->
{_Type, Name, NbPlayers, MaxPlayers} = egs_universes:read(UniID),
Padding = 8 * (44 - byte_size(Name)),
LevelCap = egs_conf:read(level_cap),
packet_send(Client, << 16#02220300:32, 16#ffff:16, 0:16, 16#00001200:32, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
UniID:32/little, NbPlayers:16/little, MaxPlayers:16/little, Name/binary, 0:Padding, LevelCap:32/little >>).
%% @doc Display a notice on the player's screen.
%% There are four types of notices: dialog, top, scroll and timeout.
%% * dialog: A dialog in the center of the screen, which can be OK'd by players.
%% * top: Horizontal scroll on top of the screen, traditionally used for server-wide messages.
%% * scroll: Vertical scroll on the right of the screen, traditionally used for rare missions obtention messages.
%% * timeout: A dialog in the center of the screen that disappears after Duration seconds.
send_0228(Type, Duration, Message, Client=#egs_net{gid=DestGID}) ->
TypeInt = case Type of dialog -> 0; top -> 1; scroll -> 2; timeout -> 3 end,
UCS2Message = << << X:8, 0:8 >> || X <- Message >>,
packet_send(Client, << 16#02280300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
TypeInt:32/little, Duration:32/little, UCS2Message/binary, 0:16 >>).
%% @todo No idea!
%% @todo This packet hasn't been reviewed at all yet.
send_022c(A, B, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#022c0300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, A:16/little, B:16/little >>).
%% @todo Not sure. Sent when going to or from room. Possibly when changing universes too?
send_0230(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#02300300:32, 16#ffff:16, 0:16, 16#00011300:32, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64 >>).
%% @doc Send the list of players already spawned in the zone when entering it.
send_0233(Users, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
NbUsers = length(Users),
Bin = build_0233_users(Users, []),
packet_send(Client, << 16#02330300:32, DestLID:16/little, 0:16, 16#00001200:32, DestGID:32/little, 0:64,
16#00011300:32, DestGID:32/little, 0:64, NbUsers:32/little, Bin/binary, 0:608 >>).
build_0233_users([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
build_0233_users([User|Tail], Acc) ->
Bin = psu_characters:character_user_to_binary(User),
build_0233_users(Tail, [<< Bin/binary, 0:32 >>|Acc]).
%% @doc Start the zone handling: load the zone file and the objects sent separately.
send_0236(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#02360300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64 >>).
%% @doc Chat message.
send_0304(FromGID, ChatTypeID, ChatGID, ChatName, ChatModifiers, ChatMessage, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
{chat_modifiers, ChatType, ChatCutIn, ChatCutInAngle, ChatMsgLength, ChatChannel, ChatCharacterType} = ChatModifiers,
packet_send(Client, << 16#03040300:32, DestLID:16/little, 0:16, 16#00011300:32, FromGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64,
ChatTypeID:32, ChatGID:32/little, 0:64, ChatType:8, ChatCutIn:8, ChatCutInAngle:8, ChatMsgLength:8,
ChatChannel:8, ChatCharacterType:8, 0:16, ChatName/binary, ChatMessage/binary >>).
%% @todo Force send a new player location. Used for warps.
%% @todo The value before IntDir seems to be the player's current animation. 01 stand up, 08 ?, 17 normal sit
%% @todo This packet hasn't been reviewed at all yet.
send_0503({PrevX, PrevY, PrevZ, _AnyDir}, Client=#egs_net{gid=DestGID}) ->
{ok, User} = egs_users:read(DestGID),
#users{pos={X, Y, Z, Dir}, area={QuestID, ZoneID, MapID}, entryid=EntryID} = User,
IntDir = trunc(Dir * 182.0416),
packet_send(Client, << 16#05030300:32, 0:64, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64, DestGID:32/little, 0:32,
16#1000:16, IntDir:16/little, PrevX:32/little-float, PrevY:32/little-float, PrevZ:32/little-float, X:32/little-float, Y:32/little-float, Z:32/little-float,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, 1:32/little >>).
%% @todo NPC inventory. Guessing it's only for NPC characters...
%% @todo This packet hasn't been reviewed at all yet.
send_0a04(NPCGID, Client=#egs_net{gid=DestGID}) ->
{ok, Bin} = file:read_file("p/packet0a04.bin"),
packet_send(Client, << 16#0a040300:32, 0:32, 16#00001d00:32, NPCGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64, Bin/binary >>).
%% @todo Inventory related. Doesn't seem to do anything.
send_0a05(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#0a050300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64 >>).
%% @doc Send the list of ItemUUID for the items in the inventory.
send_0a06(CharUser, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
Len = length(CharUser#users.inventory),
UUIDs = lists:seq(1, Len),
Bin = iolist_to_binary([ << N:32/little >> || N <- UUIDs]),
Blanks = lists:seq(1, 60 - Len),
Bin2 = iolist_to_binary([ << 16#ffffffff:32 >> || _N <- Blanks]),
packet_send(Client, << 16#0a060300:32, DestLID:16/little, 0:48, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64, Bin/binary, Bin2/binary >>).
%% @todo Handle more than just goggles.
%% @todo This packet hasn't been reviewed at all yet.
send_0a0a(Inventory, Client=#egs_net{gid=DestGID}) ->
{ok, << _:68608/bits, Rest/bits >>} = file:read_file("p/packet0a0a.bin"),
NbItems = length(Inventory),
ItemVariables = build_0a0a_item_variables(Inventory, 1, []),
ItemConstants = build_0a0a_item_constants(Inventory, []),
packet_send(Client, << 16#0a0a0300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
NbItems:8, 0:8, 6:8, 0:72, 0:192, 0:2304, ItemVariables/binary, ItemConstants/binary, 0:13824, Rest/binary >>).
build_0a0a_item_variables([], _N, Acc) ->
Bin = iolist_to_binary(lists:reverse(Acc)),
Padding = 17280 - 8 * byte_size(Bin),
<< Bin/binary, 0:Padding >>;
build_0a0a_item_variables([{ItemID, Variables}|Tail], N, Acc) ->
build_0a0a_item_variables(Tail, N + 1, [build_item_variables(ItemID, N, Variables)|Acc]).
build_0a0a_item_constants([], Acc) ->
Bin = iolist_to_binary(lists:reverse(Acc)),
Padding = 34560 - 8 * byte_size(Bin),
<< Bin/binary, 0:Padding >>;
build_0a0a_item_constants([{ItemID, _Variables}|Tail], Acc) ->
#psu_item{name=Name, rarity=Rarity, sell_price=SellPrice, data=Data} = egs_items_db:read(ItemID),
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
NamePadding = 8 * (46 - byte_size(UCS2Name)),
<< Category:8, _:24 >> = << ItemID:32 >>,
DataBin = build_item_constants(Data),
RarityInt = Rarity - 1,
Bin = << UCS2Name/binary, 0:NamePadding, RarityInt:8, Category:8, SellPrice:32/little, DataBin/binary >>,
build_0a0a_item_constants(Tail, [Bin|Acc]).
%% @doc Send an item's description.
send_0a11(ItemID, ItemDesc, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
Length = 1 + byte_size(ItemDesc) div 2,
packet_send(Client, << 16#0a110300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
ItemID:32, Length:32/little, ItemDesc/binary, 0:16 >>).
%% @doc Quest init.
%% @todo When first entering a zone it seems LID should be set to ffff apparently.
send_0c00(CharUser, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
#users{area={QuestID, _ZoneID, _MapID}} = CharUser,
packet_send(Client, << 16#0c000300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64, QuestID:32/little,
16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32,
16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32,
16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32,
16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32 >>).
%% @todo Figure out last 4 bytes!
%% @todo This packet hasn't been reviewed at all yet.
send_0c02(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#0c020300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, 0:32 >>).
%% @doc Send the huge pack of quest files available in the counter.
send_0c06(Pack, Client) ->
packet_send(Client, << 16#0c060300:32, 0:288, 1:32/little, Pack/binary >>).
%% @doc Reply that the player is allowed to use the lobby transport. Always allow.
send_0c08(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#0c080300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:96 >>).
%% @doc Send the trial start notification.
%% @todo This packet hasn't been reviewed at all yet.
send_0c09(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#0c090300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, 0:64 >>).
%% @doc Send the counter's mission options (0 = invisible, 2 = disabled, 3 = available).
send_0c10(Options, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
Size = byte_size(Options),
packet_send(Client, << 16#0c100300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 1, 0, Size:16/little, Options/binary >>).
%% @todo Add a character (NPC or real) to the party members on the right of the screen.
%% @todo NPCid is 65535 for normal characters.
%% @todo Apparently the 4 location ids are set to 0 when inviting an NPC in the lobby - NPCs have their location set to 0 when in lobby; also odd value before PartyPos related to missions
%% @todo Not sure about LID. But seems like it.
%% @todo This packet hasn't been reviewed at all yet.
send_1004(Type, User, PartyPos, Client=#egs_net{gid=DestGID}) ->
[TypeID, LID, SomeFlag] = case Type of
npc_mission -> [16#00001d00, PartyPos, 2];
npc_invite -> [0, 16#ffffffff, 3];
_ -> 1 %% seems to be for players
end,
#users{gid=GID, npcid=NPCid, name=Name, level=Level, area={QuestID, ZoneID, MapID}, entryid=EntryID} = User,
packet_send(Client, << 16#10040300:32, 16#ffff0000:32, 0:128, 16#00011300:32, DestGID:32/little, 0:64,
TypeID:32, GID:32/little, 0:64, Name/binary,
Level:16/little, 16#ffff:16,
SomeFlag, 1, PartyPos:8, 1,
NPCid:16/little, 0:16,
%% Odd unknown values. PA related? No idea. Values on invite, 0 in-mission.
%~ 16#00001f08:32, 0:32, 16#07000000:32,
%~ 16#04e41f08:32, 0:32, 16#01000000:32,
%~ 16#64e41f08:32, 0:32, 16#02000000:32,
%~ 16#64e41f08:32, 0:32, 16#03000000:32,
%~ 16#64e41f08:32, 0:32, 16#12000000:32,
%~ 16#24e41f08:32,
0:512,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little,
LID:32/little,
0:64,
16#01000000:32, 16#01000000:32, %% @todo first is current hp, second is max hp
0:608 >>).
%% @doc Send the client's own player's party information, on the bottom left of the screen.
%% @todo Location and the 20 bytes following sometimes have values, not sure why; when joining a party maybe?
send_1005(User, Client=#egs_net{gid=DestGID}) ->
#users{name=Name, level=Level, currenthp=CurrentHP, maxhp=MaxHP} = User,
Location = << 0:512 >>,
packet_send(Client, << 16#10050300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
16#00000100:32, 0:32, 16#ffffffff:32, 0:32, 16#00011200:32, DestGID:32/little, 0:64,
Name/binary, Level:8, 0:16, 1:8, 16#01010000:32, 0:32, Location/binary,
16#ffffffff:32, 0:96, 16#ffffffff:32, 0:64, CurrentHP:32/little, MaxHP:32/little, 0:640,
16#0100ffff:32, 16#0000ff00:32, 16#ffff0000:32, 0:640, 16#ffffffff:32, 0:768,
16#0100ffff:32, 16#0000ff00:32, 16#ffff0000:32, 0:640, 16#ffffffff:32, 0:768,
16#0100ffff:32, 16#0000ff00:32, 16#ffff0000:32, 0:640, 16#ffffffff:32, 0:768,
16#0100ffff:32, 16#0000ff00:32, 16#ffff0000:32, 0:640, 16#ffffffff:32, 0:768,
16#0100ffff:32, 16#0000ff00:32, 16#ffff0000:32, 0:640, 16#ffffffff:32, 0:448,
16#ffffffff:32, 0:32, 16#ff020000:32, 16#ffff0000:32, 16#ffff0000:32, 16#ffff0000:32,
16#ffff0000:32, 16#ffff0000:32, 16#ffff0000:32, 0:3680 >>).
%% @doc Party-related events.
send_1006(EventID, Client) ->
send_1006(EventID, 0, Client).
send_1006(EventID, PartyPos, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#10060300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, EventID:8, PartyPos:8, 0:16 >>).
%% @doc Send the player's current location.
%% @todo Handle PartyPos.
%% @todo Receive the AreaName as UCS2 directly to allow for color codes and the like.
%% @todo Handle TargetLID probably (right after the padding).
%% @todo Do counters even have a name?
send_100e(CounterID, AreaName, Client=#egs_net{gid=DestGID}) ->
PartyPos = 0,
UCS2Name = << << X:8, 0:8 >> || X <- AreaName >>,
Padding = 8 * (64 - byte_size(UCS2Name)),
CounterType = if CounterID =:= 16#ffffffff -> 2; true -> 1 end,
packet_send(Client, << 16#100e0300:32, 16#ffffffbf:32, 0:128, 16#00011300:32, DestGID:32, 0:64,
1, PartyPos, 0:48, 16#ffffff7f:32, UCS2Name/binary, 0:Padding, 0:32, CounterID:32/little, CounterType:32/little >>).
send_100e({QuestID, ZoneID, MapID}, EntryID, AreaName, Client=#egs_net{gid=DestGID}) ->
PartyPos = 0,
UCS2Name = << << X:8, 0:8 >> || X <- AreaName >>,
Padding = 8 * (64 - byte_size(UCS2Name)),
packet_send(Client, << 16#100e0300:32, 16#ffffffbf:32, 0:128, 16#00011300:32, DestGID:32, 0:64,
1, PartyPos, ZoneID:16/little, MapID:16/little, EntryID:16/little, QuestID:32/little,
UCS2Name/binary, 0:Padding, 0:32, 16#ffffffff:32, 0:32 >>).
%% @todo No idea. Also the 2 PartyPos in the built packet more often than not match, but sometimes don't? That's probably because one is PartyPos and the other is LID or something.
%% @todo This packet hasn't been reviewed at all yet.
send_100f(NPCid, PartyPos, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#100f0300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, NPCid:16/little, 1, PartyPos:8, PartyPos:32/little >>).
%% @doc Send the mission's quest file when starting a new mission.
%% @todo Handle correctly. 0:32 is actually a missing value. Value before that is unknown too.
%% @todo This packet hasn't been reviewed at all yet.
send_1015(QuestID, Client=#egs_net{gid=DestGID}) ->
QuestData = egs_quests_db:quest_nbl(QuestID),
Size = byte_size(QuestData),
packet_send(Client, << 16#10150300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, QuestID:32/little, 16#01010000:32, 0:32, Size:32/little, QuestData/binary >>).
%% @todo No idea.
%% @todo This packet hasn't been reviewed at all yet.
send_1016(PartyPos, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#10160300:32, 16#ffff0000:32, 0:128, 16#00011300:32, DestGID:32/little, 0:64, PartyPos:32/little >>).
%% @todo No idea.
%% @todo This packet hasn't been reviewed at all yet.
send_101a(NPCid, PartyPos, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#101a0300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:64, NPCid:16/little, PartyPos:16/little, 16#ffffffff:32 >>).
%% @doc Mission start related.
send_1020(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#10200300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64 >>).
%% @doc Update HP in the party members information on the left.
%% @todo Handle PartyPos. Probably only pass HP later.
send_1022(#users{currenthp=HP}, Client=#egs_net{gid=DestGID}) ->
PartyPos = 0,
packet_send(Client, << 16#10220300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, HP:32/little, PartyPos:32/little >>).
%% @todo Boss related command.
%% @todo This packet hasn't been reviewed at all yet.
send_110e(Data, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#110e0300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, Data/binary, 0:32, 5:16/little, 12:16/little, 0:32, 260:32/little >>).
%% @todo Boss related command.
%% @todo This packet hasn't been reviewed at all yet.
send_1113(Data, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#11130300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, Data/binary >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
%% @todo This packet hasn't been reviewed at all yet.
send_1202(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12020300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 0:32, 16#10000000:32, 0:64, 16#14000000:32, 0:32 >>).
%% @todo Always the same value, no idea what it's for.
send_1204(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#12040300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:96, 16#20000000:32, 0:256 >>).
%% @doc Object events response?
%% @todo Not sure what Value does exactly. It's either 0 or 1.
%% @todo This packet hasn't been reviewed at all yet.
send_1205(EventID, BlockID, Value, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12050300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, EventID, BlockID, 0:16, Value, 0:24 >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
%% @todo This packet hasn't been reviewed at all yet.
send_1206(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12060300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 0:32, 16#80020000:32, 0:5120 >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
%% @todo This packet hasn't been reviewed at all yet.
send_1207(Client=#egs_net{gid=DestGID}) ->
Chunk = << 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 0:224, 16#0000ffff:32, 16#ff000000:32, 16#64000a00:32 >>,
packet_send(Client, << 16#12070300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary >>).
%% @todo Object interaction? Figure out. C probably the interaction type.
%% @todo Apparently A would be TargetID/ffffffff, B would be the player LID, C would be the object type? D still completely unknown.
%% @todo This packet hasn't been reviewed at all yet.
send_1211(A, B, C, D, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12110300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, A:32/little, B:32/little, C:32/little, D:32/little >>).
%% @doc Make the client load the quest previously sent.
%% @todo This packet hasn't been reviewed at all yet.
send_1212(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12120300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 0:19200 >>).
%% @todo Not sure. Related to keys.
%% @todo This packet hasn't been reviewed at all yet.
send_1213(A, B, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12130300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, A:32/little, B:32/little >>).
%% @todo Related to boss gates.
%% @todo This packet hasn't been reviewed at all yet.
send_1215(A, B, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12150300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, A:32/little, 0:16, B:16/little >>).
%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race.
%% @todo This packet hasn't been reviewed at all yet.
send_1216(Value, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#12160300:32, 0:32, 16#00011300:32, DestGID:32/little, 0:64, 16#00011300:32, DestGID:32/little, 0:64, Value:32/little >>).
%% @todo Send an empty partner card list.
%% @todo This packet hasn't been reviewed at all yet.
send_1501(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#15010300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:96 >>).
%% @todo Send an empty blacklist.
%% @todo This packet hasn't been reviewed at all yet.
send_1512(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#15120300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:46144 >>).
%% @todo NPC related packet, sent when there's an NPC in the area.
%% @todo This packet hasn't been reviewed at all yet.
send_1601(PartyPos, Client=#egs_net{gid=DestGID}) ->
{ok, << _:32, Bin/bits >>} = file:read_file("p/packet1601.bin"),
packet_send(Client, << 16#16010300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, PartyPos:32/little, Bin/binary >>).
%% @doc Send the player's NPC and PM information.
%% @todo The value 4 is the card priority. Find what 3 is. When sending, the first 0 is an unknown value.
%% @todo This packet hasn't been reviewed at all yet.
send_1602(Client=#egs_net{gid=DestGID}) ->
NPCList = egs_npc_db:all(),
NbNPC = length(NPCList),
Bin = iolist_to_binary([<< NPCid:8, 0, 4, 0, 3, 0:24 >> || {NPCid, _Data} <- NPCList]),
MiddlePaddingSize = 8 * (344 - byte_size(Bin)),
PMName = "My PM",
UCS2PMName = << << X:8, 0:8 >> || X <- PMName >>,
EndPaddingSize = 8 * (64 - byte_size(UCS2PMName)),
packet_send(Client, << 16#16020300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:96,
Bin/binary, 0:MiddlePaddingSize, NbNPC, 0:24, UCS2PMName/binary, 0:EndPaddingSize, 0:32 >>).
%% @doc Send the list of parties to join.
%% @todo Handle lists of parties.
%% @todo Probably has to handle a LID here, although it should always be 0.
send_1701(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#17010300:32, 0:160, 16#00011300:32, DestGID:32/little, 0:96 >>).
%% @doc Party information.
%% @todo Handle existing parties.
%% @todo This packet hasn't been reviewed at all yet.
send_1706(CharName, Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#17060300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64,
16#00000300:32, 16#d5c0faff:32, 0:64, CharName/binary,
16#78000000:32, 16#01010000:32, 0:1536, 16#0100c800:32, 16#0601010a:32, 16#ffffffff:32, 0:32 >>).
%% @doc Party settings. Item distribution is random for now.
%% @todo Handle correctly.
%% @todo This packet hasn't been reviewed at all yet.
send_170a(Client=#egs_net{gid=DestGID}) ->
packet_send(Client, << 16#170a0300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 16#01010c08:32 >>).
%% @todo Find what the heck this packet is.
%% @todo This packet hasn't been reviewed at all yet.
send_170c(Client=#egs_net{gid=DestGID}) ->
{ok, File} = file:read_file("p/packet170c.bin"),
packet_send(Client, << 16#170c0300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, File/binary >>).
%% @doc Send the background to use for the counter.
send_1711(Bg, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#17110300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:64, Bg:8, 0:24 >>).
%% @doc NPC shop request reply.
send_1a02(A, B, C, D, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#1a020300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:96,
A:16/little, B:16/little, C:16/little, D:16/little >>).
%% @doc Lumilass available hairstyles/headtypes handler.
send_1a03(User, Client=#egs_net{gid=DestGID, lid=DestLID}) ->
{ok, Conf} = file:consult("priv/lumilass.conf"),
NbHeadtypes = proplists:get_value({headtypes, User#users.gender, User#users.race}, Conf, 0),
HairstylesList = proplists:get_value({hairstyles, User#users.gender}, Conf),
NbHairstyles = length(HairstylesList),
HairstylesBin = iolist_to_binary([ << N:32 >> || N <- HairstylesList]),
packet_send(Client, << 16#1a030300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:96,
NbHairstyles:32/little, NbHeadtypes:32/little, 0:416, HairstylesBin/binary, 0:32 >>).
%% @doc PP cube handler.
%% @todo The 4 bytes before the file may vary. Everything past that is the same. Figure things out.
%% @todo This packet hasn't been reviewed at all yet.
send_1a04(Client=#egs_net{gid=DestGID}) ->
{ok, File} = file:read_file("p/ppcube.bin"),
packet_send(Client, << 16#1a040300:32, 16#ffff:16, 0:144, 16#00011300:32, DestGID:32/little, 0:64, 0:32, File/binary >>).
%% @doc Available types handler. Enable all 16 types.
send_1a07(Client=#egs_net{gid=DestGID, lid=DestLID}) ->
packet_send(Client, << 16#1a070300:32, DestLID:16/little, 0:144, 16#00011300:32, DestGID:32/little, 0:160,
16#01010101:32, 16#01010101:32, 16#01010101:32, 16#01010101:32 >>).
%% Common binary building functions.
%% @todo Handle class levels.
build_char_level(#users{type=Type, level=Level, exp=EXP, blastbar=BlastBar, luck=Luck, money=Money}) ->
ClassesBin = case Type of
npc ->
<< 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32,
16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32,
16#4e4f4630:32, 16#08000000:32, 0:32, 0:32, 16#4e454e44:32 >>;
_ ->
<< 0:160,
16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32,
16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32, 16#01000000:32 >>
end,
PlayTime = 0, %% @todo
<< Level:32/little, BlastBar:16/little, Luck:8, 0:40, EXP:32/little, 0:32, Money:32/little, PlayTime:32/little, ClassesBin/binary >>.
build_item_constants(#psu_clothing_item{appearance=Appearance, manufacturer=Manufacturer, type=Type, overlap=Overlap, gender=Gender, colors=Colors}) ->
GenderInt = case Gender of male -> 16#1b; female -> 16#2b end,
<< Appearance:16, Type:4, Manufacturer:4, Overlap:8, GenderInt:8, Colors/binary, 0:40 >>;
build_item_constants(#psu_consumable_item{max_quantity=MaxQuantity, pt_diff=PointsDiff,
status_effect=StatusEffect, target=Target, use_condition=UseCondition, item_effect=ItemEffect}) ->
<< 0:8, MaxQuantity:8, Target:8, UseCondition:8, PointsDiff:16/little, StatusEffect:8, ItemEffect:8, 0:96 >>;
build_item_constants(#psu_parts_item{appearance=Appearance, manufacturer=Manufacturer, type=Type, overlap=Overlap, gender=Gender}) ->
GenderInt = case Gender of male -> 16#14; female -> 16#24 end,
<< Appearance:16, Type:4, Manufacturer:4, Overlap:8, GenderInt:8, 0:120 >>;
%% @todo Handle rank properly.
build_item_constants(#psu_striking_weapon_item{pp=PP, atp=ATP, ata=ATA, atp_req=Req, shop_element=#psu_element{type=EleType, percent=ElePercent},
hand=Hand, max_upgrades=MaxUpgrades, attack_label=AttackLabel}) ->
Rank = 4,
HandInt = case Hand of
both -> 0;
_ -> error
end,
<< PP:16/little, ATP:16/little, ATA:16/little, Req:16/little, 16#ffffff:24,
EleType:8, ElePercent:8, HandInt:8, 0:8, Rank:8, 0:8, MaxUpgrades:8, AttackLabel:8, 0:8 >>;
build_item_constants(#psu_trap_item{max_quantity=MaxQuantity}) ->
<< 2:32/little, 16#ffffff:24, MaxQuantity:8, 0:96 >>;
build_item_constants(#psu_special_item{}) ->
<< 0:160 >>.
build_item_variables(ItemID, ItemUUID, #psu_clothing_item_variables{color=ColorNb}) ->
#psu_item{rarity=Rarity, data=#psu_clothing_item{colors=ColorsBin}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
ColorInt = if ColorNb < 5 -> ColorNb; true -> 16#10 + ColorNb - 5 end,
Bits = ColorNb * 8,
<< _Before:Bits, ColorA:4, ColorB:4, _After/bits >> = ColorsBin,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:88, RarityInt:8, ColorA:8, ColorB:8, ColorInt:8, 0:72 >>;
build_item_variables(ItemID, ItemUUID, #psu_consumable_item_variables{quantity=Quantity}) ->
#psu_item{rarity=Rarity, data=#psu_consumable_item{max_quantity=MaxQuantity, action=Action}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, Quantity:32/little, MaxQuantity:32/little, 0:24, RarityInt:8, Action:8, 0:88 >>;
build_item_variables(ItemID, ItemUUID, #psu_parts_item_variables{}) ->
#psu_item{rarity=Rarity} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:88, RarityInt:8, 0:96 >>;
%% @todo Handle rank, rarity and hands properly.
build_item_variables(ItemID, ItemUUID, Variables) when element(1, Variables) =:= psu_striking_weapon_item_variables ->
#psu_striking_weapon_item_variables{is_active=IsActive, slot=Slot, current_pp=CurrentPP, max_pp=MaxPP,
element=#psu_element{type=EleType, percent=ElePercent}, pa=#psu_pa{type=PAType, level=PALevel}} = Variables,
Rank = 4,
Grind = 0,
Rarity = 14, %% Rarity - 1
Hand = both,
<< _:8, WeaponType:8, _:16 >> = << ItemID:32 >>,
HandBin = case Hand of
both -> << 16#0000:16 >>;
_ -> error
end,
<< IsActive:8, Slot:8, 0:16, ItemUUID:32/little, ItemID:32, 0:32, CurrentPP:16/little, MaxPP:16/little, 0:16, %% @todo What's this 0:16?
Grind:4, Rank:4, Rarity:8, EleType:8, ElePercent:8, HandBin/binary, WeaponType:8, PAType:8, PALevel:8, 0:40 >>;
build_item_variables(ItemID, ItemUUID, #psu_special_item_variables{}) ->
Action = case ItemID of
16#11010000 -> << 16#12020100:32 >>;
16#11020000 -> << 16#15000000:32 >>;
16#11020100 -> << 0:32 >>;
16#11020200 -> << 0:32 >>
end,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:24, 16#80:8, 0:56, 16#80:8, 0:32, Action/binary, 0:32 >>;
build_item_variables(ItemID, ItemUUID, #psu_trap_item_variables{quantity=Quantity}) ->
#psu_item{rarity=Rarity, data=#psu_trap_item{max_quantity=MaxQuantity}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, Quantity:32/little, MaxQuantity:32/little, 0:24, RarityInt:8, 0:96 >>.
%% Utility functions.
%% @doc Prepare a packet. Return the real size and padding at the end.
packet_prepare(Packet) ->
Size = 4 + byte_size(Packet),
case Size rem 4 of
0 -> {ok, Size, <<>>};
2 -> {ok, Size + 2, << 0:16 >>};
_ -> {error, badarg}
end.
%% @doc Send a packet. The packet argument must not contain the size field.
packet_send(Client, Packet) ->
{ok, Size, Padding} = packet_prepare(Packet),
packet_send(Client, << Size:32/little, Packet/binary, Padding/binary >>, Size).
%% Send a normal command.
packet_send(#egs_net{socket=Socket, transport=Transport}, Packet, Size)
when Size =< 16#4000 ->
Transport:send(Socket, Packet);
%% Send a fragmented command when size is too big.
packet_send(Client, Packet, Size) ->
packet_fragment_send(Client, Packet, Size, 0).
%% Send the last chunk of a fragmented command.
packet_fragment_send(#egs_net{socket=Socket, transport=Transport}, Packet,
Size, Current) when Size - Current =< 16#4000 ->
FragmentSize = 16#10 + byte_size(Packet),
Fragment = << FragmentSize:32/little, 16#0b030000:32, Size:32/little, Current:32/little, Packet/binary >>,
Transport:send(Socket, Fragment);
%% Send another chunk of a fragmented command.
packet_fragment_send(Client=#egs_net{socket=Socket, transport=Transport}, Packet,
Size, Current) ->
<< Chunk:131072/bits, Rest/bits >> = Packet,
Fragment = << 16#10400000:32, 16#0b030000:32, Size:32/little, Current:32/little, Chunk/binary >>,
Transport:send(Socket, Fragment),
packet_fragment_send(Client, Rest, Size, Current + 16#4000).

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Quest handler.
%%
%% This file is part of EGS.
@ -43,7 +43,7 @@ zone_pid(Pid, ZoneID) ->
init([UniID, QuestID]) ->
Zones = egs_quests_db:quest_zones(QuestID),
ZonesPids = lists:map(fun({ZoneID, ZoneData}) ->
{ok, Pid} = supervisor:start_child(egs_zones_sup, {{zone, UniID, QuestID, ZoneID}, {egs_zones, start_link, [UniID, QuestID, ZoneID, ZoneData]}, permanent, 5000, worker, dynamic}),
{ok, Pid} = egs_zones_sup:start_zone(UniID, QuestID, ZoneID, ZoneData),
{ZoneID, Pid}
end, Zones),
{ok, #state{zones=ZonesPids}}.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS quests database and cache manager.
%%
%% This file is part of EGS.
@ -19,14 +19,15 @@
-module(egs_quests_db).
-behavior(gen_server).
-export([start_link/0, stop/0, quest_nbl/1, zone_nbl/2, area_type/2, quest_zones/1, set/3, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
-record(state, {quests=[], quests_bin=[], zones_bin=[], sets=[]}).
%% Use the module name for the server's name.
-define(SERVER, ?MODULE).
-record(state, {quests=[], quests_bin=[], zones_bin=[], sets=[]}).
%% API.
%% @spec start_link() -> {ok,Pid::pid()}
@ -105,7 +106,7 @@ handle_call({zone_nbl, QuestID, ZoneID}, _From, State=#state{quests=QuestsCache,
{Set0, SetPtrs} = egs_files:load_set_rel(ZoneDir ++ io_lib:format("set_r~b.conf", [0]), AreaID, Maps, FilePos),
ScriptBin = egs_files:load_script_bin(ZoneDir ++ "script.es"),
ScriptBinSize = byte_size(ScriptBin),
ScriptBin2 = egs_prs:compress(ScriptBin),
ScriptBin2 = prs:compress(ScriptBin),
ScriptBinSize2 = byte_size(ScriptBin2),
ScriptBin3 = << ScriptBinSize:32/little, ScriptBinSize2:32/little, 0:32, 1:32/little, 0:96, ScriptBin2/binary >>,
TextBin = egs_files:load_text_bin(ZoneDir ++ "text.bin.en_US.txt"),

View File

@ -1,6 +1,6 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Supervisor for the patch, login and game listener processes.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Supervisor for the egs_quests gen_server.
%%
%% This file is part of EGS.
%%
@ -17,27 +17,30 @@
%% 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_servers_sup).
-module(egs_quests_sup).
-behaviour(supervisor).
-export([start_link/0]). %% API.
-export([start_link/0, start_quest/2]). %% API.
-export([init/1]). %% supervisor.
-define(SUPERVISOR, ?MODULE).
%% API.
-spec start_link() -> {ok, Pid::pid()}.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
-spec start_quest(egs:uniid(), egs:questid()) -> {ok, pid()}.
start_quest(UniID, QuestID) ->
supervisor:start_child(?SUPERVISOR, [UniID, QuestID]).
%% supervisor.
-spec init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{egs_quests,
{egs_quests, start_link, []}, temporary, brutal_kill,
worker, [egs_quests]}]}}.
init([]) ->
PatchPorts = egs_conf:read(patch_ports),
LoginPorts = egs_conf:read(login_ports),
{_ServerIP, GamePort} = egs_conf:read(game_server),
PatchProcs = [{{egs_patch_server, Port}, {egs_patch_server, start_link, [Port]}, permanent, 5000, worker, dynamic} || Port <- PatchPorts],
LoginProcs = [{{egs_login_server, Port}, {egs_login_server, start_link, [Port]}, permanent, 5000, worker, dynamic} || Port <- LoginPorts],
Procs = lists:flatten([PatchProcs, LoginProcs, {egs_game_server, {egs_game_server, start_link, [GamePort]}, permanent, 5000, worker, dynamic}]),
{ok, {{one_for_one, 10, 10}, Procs}}.
{ok, {{simple_one_for_one, 0, 1}, [{egs_quests,
{egs_quests, start_link, []}, temporary, brutal_kill,
worker, [egs_quests]}]}}.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS script compiler.
%%
%% This file is part of EGS.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS script lexer.
%%
%% This file is part of EGS.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS script parser.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS seasons management.
%% @todo When we know how to do it we should change the lobby automatically to the next season.
%%
@ -20,6 +20,7 @@
-module(egs_seasons).
-behavior(gen_server).
-export([start_link/0, stop/0, read/1]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS shops database.
%%
%% This file is part of EGS.
@ -19,6 +19,7 @@
-module(egs_shops_db).
-behavior(gen_server).
-export([start_link/0, stop/0, nth/2, read/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.

54
apps/egs/src/egs_sup.erl Normal file
View File

@ -0,0 +1,54 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Top-level supervisor for the egs application.
%%
%% 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_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([]) ->
Procs = procs([egs_conf, {sup, egs_quests_sup}, {sup, egs_zones_sup},
egs_accounts, egs_users, egs_seasons, egs_counters_db, egs_items_db,
egs_npc_db, egs_quests_db, egs_shops_db, egs_universes], []),
{ok, {{one_for_one, 10, 10}, Procs}}.
%% Internal.
-spec procs([module()|{sup, module()}], [supervisor:child_spec()])
-> [supervisor:child_spec()].
procs([], Acc) ->
lists:reverse(Acc);
procs([{sup, Module}|Tail], Acc) ->
procs(Tail, [sup(Module)|Acc]);
procs([Module|Tail], Acc) ->
procs(Tail, [worker(Module)|Acc]).
-spec worker(M) -> {M, {M, start_link, []}, permanent, 5000, worker, dynamic}.
worker(Module) ->
{Module, {Module, start_link, []}, permanent, 5000, worker, dynamic}.
-spec sup(M) -> {M, {M, start_link, []}, permanent, 5000, supervisor, [M]}.
sup(Module) ->
{Module, {Module, start_link, []}, permanent, 5000, supervisor, [Module]}.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS universes handler.
%%
%% This file is part of EGS.
@ -19,14 +19,15 @@
-module(egs_universes).
-behavior(gen_server).
-export([start_link/0, stop/0, all/0, defaultid/0, enter/1, leave/1, myroomid/0, read/1, lobby_pid/2, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
-record(state, {unis=[], lobbies=[]}).
%% Use the module name for the server's name.
-define(SERVER, ?MODULE).
-record(state, {unis=[], lobbies=[]}).
%% Default universe IDs.
-define(MYROOM_ID, 21).
-define(DEFAULT_ID, 26).
@ -151,6 +152,6 @@ create_unis([Name|Tail], UniID, Acc) ->
%% @doc Start lobbies for the given universe.
init_lobbies(UniID) ->
lists:map(fun(QuestID) ->
{ok, Pid} = supervisor:start_child(egs_quests_sup, {{quest, UniID, QuestID}, {egs_quests, start_link, [UniID, QuestID]}, permanent, 5000, worker, dynamic}),
{ok, Pid} = egs_quests_sup:start_quest(UniID, QuestID),
{{UniID, QuestID}, Pid}
end, ?LOBBIES).

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Users handling.
%%
%% This file is part of EGS.
@ -29,9 +29,8 @@
-include("include/records.hrl").
%% @todo We have a conflict with the global state record. Solve it then rename this to state.
-record(stateu, {
users = [] :: list({GID::integer(), #users{}})
-record(state, {
users = [] :: list({egs:gid(), #users{}})
}).
%% API.
@ -98,122 +97,117 @@ money_add(GID, MoneyDiff) ->
%% gen_server.
init([]) ->
{ok, #stateu{}}.
{ok, #state{}}.
handle_call({find_by_pid, Pid}, _From, State) ->
[User] = [User || {_GID, User} <- State#stateu.users, User#users.pid =:= Pid],
{reply, User, State};
L = [User || {_GID, User} <- State#state.users, User#users.pid =:= Pid],
case L of
[] -> {reply, undefined, State};
[User] -> {reply, User, State}
end;
handle_call({set_zone, GID, ZonePid, LID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Users2 = lists:delete({GID, User}, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{zonepid=ZonePid, lid=LID}}|Users2]}};
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Users2 = lists:delete({GID, User}, State#state.users),
{reply, ok, State#state{users=[{GID, User#users{zonepid=ZonePid, lid=LID}}|Users2]}};
handle_call({read, GID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
{GID, User} = lists:keyfind(GID, 1, State#state.users),
{reply, {ok, User}, State};
handle_call({select, UsersGID}, _From, State) ->
Users = [begin
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
{GID, User} = lists:keyfind(GID, 1, State#state.users),
User
end || GID <- UsersGID],
{reply, Users, State};
handle_call({write, User}, _From, State) ->
Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
{reply, ok, State#stateu{users=[{User#users.gid, User}|Users2]}};
Users2 = lists:keydelete(User#users.gid, 1, State#state.users),
{reply, ok, State#state{users=[{User#users.gid, User}|Users2]}};
handle_call({delete, GID}, _From, State) ->
Users2 = lists:keydelete(GID, 1, State#stateu.users),
{reply, ok, State#stateu{users=Users2}};
Users2 = lists:keydelete(GID, 1, State#state.users),
{reply, ok, State#state{users=Users2}};
handle_call({item_nth, GID, ItemIndex}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Item = lists:nth(ItemIndex + 1, (User#users.character)#characters.inventory),
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Item = lists:nth(ItemIndex + 1, User#users.inventory),
{reply, Item, State};
handle_call({item_add, GID, ItemID, Variables}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Character = User#users.character,
Inventory = Character#characters.inventory,
Inventory2 = case Variables of
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Inventory = case Variables of
#psu_consumable_item_variables{quantity=Quantity} ->
#psu_item{data=#psu_consumable_item{max_quantity=MaxQuantity}} = egs_items_db:read(ItemID),
{ItemID, #psu_consumable_item_variables{quantity=Quantity2}} = case lists:keyfind(ItemID, 1, Inventory) of
{ItemID, #psu_consumable_item_variables{quantity=Quantity2}} = case lists:keyfind(ItemID, 1, User#users.inventory) of
false -> New = true, {ItemID, #psu_consumable_item_variables{quantity=0}};
Tuple -> New = false, Tuple
end,
Quantity3 = Quantity + Quantity2,
if Quantity3 =< MaxQuantity ->
lists:keystore(ItemID, 1, Inventory, {ItemID, #psu_consumable_item_variables{quantity=Quantity3}})
lists:keystore(ItemID, 1, User#users.inventory, {ItemID, #psu_consumable_item_variables{quantity=Quantity3}})
end;
#psu_trap_item_variables{quantity=Quantity} ->
#psu_item{data=#psu_trap_item{max_quantity=MaxQuantity}} = egs_items_db:read(ItemID),
{ItemID, #psu_trap_item_variables{quantity=Quantity2}} = case lists:keyfind(ItemID, 1, Inventory) of
{ItemID, #psu_trap_item_variables{quantity=Quantity2}} = case lists:keyfind(ItemID, 1, User#users.inventory) of
false -> New = true, {ItemID, #psu_trap_item_variables{quantity=0}};
Tuple -> New = false, Tuple
end,
Quantity3 = Quantity + Quantity2,
if Quantity3 =< MaxQuantity ->
lists:keystore(ItemID, 1, Inventory, {ItemID, #psu_trap_item_variables{quantity=Quantity3}})
lists:keystore(ItemID, 1, User#users.inventory, {ItemID, #psu_trap_item_variables{quantity=Quantity3}})
end;
_ ->
New = true,
if length(Inventory) < 60 ->
Inventory ++ [{ItemID, Variables}]
if length(User#users.inventory) < 60 ->
User#users.inventory ++ [{ItemID, Variables}]
end
end,
Character2 = Character#characters{inventory=Inventory2},
Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
State2 = State#stateu{users=[{GID, User#users{character=Character2}}|Users2]},
Users2 = lists:keydelete(User#users.gid, 1, State#state.users),
State2 = State#state{users=[{GID, User#users{inventory=Inventory}}|Users2]},
case New of
false -> {reply, 16#ffffffff, State2};
true -> {reply, length(Inventory2), State2}
true -> {reply, length(Inventory), State2}
end;
handle_call({item_qty_add, GID, ItemIndex, QuantityDiff}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Character = User#users.character,
Inventory = Character#characters.inventory,
{ItemID, Variables} = lists:nth(ItemIndex + 1, Inventory),
case Variables of
{GID, User} = lists:keyfind(GID, 1, State#state.users),
{ItemID, Variables} = lists:nth(ItemIndex + 1, User#users.inventory),
Inventory = case Variables of
#psu_trap_item_variables{quantity=Quantity} ->
#psu_item{data=#psu_trap_item{max_quantity=MaxQuantity}} = egs_items_db:read(ItemID),
Quantity2 = Quantity + QuantityDiff,
if Quantity2 =:= 0 ->
Inventory2 = string:substr(Inventory, 1, ItemIndex) ++ string:substr(Inventory, ItemIndex + 2);
string:substr(User#users.inventory, 1, ItemIndex) ++ string:substr(User#users.inventory, ItemIndex + 2);
Quantity2 > 0, Quantity2 =< MaxQuantity ->
Variables2 = Variables#psu_trap_item_variables{quantity=Quantity2},
Inventory2 = string:substr(Inventory, 1, ItemIndex) ++ [{ItemID, Variables2}] ++ string:substr(Inventory, ItemIndex + 2)
string:substr(User#users.inventory, 1, ItemIndex) ++ [{ItemID, Variables2}] ++ string:substr(User#users.inventory, ItemIndex + 2)
end
end,
Character2 = Character#characters{inventory=Inventory2},
Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{character=Character2}}|Users2]}};
Users2 = lists:keydelete(User#users.gid, 1, State#state.users),
{reply, ok, State#state{users=[{GID, User#users{inventory=Inventory}}|Users2]}};
handle_call({shop_enter, GID, ShopID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Users2 = lists:delete({GID, User}, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{shopid=ShopID}}|Users2]}};
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Users2 = lists:delete({GID, User}, State#state.users),
{reply, ok, State#state{users=[{GID, User#users{shopid=ShopID}}|Users2]}};
handle_call({shop_leave, GID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Users2 = lists:delete({GID, User}, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{shopid=undefined}}|Users2]}};
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Users2 = lists:delete({GID, User}, State#state.users),
{reply, ok, State#state{users=[{GID, User#users{shopid=undefined}}|Users2]}};
handle_call({shop_get, GID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
{GID, User} = lists:keyfind(GID, 1, State#state.users),
{reply, User#users.shopid, State};
handle_call({money_add, GID, MoneyDiff}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Character = User#users.character,
Money = Character#characters.money + MoneyDiff,
{GID, User} = lists:keyfind(GID, 1, State#state.users),
Money = User#users.money + MoneyDiff,
if Money >= 0 ->
Character2 = Character#characters{money=Money},
Users2 = lists:delete({GID, User}, State#stateu.users),
{reply, ok, [{GID, User#users{character=Character2}}|Users2]}
Users2 = lists:delete({GID, User}, State#state.users),
{reply, ok, [{GID, User#users{money=Money}}|Users2]}
end;
handle_call(stop, _From, State) ->
@ -223,13 +217,13 @@ handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast({broadcast, Message, PlayersGID}, State) ->
[begin {GID, #users{pid=Pid}} = lists:keyfind(GID, 1, State#stateu.users),
[begin {GID, #users{pid=Pid}} = lists:keyfind(GID, 1, State#state.users),
Pid ! Message
end || GID <- PlayersGID],
{noreply, State};
handle_cast({broadcast_all, Message}, State) ->
[Pid ! Message || {_GID, #users{pid=Pid}} <- State#stateu.users],
[Pid ! Message || {_GID, #users{pid=Pid}} <- State#state.users],
{noreply, State};
handle_cast(_Msg, State) ->

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Zone handler.
%%
%% This file is part of EGS.

View File

@ -0,0 +1,47 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Supervisor for the egs_zones gen_server.
%%
%% 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_zones_sup).
-behaviour(supervisor).
-export([start_link/0, start_zone/4]). %% API.
-export([init/1]). %% supervisor.
-define(SUPERVISOR, ?MODULE).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
-spec start_zone(egs:uniid(), egs:questid(), egs:zoneid(), tuple())
-> {ok, pid()}.
start_zone(UniID, QuestID, ZoneID, ZoneData) ->
supervisor:start_child(?SUPERVISOR, [UniID, QuestID, ZoneID, ZoneData]).
%% supervisor.
-spec init([]) -> {ok, {{simple_one_for_one, 0, 1}, [{egs_zones,
{egs_zones, start_link, []}, temporary, brutal_kill,
worker, [egs_zones]}]}}.
init([]) ->
{ok, {{simple_one_for_one, 0, 1}, [{egs_zones,
{egs_zones, start_link, []}, temporary, brutal_kill,
worker, [egs_zones]}]}}.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Chair object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Door object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Entrance object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Exit object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Invisible block object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Label object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc NPC object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc PP cube object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Sensor object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Static model object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Type counter object.
%%
%% This file is part of EGS.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Uni cube object.
%%
%% This file is part of EGS.

View File

@ -0,0 +1,96 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc General character 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_characters).
-export([
character_user_to_binary/1
]).
-include("include/records.hrl").
%% @doc Convert a character tuple into a binary to be sent to clients.
%% Only contains the actually saved data, not the stats and related information.
%% @todo The name isn't very good anymore now that I switched #characters to #users.
character_tuple_to_binary(Tuple) ->
#users{name=Name, race=Race, gender=Gender, class=Class, appearance=Appearance} = Tuple,
RaceBin = race_atom_to_binary(Race),
GenderBin = gender_atom_to_binary(Gender),
ClassBin = class_atom_to_binary(Class),
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 >>.
%% @doc Convert a character tuple into a binary to be sent to clients.
%% Contains everything from character_tuple_to_binary/1 along with location, stats, SE and more.
%% @todo The second StatsBin seems unused. Not sure what it's for.
%% @todo Find out what the big block of 0 is at the end.
%% @todo The value before IntDir seems to be the player's current animation. 01 stand up, 08 ?, 17 normal sit
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 = character_tuple_to_binary(User),
StatsBin = stats_tuple_to_binary(Stats),
EXPNextLevel = 100,
EXPCurrentLevel = 0,
IntDir = trunc(Dir * 182.0416),
TypeID = case Type of npc -> 16#00001d00; _ -> 16#00001200 end,
NPCStuff = case Type of npc -> 16#01ff; _ -> 16#0000 end,
<< TypeID:32, CharGID:32/little, 0:64, CharLID:16/little, 0:16, NPCStuff:16, NPCid:16/little, QuestID:32/little,
ZoneID:32/little, MapID:32/little, EntryID:16/little, 0:16,
16#0100:16, IntDir:16/little, X:32/little-float, Y:32/little-float, Z:32/little-float, 0:64,
PrevQuestID:32/little, PrevZoneID:32/little, PrevMapID:32/little, PrevEntryID:32/little,
CharBin/binary, EXPNextLevel:32/little, EXPCurrentLevel:32/little, MaxHP:32/little,
StatsBin/binary, 0:96, Level:32/little, StatsBin/binary, CurrentHP:32/little, MaxHP:32/little,
0:1344, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:160, 16#0000803f:32, 0:352 >>.
%% @doc Convert a class atom into a binary to be sent to clients.
class_atom_to_binary(Class) ->
case Class of
hunter -> 12;
ranger -> 13;
force -> 14;
acro -> 15
end.
%% @doc Convert a gender atom into a binary to be sent to clients.
gender_atom_to_binary(Gender) ->
case Gender of
male -> 0;
female -> 1
end.
%% @doc Convert a race atom into a binary to be sent to clients.
race_atom_to_binary(Race) ->
case Race of
human -> 0;
newman -> 1;
cast -> 2;
beast -> 3
end.
%% @doc Convert the tuple of stats data into a binary to be sent to clients.
stats_tuple_to_binary(Tuple) ->
{stats, ATP, ATA, TP, DFP, EVP, MST, STA} = Tuple,
<< ATP:16/little, DFP:16/little, ATA:16/little, EVP:16/little,
STA:16/little, 0:16, TP:16/little, MST:16/little >>.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc Party gen_server.
%%
%% This file is part of EGS.

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

View File

@ -22,4 +22,6 @@
%% @doc Files in the DATA folder.
{{folder, "DATA"}, [
"1ffd0db3e0b54048caff394e9c09eda8", %% crc.bin
"bb04cc8e1727288bd2a336d60040eff1" %% SmutFilter_J.bin
]}.

View File

@ -0,0 +1,3 @@
{deps, [
{cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "HEAD"}}
]}.

View File

@ -0,0 +1,14 @@
%%-*- mode: erlang -*-
{application, egs_patch, [
{description, "EGS patch server"},
{vsn, "0.1.0"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib,
cowboy
]},
{mod, {egs_patch_app, []}},
{env, []}
]}.

View File

@ -0,0 +1,47 @@
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
%%
%% 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_patch_app).
-behaviour(application).
-export([start/2, stop/1]). %% API.
-type application_start_type()
:: normal | {takeover, node()} | {failover, node()}.
%% API.
-spec start(application_start_type(), term()) -> {ok, pid()}.
start(_Type, _StartArgs) ->
{ok, PatchPorts} = application:get_env(patch_ports),
start_listeners(PatchPorts),
egs_patch_sup:start_link().
-spec stop(term()) -> ok.
stop(_State) ->
ok.
%% Internal.
-spec start_listeners([inet:ip_port()]) -> ok.
start_listeners([]) ->
ok;
start_listeners([Port|Tail]) ->
{ok, _Pid} = cowboy:start_listener({patch, Port}, 10,
cowboy_tcp_transport, [{port, Port}],
egs_patch_protocol, []),
start_listeners(Tail).

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS patch files database and cache manager.
%%
%% This file is part of EGS.
@ -19,17 +19,18 @@
-module(egs_patch_files_db).
-behavior(gen_server).
-export([start_link/0, stop/0, list/0, check/3, get_size/1, get_info/1, reload/0]). %% API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
%% Use the module name for the server's name.
-define(SERVER, ?MODULE).
-include_lib("kernel/include/file.hrl").
-record(state, {list_bin=[], files=[]}).
-record(file, {crc, size, folder, filename_bin, full_filename}).
%% Use the module name for the server's name.
-define(SERVER, ?MODULE).
%% API.
%% @spec start_link() -> {ok,Pid::pid()}
@ -106,21 +107,23 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal.
build_state() ->
{ok, Terms} = file:consult("priv/patch.conf"),
{ok, App} = application:get_application(),
PrivDir = code:priv_dir(App),
{ok, Terms} = file:consult([PrivDir, "/patch.conf"]),
Folders = proplists:get_value(folders, Terms),
{ListBin, Files} = build_list_bin(Folders, Terms),
{ListBin, Files} = build_list_bin(Folders, Terms, [PrivDir, "/files/"]),
#state{list_bin=ListBin, files=Files}.
%% The file number must start at 0.
build_list_bin(Folders, Terms) ->
build_list_bin(Folders, Terms, 0, [], []).
build_list_bin([], _Terms, _N, Acc, FilesAcc) ->
build_list_bin(Folders, Terms, PatchDir) ->
build_list_bin(Folders, Terms, PatchDir, 0, [], []).
build_list_bin([], _Terms, _PatchDir, _N, Acc, FilesAcc) ->
Bin = list_to_binary(lists:reverse(Acc)),
Bin2 = << 16#08:32/little, 16#06:32/little, Bin/binary, 16#08:32/little, 16#08:32/little >>,
{Bin2, lists:flatten(FilesAcc)};
build_list_bin([Folder|Tail], Terms, N, Acc, FilesAcc) ->
build_list_bin([Folder|Tail], Terms, PatchDir, N, Acc, FilesAcc) ->
Filenames = proplists:get_value({folder, Folder}, Terms),
{BinFiles, Files, N2} = build_files_bin(Folder, Filenames, N),
{BinFiles, Files, N2} = build_files_bin(Folder, Filenames, PatchDir, N),
BinFiles2 = case Folder of
root -> BinFiles;
_Any ->
@ -129,17 +132,17 @@ build_list_bin([Folder|Tail], Terms, N, Acc, FilesAcc) ->
<< 16#48:32/little, 16#09:32/little, FolderBin/binary, 0:Padding,
BinFiles/binary, 16#08:32/little, 16#0a:32/little >>
end,
build_list_bin(Tail, Terms, N2, [BinFiles2|Acc], [Files|FilesAcc]).
build_list_bin(Tail, Terms, PatchDir, N2, [BinFiles2|Acc], [Files|FilesAcc]).
build_files_bin(Folder, Filenames, N) ->
build_files_bin(Folder, Filenames, N, [], []).
build_files_bin(_Folder, [], N, Acc, FilesAcc) ->
build_files_bin(Folder, Filenames, PatchDir, N) ->
build_files_bin(Folder, Filenames, PatchDir, N, [], []).
build_files_bin(_Folder, [], _PatchDir, N, Acc, FilesAcc) ->
Bin = list_to_binary(lists:reverse(Acc)),
{Bin, FilesAcc, N};
build_files_bin(Folder, [Filename|Tail], N, Acc, FilesAcc) ->
build_files_bin(Folder, [Filename|Tail], PatchDir, N, Acc, FilesAcc) ->
FullFilename = case Folder of
root -> ["priv/patch/"|Filename];
_Any -> ["priv/patch/",Folder,"/"|Filename]
root -> [PatchDir|Filename];
_Any -> [PatchDir,Folder,"/"|Filename]
end,
Size = file_get_size(FullFilename),
CRC = file_get_crc(FullFilename),
@ -147,7 +150,7 @@ build_files_bin(Folder, [Filename|Tail], N, Acc, FilesAcc) ->
Padding = 8 * (64 - length(Filename)),
FilenameBin2 = << FilenameBin/binary, 0:Padding >>,
Bin = << 16#4c:32/little, 16#07:32/little, N:32/little, FilenameBin2/binary >>,
build_files_bin(Folder, Tail, N + 1, [Bin|Acc], [{N, #file{crc=CRC, size=Size, folder=Folder, filename_bin=FilenameBin2, full_filename=FullFilename}}|FilesAcc]).
build_files_bin(Folder, Tail, PatchDir, N + 1, [Bin|Acc], [{N, #file{crc=CRC, size=Size, folder=Folder, filename_bin=FilenameBin2, full_filename=FullFilename}}|FilesAcc]).
file_get_size(Filename) ->
{ok, FileInfo} = file:read_file_info(Filename),

View File

@ -0,0 +1,218 @@
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
%%
%% 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/>.
%% @doc Cowboy protocol module for the patch server.
-module(egs_patch_protocol).
-export([start_link/4, init/2]).
%% @todo Move that in a configuration file.
-define(TIMEOUT, 5000).
-record(state, {
socket :: inet:socket(),
transport :: module(),
buffer = <<>> :: binary(),
files = [] :: list(integer())
}).
-type state() :: #state{}.
-type cmd() :: 0..16#14.
-type cmd_size() :: 0..16#ffffffff.
-spec start_link(pid(), inet:socket(), module(), []) -> {ok, pid()}.
start_link(_ListenerPid, Socket, Transport, []) ->
Pid = spawn_link(?MODULE, init, [Socket, Transport]),
{ok, Pid}.
-spec init(inet:socket(), module()) -> ok | closed.
init(Socket, Transport) ->
State = #state{socket=Socket, transport=Transport},
send_01(State),
wait_hello(State).
-spec next(state()) -> {ok, cmd(), cmd_size(), binary(), state()} | closed.
next(State=#state{buffer= << Size:32/little, Cmd:16/little, _:16, Rest/bits >>})
when byte_size(Rest) + 8 >= Size ->
Size2 = Size - 8,
<< Data:Size2/binary, Buffer/bits >> = Rest,
{ok, Cmd, Size, Data, State#state{buffer=Buffer}};
next(State=#state{socket=Socket, transport=Transport, buffer=Buffer}) ->
Transport:setopts(Socket, [{active, once}]),
{OK, Closed, Error} = Transport:messages(),
receive
{OK, Socket, Data} -> next(State#state{
buffer= << Buffer/binary, Data/binary >>});
{Closed, Socket} -> closed;
{Error, Socket, _Reason} -> closed
after ?TIMEOUT ->
closed
end.
-spec wait_hello(state()) -> ok | closed.
wait_hello(State) ->
case next(State) of
{ok, 16#14, 52, Data, State2} -> handle_hello(State2, Data);
closed -> closed
end.
-spec handle_hello(state(), binary()) -> ok | closed.
handle_hello(State=#state{socket=Socket, transport=Transport}, Data) ->
<< 16#e44c0915:32, UnknownA:32/little,
UnknownB:32/little, UnknownC:32/little, UnknownD:32/little,
_GameVersion:32/little, UnknownE:32/little, 0:128 >> = Data,
io:format("patch hello: ~p ~p ~p ~p ~p~n",
[UnknownA, UnknownB, UnknownC, UnknownD, UnknownE]),
ListBin = egs_patch_files_db:list(),
Transport:send(Socket, ListBin),
wait_fileinfo_begin(State).
-spec wait_fileinfo_begin(state()) -> ok | closed.
wait_fileinfo_begin(State) ->
case next(State) of
{ok, 16#0c, 8, <<>>, State2} -> wait_fileinfo(State2);
closed -> closed
end.
-spec wait_fileinfo(state()) -> ok | closed.
wait_fileinfo(State) ->
case next(State) of
{ok, 16#0d, 20, Data, State2} -> handle_fileinfo(State2, Data);
{ok, 16#0e, 8, <<>>, State2} -> handle_fileinfo_end(State2);
closed -> closed
end.
-spec handle_fileinfo(state(), binary()) -> ok | closed.
handle_fileinfo(State=#state{files=Files}, Data) ->
<< FileNumber:32/little, CRC:32/little, Size:32/little >> = Data,
case egs_patch_files_db:check(FileNumber, CRC, Size) of
ok -> wait_fileinfo(State);
invalid -> wait_fileinfo(State#state{files=[FileNumber|Files]})
end.
-spec handle_fileinfo_end(state()) -> ok.
handle_fileinfo_end(State=#state{files=[]}) ->
handle_update_complete(State);
handle_fileinfo_end(State=#state{files=Files}) ->
Files2 = lists:reverse(Files),
State2 = State#state{files=Files2},
send_0f(State2),
handle_update(State2, root, Files2).
-spec handle_update(state(), root | string(), list(integer())) -> ok.
handle_update(State, _CurrentDir, []) ->
handle_update_complete(State);
handle_update(State=#state{}, CurrentDir, [FileNumber|Tail]) ->
{file, _CRC, Size, Dir, FilenameBin, FullFilename}
= egs_patch_files_db:get_info(FileNumber),
change_directory(State, CurrentDir, Dir),
send_10(State, Size, FilenameBin),
sendfile(State, FullFilename),
send_12(State),
handle_update(State, Dir, Tail).
-spec change_directory(state(), root | string(), root | string()) -> ok.
change_directory(_State, CurrentDir, CurrentDir) ->
ok;
change_directory(State, _CurrentDir, root) ->
send_0a(State);
change_directory(State, root, Dir) ->
send_09(State, Dir).
-spec sendfile(state(), string()) -> ok.
sendfile(State, Filename) ->
{ok, IoDevice} = file:open(Filename, [read, raw, binary]),
sendfile(State, IoDevice, 0).
sendfile(State, IoDevice, N) ->
case file:read(IoDevice, 24576) of
{ok, Data} ->
send_11(State, Data, N),
sendfile(State, IoDevice, N + 1);
eof ->
ok = file:close(IoDevice)
end.
-spec handle_update_complete(state()) -> ok.
handle_update_complete(State=#state{socket=Socket, transport=Transport}) ->
send_13(State),
ok = Transport:close(Socket).
-spec send_01(state()) -> ok.
%% @doc Hello command sent on connect. Encryption is disabled.
send_01(#state{socket=Socket, transport=Transport}) ->
Bin = << 16#28:32/little, 16#01:32/little,
16#8b9f2dfa:32, 0:96, 1:32/little, 0:96 >>,
ok = Transport:send(Socket, Bin).
-spec send_09(state(), string()) -> ok.
%% @doc Change folder command.
send_09(#state{socket=Socket, transport=Transport}, Folder) ->
FolderBin = list_to_binary(Folder),
Padding = 8 * (64 - length(Folder)),
Bin = << 16#48:32/little, 16#09:32/little, FolderBin/binary, 0:Padding >>,
ok = Transport:send(Socket, Bin).
-spec send_0a(state()) -> ok.
%% @doc Back to root folder command.
send_0a(#state{socket=Socket, transport=Transport}) ->
Bin = << 16#8:32/little, 16#0a:32/little >>,
ok = Transport:send(Socket, Bin).
-spec send_0f(state()) -> ok.
%% @doc General update information command. Prepare the update screen.
send_0f(#state{socket=Socket, transport=Transport, files=Files}) ->
Size = lists:foldl(
fun(N, Acc) -> Acc + egs_patch_files_db:get_size(N) end, 0, Files),
NbFiles = length(Files),
Bin = << 16#10:32/little, 16#0f:32/little,
Size:32/little, NbFiles:32/little >>,
ok = Transport:send(Socket, Bin).
-spec send_10(state(), non_neg_integer(), binary()) -> ok.
%% @doc File update begin command. Prepare sending an individual file.
send_10(#state{socket=Socket, transport=Transport}, Size, FilenameBin) ->
Bin = << 16#50:32/little, 16#10:32/little, 0:32,
Size:32/little, FilenameBin/binary >>,
ok = Transport:send(Socket, Bin).
-spec send_11(state(), binary(), non_neg_integer()) -> ok.
%% @doc Command to send a file fragment.
send_11(#state{socket=Socket, transport=Transport}, Data, N) ->
DataSize = byte_size(Data),
Padding = case DataSize rem 4 of
0 -> 0;
Rem -> 8 * (4 - Rem)
end,
Data2 = << Data/binary, 0:Padding >>,
DataSize2 = DataSize + Padding div 8,
Size = DataSize2 + 16#14,
CRC = erlang:crc32(Data2),
Bin = << Size:32/little, 16#11:32/little, N:32/little,
CRC:32/little, DataSize:32/little, Data2/binary >>,
ok = Transport:send(Socket, Bin).
-spec send_12(state()) -> ok.
%% @doc File update end command.
send_12(#state{socket=Socket, transport=Transport}) ->
Bin = << 16#8:32/little, 16#12:32/little >>,
ok = Transport:send(Socket, Bin).
-spec send_13(state()) -> ok.
%% @doc Update complete command. Followed by the server closing the connection.
send_13(#state{socket=Socket, transport=Transport}) ->
Bin = << 16#8:32/little, 16#13:32/little >>,
ok = Transport:send(Socket, Bin).

View File

@ -1,6 +1,4 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Supervisor for the egs_quests gen_server.
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
%%
%% This file is part of EGS.
%%
@ -17,22 +15,18 @@
%% 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_quests_sup).
-module(egs_patch_sup).
-behaviour(supervisor).
-export([start_link/0]). %% API.
-export([init/1]). %% supervisor.
-export([init/1]). %% Supervisor.
-define(SUPERVISOR, ?MODULE).
%% API.
-spec start_link() -> {ok, Pid::pid()}.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
%% supervisor.
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([]) -> {ok, {{one_for_one, 10, 10}, [supervisor:child_spec(), ...]}}.
init([]) ->
Procs = [],
Procs = [{egs_patch_files_db, {egs_patch_files_db, start_link, []},
permanent, 5000, worker, [egs_patch_files_db]}],
{ok, {{one_for_one, 10, 10}, Procs}}.

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

@ -1,7 +1,3 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Callbacks for the egs application.
%%
%% This file is part of EGS.
%%
%% EGS is free software: you can redistribute it and/or modify
@ -17,18 +13,19 @@
%% 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_app).
-module(egs_store_app).
-behaviour(application).
-export([start/2, stop/1]).
-export([start/2, stop/1]). %% API.
-include("include/records.hrl").
-type application_start_type()
:: normal | {takeover, node()} | {failover, node()}.
%% @spec start(_Type, _StartArgs) -> ServerRet
%% @doc application start callback for egs.
%% API.
-spec start(application_start_type(), any()) -> {ok, pid()}.
start(_Type, _StartArgs) ->
egs_sup:start_link().
egs_store_sup:start_link().
%% @spec stop(_State) -> ServerRet
%% @doc application stop callback for egs.
-spec stop(any()) -> ok.
stop(_State) ->
ok.

View File

@ -1,7 +1,3 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2011 Loïc Hoguin.
%% @doc Supervisor for the egs_zones gen_server.
%%
%% This file is part of EGS.
%%
%% EGS is free software: you can redistribute it and/or modify
@ -17,22 +13,19 @@
%% 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_zones_sup).
-module(egs_store_sup).
-behaviour(supervisor).
-export([start_link/0]). %% API.
-export([init/1]). %% supervisor.
-export([init/1]). %% Supervisor.
-define(SUPERVISOR, ?MODULE).
%% API.
-spec start_link() -> {ok, Pid::pid()}.
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?SUPERVISOR}, ?MODULE, []).
%% supervisor.
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([]) -> {ok, {{one_for_one, 10, 10}, [supervisor:child_spec(), ...]}}.
init([]) ->
Procs = [],
{ok, {{one_for_one, 10, 10}, Procs}}.
{ok, {{one_for_one, 10, 10}, [
{egs_store, {egs_store, start_link, []},
permanent, 5000, worker, [egs_store]}
]}}.

View File

@ -1,6 +1,6 @@
/*
@author Loïc Hoguin <essen@dev-extend.eu>
@copyright 2010 Loïc Hoguin.
@copyright 2010-2011 Loïc Hoguin.
@doc PRS Erlang driver for EGS.
This file is part of EGS.
@ -71,4 +71,4 @@ static ErlNifFunc nif_funcs[] = {
{"compress", 1, compress_nif}
};
ERL_NIF_INIT(egs_prs, nif_funcs, load, reload, upgrade, unload)
ERL_NIF_INIT(prs, nif_funcs, load, reload, upgrade, unload)

11
apps/prs/src/prs.app.src Normal file
View File

@ -0,0 +1,11 @@
%%-*- mode: erlang -*-
{application, prs, [
{description, "PRS compression library."},
{vsn, "0.1.0"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]}
]}.

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010-2011 Loïc Hoguin.
%% @doc EGS file creation functions.
%%
%% This file is part of EGS.
@ -17,12 +17,13 @@
%% 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_prs).
-module(prs).
-export([init/0, compress/1]).
-on_load(init/0).
init() ->
erlang:load_nif("priv/egs_drv", 0).
PrivDir = code:priv_dir(prs),
erlang:load_nif(PrivDir ++ "/prs_drv", 0).
compress(_SrcBin) ->
erlang:nif_error(not_loaded).

View File

@ -1 +0,0 @@
*

View File

@ -1 +0,0 @@
*

View File

@ -1 +0,0 @@
*

View File

@ -1 +0,0 @@
*

Binary file not shown.

Binary file not shown.

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 +0,0 @@
*

View File

@ -27,15 +27,15 @@
%% @todo Default enemy_level to 1 if unspecified (lobbies).
%% @todo Default sets to [100] if unspecified (lobbies).
{zones, [
{ 0, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [1, 2, 3, 4, 103]}]},
{ 1, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9001]}]},
{ 2, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9000]}]},
{ 3, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9102]}]},
{ 4, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9010]}]},
{ 7, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9200, 9202]}]},
{11, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [5, 100, 101, 102, 110]}]},
{12, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [100, 101, 102]}]},
{13, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [100, 101, 102]}]}
{ 0, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [1, 2, 3, 4, 5, 103]}]}
%% { 1, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9001]}]},
%% { 2, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9000]}]},
%% { 3, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9102]}]},
%% { 4, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9010]}]},
%% { 7, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [9200, 9202]}]},
%% {11, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [100, 101, 102, 110]}]},
%% {12, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [100, 101, 102]}]},
%% {13, [{areaid, 2}, {enemy_level, 1}, {sets, [100]}, {maps, [100, 101, 102]}]}
]}.
{temp_flags, []}.
@ -101,13 +101,12 @@
%% @todo Exit 6.
%% 4rd floor.
{{1100000, 0, 4, 0}, {1100000, 0, 3, 1}},
{{1100000, 0, 4, 1}, {1100000, 11, 5, 0}},
{{1100000, 0, 4, 1}, {1100000, 0, 5, 0}},
{{1100000, 0, 4, 2}, {1104000, 0, 900, 0}},
{{1100000, 0, 4, 3}, {1104000, 0, 900, 0}},
{{1100000, 0, 4, 4}, {1104000, 0, 900, 0}},
%% 5th floor.
%% @todo Add 5th floor to zone 0.
{{1100000, 11, 5, 0}, {1100000, 0, 4, 1}},
{{1100000, 0, 5, 0}, {1100000, 0, 4, 1}},
%% 2nd floor shops.
{{1100000, 11, 101, 0}, {1100000, 0, 2, 3}},
{{1100000, 12, 101, 1}, {1100000, 0, 2, 4}},

View File

@ -83,6 +83,10 @@ event entr_unit0004 ->
push 0, npc.talk_on, %% Linear Line NPC.
push 1, npc.talk_on. %% Space Docks NPC.
%% @doc Enter map 5 event. Initialize the labels.
event entr_unit0005 ->
push 6, push 0, obj.set_caption. %% Elevators.
%% @doc Enter map 103 event. Initialize the label.
event entr_unit0103 ->
push 3, push 0, obj.set_caption. %% Exits to 3rd floor.
@ -118,7 +122,7 @@ function coli_unit0001_elevator ->
push 4, %% selected option: 1st floor
mes.select_win_b,
case
0 -> push 11, push 5, push 0, player.change_unit;
0 -> push 0, push 5, num_get nElevatorEntry, player.change_unit;
1 -> push 0, push 4, num_get nElevatorEntry, player.change_unit;
2 -> push 0, push 3, num_get nElevatorEntry, player.change_unit;
3 -> push 0, push 2, num_get nElevatorEntry, player.change_unit
@ -154,7 +158,7 @@ function coli_unit0002_elevator ->
push 3, %% selected option: 2nd floor
mes.select_win_b,
case
0 -> push 11, push 5, push 0, player.change_unit;
0 -> push 0, push 5, num_get nElevatorEntry, player.change_unit;
1 -> push 0, push 4, num_get nElevatorEntry, player.change_unit;
2 -> push 0, push 3, num_get nElevatorEntry, player.change_unit;
4 -> push 0, push 1, num_get nElevatorEntry, player.change_unit
@ -204,7 +208,7 @@ function coli_unit0003_elevator ->
push 2, %% selected option: 3rd floor
mes.select_win_b,
case
0 -> push 11, push 5, push 0, player.change_unit;
0 -> push 0, push 5, num_get nElevatorEntry, player.change_unit;
1 -> push 0, push 4, num_get nElevatorEntry, player.change_unit;
3 -> push 0, push 2, num_get nElevatorEntry, player.change_unit;
4 -> push 0, push 1, num_get nElevatorEntry, player.change_unit
@ -254,7 +258,7 @@ function coli_unit0004_elevator ->
push 1, %% selected option: 4th floor
mes.select_win_b,
case
0 -> push 11, push 5, push 0, player.change_unit;
0 -> push 0, push 5, num_get nElevatorEntry, player.change_unit;
2 -> push 0, push 3, num_get nElevatorEntry, player.change_unit;
3 -> push 0, push 2, num_get nElevatorEntry, player.change_unit;
4 -> push 0, push 1, num_get nElevatorEntry, player.change_unit
@ -289,6 +293,42 @@ event coli_unit0004_obje023 ->
num_get nElevatorEntry,
obj.coli_end.
%% Map 5.
function coli_unit0005_elevator ->
player.pad_off,
push 39, %% return
push 29, %% 1st floor
push 31, %% 2nd floor
push 33, %% 3rd floor
push 35, %% 4th floor
push 38, %% 5th floor
push 27, %% stringid question
push 6, %% number of options
push 0, %% selected option: 5th floor
mes.select_win_b,
case
1 -> push 0, push 4, num_get nElevatorEntry, player.change_unit;
2 -> push 0, push 3, num_get nElevatorEntry, player.change_unit;
3 -> push 0, push 2, num_get nElevatorEntry, player.change_unit;
4 -> push 0, push 1, num_get nElevatorEntry, player.change_unit
end,
player.pad_on.
event coli_unit0005_obje022 ->
push 22,
num_set nElevatorEntry,
coli_unit0005_elevator,
num_get nElevatorEntry,
obj.coli_end.
event coli_unit0005_obje023 ->
push 23,
num_set nElevatorEntry,
coli_unit0005_elevator,
num_get nElevatorEntry,
obj.coli_end.
%% NPCs.
num_var nTransportNPC.

View File

@ -1881,6 +1881,53 @@
]
]}.
{{map, 5}, [
[ %% Always available.
{chair, { 90.0, 16.0, 197.0}, {0.0, 187.0, 0.0}, [{id, 1}]},
{chair, { 104.0, 16.0, 194.0}, {0.0, 200.0, 0.0}, [{id, 2}]},
{chair, { 116.0, 16.0, 187.0}, {0.0, 220.0, 0.0}, [{id, 3}]},
{chair, { 127.0, 16.0, 177.0}, {0.0, 235.0, 0.0}, [{id, 4}]},
{chair, { 134.0, 16.0, 164.0}, {0.0, 250.0, 0.0}, [{id, 5}]},
{chair, { 136.0, 16.0, 150.0}, {0.0, 260.0, 0.0}, [{id, 6}]},
{chair, { -90.0, 16.0, 197.0}, {0.0, 173.0, 0.0}, [{id, 7}]},
{chair, {-104.0, 16.0, 194.0}, {0.0, 160.0, 0.0}, [{id, 8}]},
{chair, {-116.0, 16.0, 187.0}, {0.0, 140.0, 0.0}, [{id, 9}]},
{chair, {-127.0, 16.0, 177.0}, {0.0, 125.0, 0.0}, [{id, 10}]},
{chair, {-134.0, 16.0, 164.0}, {0.0, 110.0, 0.0}, [{id, 11}]},
{chair, {-136.0, 16.0, 150.0}, {0.0, 100.0, 0.0}, [{id, 12}]},
{door, { 0.0, 0.0, 640.0}, {0.0, 0.0, 0.0}, [{model, 61}]},
{door, { 25.0, 30.0, -290.0}, {0.0, 0.0, 0.0}, [{model, 66}]},
{door, {-25.0, 30.0, -290.0}, {0.0, 0.0, 0.0}, [{model, 66}]},
{exit, {0.0, 0.0, 620.0}, {0.0, 0.0, 0.0}, [
{entryid, 0}, {type, map}, {animation, run},
{exit_box, {0.0, 70.0, 20.0, 20.0}}, {exit_movement, {0.0, 0.0, 45.0}},
{camera_box, {0.0, 70.0, 19.0, 90.0}}, {camera_movement, {0.0, 30.0, -100.0}}
]},
{entrance, { 0.0, 0.0, 550.0}, {0.0, 180.0, 0.0}, [{entryid, 0}]},
{entrance, { 25.0, 30.0, -260.0}, {0.0, 0.0, 0.0}, [{entryid, 23}]},
{entrance, {-25.0, 30.0, -260.0}, {0.0, 0.0, 0.0}, [{entryid, 22}]},
{label, { 25.0, 51.0, -290.0}, {0.0, 0.0, 0.0}, [{id, 0}, {box, {20.0, 60.0, 30.0, 180.0}}]},
{label, {-25.0, 51.0, -290.0}, {0.0, 0.0, 0.0}, [{id, 0}, {box, {20.0, 60.0, 30.0, 180.0}}]},
{npc, { 0.0, 11.0, 85.0}, {0.0, 0.0, 0.0}, [{model, 80}, {id, 0}, {talk_radius, 30.0}]},
{npc, { 25.0, 11.0, 60.0}, {0.0, 90.0, 0.0}, [{model, 81}, {id, 1}]},
{npc, {-25.0, 11.0, 60.0}, {0.0, -90.0, 0.0}, [{model, 81}, {id, 2}]},
{npc, { 0.0, 11.0, 35.0}, {0.0, 180.0, 0.0}, [{model, 81}, {id, 3}]},
{npc, {-70.0, 0.0, 305.0}, {0.0, 90.0, 0.0}, [{model, 81}, {id, 4}]},
{pp_cube, {60.0, 0.0, 285.0}, {0.0, 0.0, 0.0}, []},
{sensor, { 25.0, 31.0, -290.0}, {0.0, 0.0, 0.0}, [{id, 23}, {box, {30.0, 40.0, 40.0, 40.0}}]},
{sensor, {-25.0, 31.0, -290.0}, {0.0, 0.0, 0.0}, [{id, 22}, {box, {30.0, 40.0, 40.0, 40.0}}]},
{uni_cube, {-30.0, 0.0, 485.0}, {0.0, 0.0, 0.0}, [{i, 0}, {entryid, 0}]}
]
]}.
{{map, 103}, [
[ %% Always available.
{chair, {-599.0, 186.0, -1049.0}, {0.0, 180.0, 0.0}, [{id, 1}, {stand_dist, 8.0}]},

BIN
rebar vendored

Binary file not shown.

View File

@ -1,4 +1,19 @@
%%-*- mode: erlang -*-
{deps, [
{ex_reloader, ".*", {git, "git://github.com/extend/ex_reloader.git", "HEAD"}}
{sub_dirs, [
"apps/egs",
"apps/egs_net",
"apps/egs_patch",
"apps/egs_store",
"apps/prs"
]}.
{dialyzer_opts, [src, {warnings, [
behaviours,
error_handling,
race_conditions,
unmatched_returns
%% underspecs
]}]}.
{erl_opts, [
%% bin_opt_info,
%% warnings_as_errors,
warn_export_all
]}.

View File

@ -1,66 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc EGS startup code.
%%
%% 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).
-compile(export_all).
-include("include/records.hrl").
%% @spec ensure_started(App) -> ok
%% @doc Make sure the given App is started.
ensure_started(App) ->
case application:start(App) of
ok -> ok;
{error, {already_started, App}} -> ok
end.
%% @spec start() -> ok
%% @doc Start the EGS server.
start() ->
ensure_started(crypto),
ensure_started(public_key),
ensure_started(ssl),
ssl:seed(crypto:rand_bytes(256)),
application:start(egs).
%% @spec stop() -> ok
%% @doc Stop the EGS server.
stop() ->
Res = application:stop(egs),
application:stop(ssl),
application:stop(public_key),
application:stop(crypto),
Res.
%% @doc Send a global message.
global(Message) ->
if length(Message) > 511 ->
io:format("global: message too long~n");
true ->
egs_users:broadcast_all({egs, notice, top, Message})
end.
%% @doc Warp all players to a new map.
warp(QuestID, ZoneID, MapID, EntryID) ->
egs_users:broadcast_all({egs, warp, QuestID, ZoneID, MapID, EntryID}).
%% @doc Warp one player to a new map.
warp(GID, QuestID, ZoneID, MapID, EntryID) ->
{ok, User} = egs_users:read(GID),
User#users.pid ! {egs, warp, QuestID, ZoneID, MapID, EntryID}.

View File

@ -1,107 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Character selection callback module.
%%
%% 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_char_select).
-export([keepalive/1, info/2, cast/3, raw/3, event/2]).
-include("include/records.hrl").
%% @doc Send a keepalive.
keepalive(#state{socket=Socket}) ->
psu_proto:send_keepalive(Socket).
%% @doc We don't expect any message here.
info(_Msg, _State) ->
ok.
%% @doc Nothing to broadcast.
cast(_Command, _Data, _State) ->
ok.
%% @doc Dismiss all raw commands with a log notice.
%% @todo Have a log event handler instead.
raw(Command, _Data, State) ->
io:format("~p (~p): dismissed command ~4.16.0b~n", [?MODULE, State#state.gid, Command]).
%% Events.
%% @doc Character screen selection request and delivery.
event(char_select_request, #state{gid=GID}) ->
Folder = egs_accounts:get_folder(GID),
psu_game:send_0d03(data_load(Folder, 0), data_load(Folder, 1), data_load(Folder, 2), data_load(Folder, 3));
%% @doc The options default to 0 for everything except brightness to 4.
%% @todo Don't forget to check for the character's name.
event({char_select_create, Slot, CharBin}, #state{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_characters:validate_name(Name),
%~ 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 >>);
%% @doc Load the selected character into the game's default universe.
event({char_select_enter, Slot, _BackToPreviousField}, State=#state{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),
Character = #characters{slot=Slot, name=Name, race=Race, gender=Gender, class=Class, appearance=Appearance, options=Options}, % TODO: temporary set the slot here, won't be needed later
UniID = egs_universes:defaultid(),
egs_universes:enter(UniID),
User = #users{gid=GID, pid=self(), uni=UniID, character=Character, area={1100000, 0, 4}, entryid=0},
egs_users:write(User),
egs_game_server:link_exit(),
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}}),
{ok, User2} = egs_users:read(GID),
State2 = State#state{slot=Slot},
psu_game:char_load(User2, State2),
{ok, egs_game, State2}.
%% 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.

View File

@ -1,50 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc General purpose module for monitoring exit signals of linked processes.
%%
%% 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_exit_mon).
-export([start_link/1]). %% External.
-export([start/1, loop/1]). %% Internal.
%% @spec start_link(CleanupFn) -> {ok,Pid::pid()}
%% @doc Start the monitor and return the process' Pid.
start_link(CallbackFn) ->
Pid = spawn(?MODULE, start, [CallbackFn]),
{ok, Pid}.
%% @spec start(CallbackFn) -> ok
%% @doc Start the main loop.
start(CallbackFn) ->
error_logger:info_report(io_lib:format("egs_exit_mon started with callback ~p", [CallbackFn])),
process_flag(trap_exit, true),
?MODULE:loop(CallbackFn).
%% @spec loop(CallbackFn) -> ok
%% @doc Main loop, trap exit messages and call the callback function.
loop(CallbackFn = {Module, Function}) ->
receive
{'EXIT', Pid, _} ->
spawn(Module, Function, [Pid]);
{link, Pid} ->
link(Pid);
_ ->
reload
after 5000 ->
reload
end,
?MODULE:loop(CallbackFn).

View File

@ -1,62 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Game server module.
%%
%% 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_game_server).
-export([start_link/1, link_exit/0, on_exit/1, init/1]).
-include("include/records.hrl").
%% @spec start_link(Port) -> {ok,Pid::pid()}
%% @doc Start the game server.
start_link(Port) ->
{ok, MPid} = egs_exit_mon:start_link({?MODULE, on_exit}),
register(egs_game_server_exit_mon, MPid),
LPid = spawn(egs_network, listen, [Port, ?MODULE]),
{ok, LPid}.
%% @doc Link the on_exit handler to the current process.
link_exit() ->
egs_game_server_exit_mon ! {link, self()}.
%% @spec on_exit(Pid) -> ok
%% @doc Cleanup the data associated with the failing process.
%% @todo Cleanup the instance process if there's nobody in it anymore.
%% @todo Leave party instead of stopping it.
on_exit(Pid) ->
User = egs_users:find_by_pid(Pid),
case User#users.partypid of
undefined ->
ignore;
PartyPid ->
{ok, NPCList} = psu_party:get_npc(PartyPid),
[egs_users:delete(NPCGID) || {_Spot, NPCGID} <- NPCList],
psu_party:stop(PartyPid)
end,
egs_zones:leave(User#users.zonepid, User#users.gid),
egs_universes:leave(User#users.uni),
egs_users:delete(User#users.gid),
io:format("game (~p): quit~n", [User#users.gid]).
%% @doc Initialize the game state and start receiving messages.
%% @todo Handle keepalive messages globally?
init(Socket) ->
timer:send_interval(5000, {egs, keepalive}),
State = #state{socket=Socket, gid=egs_accounts:tmp_gid()},
psu_proto:send_0202(State),
egs_network:recv(<< >>, egs_login, State).

View File

@ -1,107 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 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([listen/2, recv/3]). %% API.
-export([accept/2]). %% Internal.
-define(OPTIONS, [binary, {active, true}, {reuseaddr, true}, {ssl_imp, old}, {certfile, "priv/ssl/servercert.pem"}, {keyfile, "priv/ssl/serverkey.pem"}, {password, "alpha"}]).
%% @doc Listen for connections.
listen(Port, CallbackMod) ->
error_logger:info_report(io_lib:format("listener started for ~p on port ~b", [CallbackMod, Port])),
{ok, LSocket} = ssl:listen(Port, ?OPTIONS),
?MODULE:accept(LSocket, CallbackMod).
%% @doc Accept connections.
accept(LSocket, CallbackMod) ->
case ssl:transport_accept(LSocket, 5000) of
{ok, CSocket} ->
case ssl:ssl_accept(CSocket, 5000) of
ok ->
Pid = spawn(CallbackMod, init, [CSocket]),
ssl:controlling_process(CSocket, Pid);
{error, _Reason} ->
ignore
end;
{error, _Reason} ->
ignore
end,
?MODULE:accept(LSocket, CallbackMod).
%% @doc Main loop for the network stack. Receive and handle messages.
recv(SoFar, CallbackMod, State) ->
receive
{ssl, _Any, Data} ->
{Commands, Rest} = split(<< SoFar/bits, Data/bits >>, []),
case dispatch(Commands, CallbackMod, CallbackMod, State) of
{ok, NextCallbackMod, NewState} ->
?MODULE:recv(Rest, NextCallbackMod, NewState);
closed -> closed
end;
{ssl_closed, _} ->
ssl_closed; %% exit
{ssl_error, _, _} ->
ssl_error; %% exit
{egs, keepalive} ->
CallbackMod:keepalive(State),
?MODULE:recv(SoFar, CallbackMod, State);
Tuple when element(1, Tuple) =:= egs ->
case CallbackMod:info(Tuple, State) of
{ok, NewState} -> ?MODULE:recv(SoFar, CallbackMod, NewState);
_Any -> ?MODULE:recv(SoFar, CallbackMod, State)
end;
_ ->
?MODULE:recv(SoFar, CallbackMod, State)
end.
%% @doc Dispatch the commands received to the right handler.
dispatch([], _CallbackMod, NextMod, State) ->
{ok, NextMod, State};
dispatch([Data|Tail], CallbackMod, NextMod, State) ->
Ret = case psu_proto:parse(Data) of
{command, Command, Channel} ->
case Channel of
1 -> CallbackMod:cast(Command, Data, State);
_ -> CallbackMod:raw(Command, Data, State)
end;
ignore ->
ignore;
Event ->
CallbackMod:event(Event, State)
end,
case Ret of
{ok, NewMod, NewState} ->
dispatch(Tail, CallbackMod, NewMod, NewState);
{ok, NewState} ->
dispatch(Tail, CallbackMod, NextMod, NewState);
closed ->
closed;
_Any ->
dispatch(Tail, CallbackMod, NextMod, State)
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]).

View File

@ -1,234 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Patch server module.
%%
%% 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_patch_server).
-export([start_link/1]). %% External.
-export([listen/1, accept/1, init/1, recv/3]). %% Internal
-define(OPTIONS, [binary, {active, true}, {packet, 0}, {reuseaddr, true}, {send_timeout, 5000}]).
-record(state, {step=start, files=[]}).
%% @spec start_link(Port) -> {ok,Pid::pid()}
%% @doc Start the PSU patch server for inclusion in a supervisor tree.
start_link(Port) ->
Pid = spawn(?MODULE, listen, [Port]),
{ok, Pid}.
%% @spec listen(Port) -> ok
%% @doc Listen for connections.
listen(Port) ->
error_logger:info_report(io_lib:format("listener started for ~p on port ~b", [?MODULE, Port])),
{ok, LSocket} = gen_tcp:listen(Port, ?OPTIONS),
?MODULE:accept(LSocket).
%% @spec accept(LSocket) -> ok
%% @doc Accept connections.
accept(LSocket) ->
case gen_tcp:accept(LSocket, 5000) of
{ok, CSocket} ->
Pid = spawn(?MODULE, init, [CSocket]),
gen_tcp:controlling_process(CSocket, Pid);
{error, timeout} ->
reload
end,
?MODULE:accept(LSocket).
%% @spec init(CSocket) -> ok
%% @doc Send the hello packet and move on to the main loop.
init(CSocket) ->
send_01(CSocket),
recv(CSocket, << >>, #state{}).
%% @spec recv(CSocket, SoFar, State) -> ok
%% @doc Receive commands from the client and process them.
recv(CSocket, SoFar, State) ->
receive
{tcp, CSocket, Data} ->
{Commands, Rest} = split(<< SoFar/bits, Data/bits >>, []),
case handle(CSocket, Commands, State) of
closed -> closed;
State2 -> ?MODULE:recv(CSocket, Rest, State2)
end;
{tcp_closed, CSocket} ->
tcp_closed;
{tcp_error, CSocket, _Any} ->
tcp_error;
_ ->
?MODULE:recv(CSocket, SoFar, State)
end.
%% @spec split(Bin, Acc) -> {Commands, Rest}
%% @doc Split the given binary into a list of commands.
split(<< >>, Acc) ->
{lists:reverse(Acc), << >>};
split(Rest = << Size:32/little, Data/bits >>, Acc) when Data + 4 < Size ->
{lists:reverse(Acc), Rest};
split(<< Size:32/little, Cmd:16/little, _Junk:16, Rest/bits >>, Acc) ->
BitSize = 8 * Size - 64,
<< Data:BitSize/bits, Rest2/bits >> = Rest,
split(Rest2, [{command, Cmd, Size, Data}|Acc]).
%% @spec handle(CSocket, CommandsList) -> closed | State
%% @doc Handle the given commands.
handle(_CSocket, [], State) ->
State;
%% Start of file info reply.
handle(CSocket, [{command, 16#0c, 8, << >>}|Tail], State=#state{step=waitfileinfo}) ->
handle(CSocket, Tail, State#state{step=recvfileinfo});
%% File info.
handle(CSocket, [{command, 16#0d, 20, << FileNumber:32/little, CRC:32/little, Size:32/little >>}|Tail], State=#state{step=recvfileinfo}) ->
State2 = case egs_patch_files_db:check(FileNumber, CRC, Size) of
ok -> State;
invalid -> State#state{files=[FileNumber|State#state.files]}
end,
handle(CSocket, Tail, State2);
%% End of file info reply. Nothing expected from the client afterward.
handle(CSocket, [{command, 16#0e, 8, << >>}], #state{step=recvfileinfo, files=Files}) ->
case Files of
[] -> ok; %% No files to update.
_List -> update(CSocket, lists:reverse(Files))
end,
send_13(CSocket),
closed;
%% Hello reply.
%% @todo Figure out the remaining unknown values.
handle(CSocket, [{command, 16#14, 52, << 16#e44c0915:32, UnknownA:32/little, UnknownB:32/little, UnknownC:32/little,
UnknownD:32/little, _GameVersion:32/little, UnknownE:32/little, 0:128 >>}|Tail], State=#state{step=start}) ->
io:format("patch #14: ~p ~p ~p ~p ~p~n", [UnknownA, UnknownB, UnknownC, UnknownD, UnknownE]),
ListBin = egs_patch_files_db:list(),
gen_tcp:send(CSocket, ListBin),
handle(CSocket, Tail, State#state{step=waitfileinfo});
%% Unknown command.
handle(_CSocket, [{command, Cmd, _Size, Data}|_Tail], State) ->
io:format("~p: dismissed command ~2.16.0b - ~p - ~p~n", [?MODULE, Cmd, Data, State]),
closed.
%% @spec update(CSocket, Files) -> ok
%% @doc Update the invalid client files.
update(CSocket, Files) ->
Size = update_size(Files),
send_0f(CSocket, Size, length(Files)),
update_files(CSocket, root, Files).
%% @spec update_files(CSocket, Files) -> ok
%% @doc Send all the files the client needs to update.
update_files(_CSocket, _CurrentFolder, []) ->
ok;
update_files(CSocket, CurrentFolder, [FileNumber|Tail]) ->
{file, _CRC, Size, Folder, FilenameBin, FullFilename} = egs_patch_files_db:get_info(FileNumber),
case CurrentFolder of
Folder -> ok;
_Any ->
if CurrentFolder =/= root ->
send_0a(CSocket);
true -> ok
end,
if Folder =/= root ->
send_09(CSocket, Folder);
true -> ok
end
end,
send_10(CSocket, Size, FilenameBin),
update_send_file(CSocket, FullFilename),
send_12(CSocket),
update_files(CSocket, Folder, Tail).
%% @spec update_send_file(CSocket, Filename) -> ok
%% @doc Send a file by fragmenting it into many chunks of fixed size.
update_send_file(CSocket, Filename) ->
{ok, IoDevice} = file:open(Filename, [read, raw, binary]),
update_send_file(CSocket, IoDevice, 0).
update_send_file(CSocket, IoDevice, N) ->
case file:read(IoDevice, 24576) of
{ok, Data} ->
send_11(CSocket, Data, N),
update_send_file(CSocket, IoDevice, N + 1);
eof ->
file:close(IoDevice)
end.
%% @spec update_size(Files) -> Size
%% @doc Return the total size for all the files given.
update_size(Files) ->
update_size(Files, 0).
update_size([], Size) ->
Size;
update_size([FileNumber|Tail], Size) ->
FileSize = egs_patch_files_db:get_size(FileNumber),
update_size(Tail, Size + FileSize).
%% @spec send_01(CSocket) -> ok
%% @doc Hello command sent when a client connects to the server. Encryption is disabled.
send_01(CSocket) ->
Bin = << 16#28:32/little, 16#01:32/little, 16#8b9f2dfa:32, 0:96, 1:32/little, 0:96 >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_09(CSocket, Folder) -> ok
%% @doc Change folder command.
send_09(CSocket, Folder) ->
FolderBin = list_to_binary(Folder),
Padding = 8 * (64 - length(Folder)),
Bin = << 16#48:32/little, 16#09:32/little, FolderBin/binary, 0:Padding >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_0a(CSocket) -> ok
%% @doc Back to root folder command.
send_0a(CSocket) ->
Bin = << 16#8:32/little, 16#0a:32/little >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_0f(CSocket, TotalSize, NbFiles) -> ok
%% @doc General update information command. Prepare the update screen.
send_0f(CSocket, Size, NbFiles) ->
Bin = << 16#10:32/little, 16#0f:32/little, Size:32/little, NbFiles:32/little >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_10(CSocket, Size, FilenameBin) -> ok
%% @doc File update begin command. Prepare sending an individual file.
send_10(CSocket, Size, FilenameBin) ->
Bin = << 16#50:32/little, 16#10:32/little, 0:32, Size:32/little, FilenameBin/binary >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_11(CSocket, Data, N) -> ok
%% @doc Command to send a file fragment.
send_11(CSocket, Data, N) ->
DataSize = byte_size(Data),
Padding = case DataSize rem 4 of
0 -> 0;
Rem -> 8 * (4 - Rem)
end,
Data2 = << Data/binary, 0:Padding >>,
DataSize2 = DataSize + Padding div 8,
Size = DataSize2 + 16#14,
CRC = erlang:crc32(Data2),
Bin = << Size:32/little, 16#11:32/little, N:32/little, CRC:32/little, DataSize:32/little, Data2/binary >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_12(CSocket) -> ok
%% @doc File update end command.
send_12(CSocket) ->
Bin = << 16#8:32/little, 16#12:32/little >>,
gen_tcp:send(CSocket, Bin).
%% @spec send_13(CSocket) -> ok
%% @doc Update complete command. Usually followed by the server closing the connection.
send_13(CSocket) ->
Bin = << 16#8:32/little, 16#13:32/little >>,
gen_tcp:send(CSocket, Bin).

View File

@ -1,49 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Supervisor for the egs application.
%%
%% 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_sup).
-behaviour(supervisor).
-export([init/1]). %% Supervisor callbacks.
-export([start_link/0]). %% Other functions.
%% @spec start_link() -> ServerRet
%% @doc API for starting the supervisor.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% @spec init([]) -> SupervisorTree
%% @doc supervisor callback.
init([]) ->
Procs = [
{egs_conf, {egs_conf, start_link, []}, permanent, 5000, worker, dynamic},
{egs_servers_sup, {egs_servers_sup, start_link, []}, permanent, 5000, supervisor, [egs_servers_sup]},
{egs_quests_sup, {egs_quests_sup, start_link, []}, permanent, 5000, supervisor, [egs_quests_sup]},
{egs_zones_sup, {egs_zones_sup, start_link, []}, permanent, 5000, supervisor, [egs_zones_sup]},
{egs_accounts, {egs_accounts, start_link, []}, permanent, 5000, worker, dynamic},
{egs_users, {egs_users, start_link, []}, permanent, 5000, worker, dynamic},
{egs_seasons, {egs_seasons, start_link, []}, permanent, 5000, worker, dynamic},
{egs_counters_db, {egs_counters_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_items_db, {egs_items_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_npc_db, {egs_npc_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_patch_files_db, {egs_patch_files_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_quests_db, {egs_quests_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_shops_db, {egs_shops_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_universes, {egs_universes, start_link, []}, permanent, 5000, worker, dynamic}
],
{ok, {{one_for_one, 10, 10}, Procs}}.

View File

@ -1,254 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 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

@ -1,190 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc General character 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_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, se_list_to_binary/1, stats_tuple_to_binary/1, validate_name/1
]).
-include("include/records.hrl").
%% @doc Convert a character tuple into a binary to be sent to clients.
%% Only contains the actually saved data, not the stats and related information.
character_tuple_to_binary(Tuple) ->
#characters{name=Name, race=Race, gender=Gender, class=Class, appearance=Appearance} = 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),
LevelsBin = psu_proto:build_char_level(Tuple),
<< Name/binary, RaceBin:8, GenderBin:8, ClassBin:8, AppearanceBin/binary, LevelsBin/binary >>.
%% @doc Convert a character tuple into a binary to be sent to clients.
%% Contains everything from character_tuple_to_binary/1 along with location, stats, SE and more.
%% @todo The second StatsBin seems unused. Not sure what it's for.
%% @todo Find out what the big block of 0 is at the end.
%% @todo The value before IntDir seems to be the player's current animation. 01 stand up, 08 ?, 17 normal sit
character_user_to_binary(User) ->
#users{gid=CharGID, lid=CharLID, character=Character, pos={X, Y, Z, Dir}, area={QuestID, ZoneID, MapID}, entryid=EntryID,
prev_area={PrevQuestID, PrevZoneID, PrevMapID}, prev_entryid=PrevEntryID} = User,
#characters{npcid=NPCid, type=Type, mainlevel=Level, stats=Stats, se=SE, currenthp=CurrentHP, maxhp=MaxHP} = Character,
#level{number=LV} = Level,
CharBin = psu_characters:character_tuple_to_binary(Character),
StatsBin = psu_characters:stats_tuple_to_binary(Stats),
SEBin = psu_characters:se_list_to_binary(SE),
EXPNextLevel = 100,
EXPCurrentLevel = 0,
IntDir = trunc(Dir * 182.0416),
TypeID = case Type of npc -> 16#00001d00; _ -> 16#00001200 end,
NPCStuff = case Type of npc -> 16#01ff; _ -> 16#0000 end,
<< TypeID:32, CharGID:32/little, 0:64, CharLID:16/little, 0:16, NPCStuff:16, NPCid:16/little, QuestID:32/little,
ZoneID:32/little, MapID:32/little, EntryID:16/little, 0:16,
16#0100:16, IntDir:16/little, X:32/little-float, Y:32/little-float, Z:32/little-float, 0:64,
PrevQuestID:32/little, PrevZoneID:32/little, PrevMapID:32/little, PrevEntryID:32/little,
CharBin/binary, EXPNextLevel:32/little, EXPCurrentLevel:32/little, MaxHP:32/little,
StatsBin/binary, 0:32, SEBin/binary, 0:32, LV:32/little, StatsBin/binary, CurrentHP:32/little, MaxHP:32/little,
0:1344, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:64, 16#0000803f:32, 0:160, 16#0000803f:32, 0:352 >>.
%% @doc Convert a class atom into a binary to be sent to clients.
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
end.
%% @doc Convert a gender atom into a binary to be sent to clients.
gender_atom_to_binary(Gender) ->
case Gender of
male -> 0;
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) ->
case Race of
human -> 0;
newman -> 1;
cast -> 2;
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 a list of status effects into a binary to be sent to clients.
%% @todo Do it for real.
se_list_to_binary(_List) ->
<< 0:32 >>.
%% @doc Convert the tuple of stats data into a binary to be sent to clients.
stats_tuple_to_binary(Tuple) ->
{stats, ATP, ATA, TP, DFP, EVP, MST, STA} = Tuple,
<< ATP:16/little, DFP:16/little, ATA:16/little, EVP:16/little,
STA:16/little, 0:16, TP:16/little, MST:16/little >>.
%% @doc Validate the character's name.
%% 00F7 is the RGBA color control character.
%% 03F7 is the RGB color control character.
%% Trigger an exception rather than handling errors.
validate_name(_Name) ->
%~ Something like that probably: << true = X =/= 16#00F7 andalso X =/= 16#03F7 || X:16 <- Name>>.
ok.

View File

@ -1,473 +0,0 @@
%% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin.
%% @doc Handle game clients.
%%
%% 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_game).
-compile(export_all). %% @todo Temporarily export all until send_xxxx functions are moved to psu_proto.
-include("include/records.hrl").
%% @doc Load and send the character information to the client.
%% @todo Move this whole function directly to psu_proto, probably.
char_load(User, State) ->
psu_proto:send_0d01(User#users.character, State),
%% 0246
send_0a0a((User#users.character)#characters.inventory),
psu_proto:send_1006(5, 0, State), %% @todo The 0 here is PartyPos, save it in User.
psu_proto:send_1005(User#users.character, State),
psu_proto:send_1006(12, State),
psu_proto:send_0210(State),
psu_proto:send_0222(User#users.uni, State),
psu_proto:send_1500(User#users.character, State),
send_1501(),
send_1512(),
%% 0303
send_1602(),
psu_proto:send_021b(State).
%% @doc Load the given map as a standard lobby.
area_load(QuestID, ZoneID, MapID, EntryID, State) ->
{ok, OldUser} = egs_users:read(State#state.gid),
{OldQuestID, OldZoneID, _OldMapID} = OldUser#users.area,
QuestChange = OldQuestID /= QuestID,
ZoneChange = if OldQuestID =:= QuestID, OldZoneID =:= ZoneID -> false; true -> true end,
AreaType = egs_quests_db:area_type(QuestID, ZoneID),
AreaShortName = "dammy", %% @todo Load the short name from egs_quests_db.
{IsSeasonal, SeasonID} = egs_seasons:read(QuestID),
User = OldUser#users{areatype=AreaType, area={QuestID, ZoneID, MapID}, entryid=EntryID},
egs_users:write(User), %% @todo Booh ugly! But temporary.
%% Load the quest.
User2 = if QuestChange ->
psu_proto:send_0c00(User, State),
psu_proto:send_020e(egs_quests_db:quest_nbl(QuestID), State),
User#users{questpid=egs_universes:lobby_pid(User#users.uni, QuestID)};
true -> User
end,
%% Load the zone.
State1 = if ZoneChange ->
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),
NewState = State#state{lid=NewLID},
{ok, User3} = egs_users:read(User2#users.gid),
psu_proto:send_0a05(NewState),
psu_proto:send_0111(User3, 6, NewState),
psu_proto:send_010d(User3, NewState),
psu_proto:send_0200(ZoneID, AreaType, NewState),
psu_proto:send_020f(egs_quests_db:zone_nbl(QuestID, ZoneID), egs_zones:setid(ZonePid), SeasonID, NewState),
NewState;
true ->
User3 = User2,
State
end,
%% Save the user.
egs_users:write(User3),
%% Load the player location.
State2 = State1#state{areanb=State#state.areanb + 1},
psu_proto:send_0205(User3, IsSeasonal, State2),
psu_proto:send_100e(User3#users.area, User3#users.entryid, AreaShortName, State2),
%% Load the zone objects.
if ZoneChange ->
send_1212(); %% @todo Only sent if there is a set file.
true -> ignore
end,
%% Load the player.
psu_proto:send_0201(User3, State2),
if ZoneChange ->
psu_proto:send_0a06(User3, State2),
%% Load the other players in the zone.
OtherPlayersGID = egs_zones:get_all_players(User3#users.zonepid, User3#users.gid),
if OtherPlayersGID =:= [] -> ignore;
true ->
OtherPlayers = egs_users:select(OtherPlayersGID),
psu_proto:send_0233(OtherPlayers, State)
end;
true -> ignore
end,
%% End of loading.
State3 = State2#state{areanb=State2#state.areanb + 1},
psu_proto:send_0208(State3),
psu_proto:send_0236(State3),
%% @todo Load APC characters.
{ok, State3}.
%% @todo Don't change the NPC info unless you are the leader!
npc_load(_Leader, [], _State) ->
ok;
npc_load(Leader, [{PartyPos, NPCGID}|NPCList], State) ->
{ok, OldNPCUser} = egs_users:read(NPCGID),
#users{instancepid=InstancePid, area=Area, entryid=EntryID, pos=Pos} = Leader,
NPCUser = OldNPCUser#users{lid=PartyPos, instancepid=InstancePid, areatype=mission, area=Area, entryid=EntryID, pos=Pos},
%% @todo This one on mission end/abort?
%~ OldNPCUser#users{lid=PartyPos, instancepid=undefined, areatype=AreaType, area={0, 0, 0}, entryid=0, pos={0.0, 0.0, 0.0, 0}}
egs_users:write(NPCUser),
psu_proto:send_010d(NPCUser, State),
psu_proto:send_0201(NPCUser, State),
psu_proto:send_0215(0, State),
send_0a04(NPCUser#users.gid),
send_1004(npc_mission, NPCUser, PartyPos),
send_100f((NPCUser#users.character)#characters.npcid, PartyPos),
send_1601(PartyPos),
send_1016(PartyPos),
npc_load(Leader, NPCList, State).
%% @doc Build the packet header.
header(Command) ->
GID = get(gid),
<< Command:16/unsigned-integer, 16#0300:16, 0:160, 16#00011300:32, GID:32/little, 0:64 >>.
%% @doc Send the given packet to the client.
%% @todo Consolidate the receive and send functions better.
send(Packet) ->
psu_proto:packet_send(get(socket), Packet).
%% @doc Send a shop listing.
send_010a(ItemsList) ->
GID = get(gid),
NbItems = length(ItemsList),
ItemsBin = build_010a_list(ItemsList, []),
send(<< 16#010a0300:32, 0:64, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
GID:32/little, 0:32, 1:16/little, NbItems:8, 2:8, 0:32, ItemsBin/binary >>).
%% @todo The values set to 0 are unknown.
build_010a_list([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
build_010a_list([ItemID|Tail], Acc) ->
#psu_item{name=Name, rarity=Rarity, buy_price=SellPrice, data=Data} = egs_items_db:read(ItemID),
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
NamePadding = 8 * (46 - byte_size(UCS2Name)),
RarityBin = Rarity - 1,
DataBin = build_item_constants(Data),
BinItemID = case element(1, Data) of
psu_clothing_item -> %% Change the ItemID to enable all colors.
<< A:8, _:4, B:12, _:8 >> = << ItemID:32 >>,
<< A:8, 3:4, B:12, 16#ff:8 >>;
_Any ->
<< ItemID:32 >>
end,
Bin = << UCS2Name/binary, 0:NamePadding, RarityBin:8, 0:8, BinItemID/binary, SellPrice:32/little, DataBin/binary >>,
build_010a_list(Tail, [Bin|Acc]).
%% @todo Types capability list.
send_0113() ->
{ok, File} = file:read_file("p/typesinfo.bin"),
GID = get(gid),
send(<< 16#01130300:32, 0:64, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, GID:32/little, File/binary >>).
%% @todo No idea!
send_022c(A, B) ->
send(<< (header(16#022c))/binary, A:16/little, B:16/little >>).
%% @todo Force send a new player location. Used for warps.
%% @todo The value before IntDir seems to be the player's current animation. 01 stand up, 08 ?, 17 normal sit
send_0503({PrevX, PrevY, PrevZ, _AnyDir}) ->
{ok, User} = egs_users:read(get(gid)),
#users{gid=GID, pos={X, Y, Z, Dir}, area={QuestID, ZoneID, MapID}, entryid=EntryID} = User,
IntDir = trunc(Dir * 182.0416),
send(<< 16#05030300:32, 0:64, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, GID:32/little, 0:32,
16#1000:16, IntDir:16/little, PrevX:32/little-float, PrevY:32/little-float, PrevZ:32/little-float, X:32/little-float, Y:32/little-float, Z:32/little-float,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, 1:32/little >>).
%% @todo NPC inventory. Guessing it's only for NPC characters...
send_0a04(NPCGID) ->
GID = get(gid),
{ok, Bin} = file:read_file("p/packet0a04.bin"),
send(<< 16#0a040300:32, 0:32, 16#00001d00:32, NPCGID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, Bin/binary >>).
%% @todo Handle more than just goggles.
send_0a0a(Inventory) ->
{ok, << _:68608/bits, Rest/bits >>} = file:read_file("p/packet0a0a.bin"),
GID = get(gid),
NbItems = length(Inventory),
ItemVariables = build_0a0a_item_variables(Inventory, 1, []),
ItemConstants = build_0a0a_item_constants(Inventory, []),
send(<< 16#0a0a0300:32, 16#ffff:16, 0:144, 16#00011300:32, GID:32/little, 0:64, NbItems:8, 0:8, 6:8, 0:72, 0:192, 0:2304, ItemVariables/binary, ItemConstants/binary, 0:13824, Rest/binary >>).
build_0a0a_item_variables([], _N, Acc) ->
Bin = iolist_to_binary(lists:reverse(Acc)),
Padding = 17280 - 8 * byte_size(Bin),
<< Bin/binary, 0:Padding >>;
build_0a0a_item_variables([{ItemID, Variables}|Tail], N, Acc) ->
build_0a0a_item_variables(Tail, N + 1, [build_item_variables(ItemID, N, Variables)|Acc]).
build_item_variables(ItemID, ItemUUID, #psu_clothing_item_variables{color=ColorNb}) ->
#psu_item{rarity=Rarity, data=#psu_clothing_item{colors=ColorsBin}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
ColorInt = if ColorNb < 5 -> ColorNb; true -> 16#10 + ColorNb - 5 end,
Bits = ColorNb * 8,
<< _Before:Bits, ColorA:4, ColorB:4, _After/bits >> = ColorsBin,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:88, RarityInt:8, ColorA:8, ColorB:8, ColorInt:8, 0:72 >>;
build_item_variables(ItemID, ItemUUID, #psu_consumable_item_variables{quantity=Quantity}) ->
#psu_item{rarity=Rarity, data=#psu_consumable_item{max_quantity=MaxQuantity, action=Action}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, Quantity:32/little, MaxQuantity:32/little, 0:24, RarityInt:8, Action:8, 0:88 >>;
build_item_variables(ItemID, ItemUUID, #psu_parts_item_variables{}) ->
#psu_item{rarity=Rarity} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:88, RarityInt:8, 0:96 >>;
%% @todo Handle rank, rarity and hands properly.
build_item_variables(ItemID, ItemUUID, Variables) when element(1, Variables) =:= psu_striking_weapon_item_variables ->
#psu_striking_weapon_item_variables{is_active=IsActive, slot=Slot, current_pp=CurrentPP, max_pp=MaxPP,
element=#psu_element{type=EleType, percent=ElePercent}, pa=#psu_pa{type=PAType, level=PALevel}} = Variables,
Rank = 4,
Grind = 0,
Rarity = 14, %% Rarity - 1
Hand = both,
<< _:8, WeaponType:8, _:16 >> = << ItemID:32 >>,
HandBin = case Hand of
both -> << 16#0000:16 >>;
_ -> error
end,
<< IsActive:8, Slot:8, 0:16, ItemUUID:32/little, ItemID:32, 0:32, CurrentPP:16/little, MaxPP:16/little, 0:16, %% @todo What's this 0:16?
Grind:4, Rank:4, Rarity:8, EleType:8, ElePercent:8, HandBin/binary, WeaponType:8, PAType:8, PALevel:8, 0:40 >>;
build_item_variables(ItemID, ItemUUID, #psu_special_item_variables{}) ->
Action = case ItemID of
16#11010000 -> << 16#12020100:32 >>;
16#11020000 -> << 16#15000000:32 >>;
16#11020100 -> << 0:32 >>;
16#11020200 -> << 0:32 >>
end,
<< 0:32, ItemUUID:32/little, ItemID:32, 0:24, 16#80:8, 0:56, 16#80:8, 0:32, Action/binary, 0:32 >>;
build_item_variables(ItemID, ItemUUID, #psu_trap_item_variables{quantity=Quantity}) ->
#psu_item{rarity=Rarity, data=#psu_trap_item{max_quantity=MaxQuantity}} = egs_items_db:read(ItemID),
RarityInt = Rarity - 1,
<< 0:32, ItemUUID:32/little, ItemID:32, Quantity:32/little, MaxQuantity:32/little, 0:24, RarityInt:8, 0:96 >>.
build_0a0a_item_constants([], Acc) ->
Bin = iolist_to_binary(lists:reverse(Acc)),
Padding = 34560 - 8 * byte_size(Bin),
<< Bin/binary, 0:Padding >>;
build_0a0a_item_constants([{ItemID, _Variables}|Tail], Acc) ->
#psu_item{name=Name, rarity=Rarity, sell_price=SellPrice, data=Data} = egs_items_db:read(ItemID),
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
NamePadding = 8 * (46 - byte_size(UCS2Name)),
<< Category:8, _:24 >> = << ItemID:32 >>,
DataBin = build_item_constants(Data),
RarityInt = Rarity - 1,
Bin = << UCS2Name/binary, 0:NamePadding, RarityInt:8, Category:8, SellPrice:32/little, DataBin/binary >>,
build_0a0a_item_constants(Tail, [Bin|Acc]).
build_item_constants(#psu_clothing_item{appearance=Appearance, manufacturer=Manufacturer, type=Type, overlap=Overlap, gender=Gender, colors=Colors}) ->
GenderInt = case Gender of male -> 16#1b; female -> 16#2b end,
<< Appearance:16, Type:4, Manufacturer:4, Overlap:8, GenderInt:8, Colors/binary, 0:40 >>;
build_item_constants(#psu_consumable_item{max_quantity=MaxQuantity, pt_diff=PointsDiff,
status_effect=StatusEffect, target=Target, use_condition=UseCondition, item_effect=ItemEffect}) ->
<< 0:8, MaxQuantity:8, Target:8, UseCondition:8, PointsDiff:16/little, StatusEffect:8, ItemEffect:8, 0:96 >>;
build_item_constants(#psu_parts_item{appearance=Appearance, manufacturer=Manufacturer, type=Type, overlap=Overlap, gender=Gender}) ->
GenderInt = case Gender of male -> 16#14; female -> 16#24 end,
<< Appearance:16, Type:4, Manufacturer:4, Overlap:8, GenderInt:8, 0:120 >>;
%% @todo Handle rank properly.
build_item_constants(#psu_striking_weapon_item{pp=PP, atp=ATP, ata=ATA, atp_req=Req, shop_element=#psu_element{type=EleType, percent=ElePercent},
hand=Hand, max_upgrades=MaxUpgrades, attack_label=AttackLabel}) ->
Rank = 4,
HandInt = case Hand of
both -> 0;
_ -> error
end,
<< PP:16/little, ATP:16/little, ATA:16/little, Req:16/little, 16#ffffff:24,
EleType:8, ElePercent:8, HandInt:8, 0:8, Rank:8, 0:8, MaxUpgrades:8, AttackLabel:8, 0:8 >>;
build_item_constants(#psu_trap_item{max_quantity=MaxQuantity}) ->
<< 2:32/little, 16#ffffff:24, MaxQuantity:8, 0:96 >>;
build_item_constants(#psu_special_item{}) ->
<< 0:160 >>.
%% @todo Figure out last 4 bytes!
send_0c02() ->
send(<< (header(16#0c02))/binary, 0:32 >>).
%% @doc Send the trial start notification.
send_0c09() ->
send(<< (header(16#0c09))/binary, 0:64 >>).
%% @doc Send the character list for selection.
%% @todo There's a few odd values blanked, also the last known location apparently.
send_0d03(Data0, Data1, Data2, Data3) ->
[{status, Status0}, {char, Char0}|_] = Data0,
[{status, Status1}, {char, Char1}|_] = Data1,
[{status, Status2}, {char, Char2}|_] = Data2,
[{status, Status3}, {char, Char3}|_] = Data3,
GID = get(gid),
send(<< 16#0d030300:32/unsigned-integer, 0:32, 16#00011300:32, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:104,
Status0:8, 0:48, Char0/binary, 0:520,
Status1:8, 0:48, Char1/binary, 0:520,
Status2:8, 0:48, Char2/binary, 0:520,
Status3:8, 0:48, Char3/binary, 0:512 >>).
%% @todo Add a character (NPC or real) to the party members on the right of the screen.
%% @todo NPCid is 65535 for normal characters.
%% @todo Apparently the 4 location ids are set to 0 when inviting an NPC in the lobby - NPCs have their location set to 0 when in lobby; also odd value before PartyPos related to missions
%% @todo Not sure about LID. But seems like it.
send_1004(Type, User, PartyPos) ->
[TypeID, LID, SomeFlag] = case Type of
npc_mission -> [16#00001d00, PartyPos, 2];
npc_invite -> [0, 16#ffffffff, 3];
_ -> 1 %% seems to be for players
end,
UserGID = get(gid),
#users{gid=GID, character=Character, area={QuestID, ZoneID, MapID}, entryid=EntryID} = User,
#characters{npcid=NPCid, name=Name, mainlevel=MainLevel} = Character,
Level = MainLevel#level.number,
send(<< 16#10040300:32, 16#ffff0000:32, 0:128, 16#00011300:32, UserGID:32/little, 0:64,
TypeID:32, GID:32/little, 0:64, Name/binary,
Level:16/little, 16#ffff:16,
SomeFlag, 1, PartyPos:8, 1,
NPCid:16/little, 0:16,
%% Odd unknown values. PA related? No idea. Values on invite, 0 in-mission.
%~ 16#00001f08:32, 0:32, 16#07000000:32,
%~ 16#04e41f08:32, 0:32, 16#01000000:32,
%~ 16#64e41f08:32, 0:32, 16#02000000:32,
%~ 16#64e41f08:32, 0:32, 16#03000000:32,
%~ 16#64e41f08:32, 0:32, 16#12000000:32,
%~ 16#24e41f08:32,
0:512,
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little,
LID:32/little,
0:64,
16#01000000:32, 16#01000000:32, %% @todo first is current hp, second is max hp
0:608 >>).
%% @todo No idea. Also the 2 PartyPos in the built packet more often than not match, but sometimes don't? That's probably because one is PartyPos and the other is LID or something.
send_100f(NPCid, PartyPos) ->
send(<< (header(16#100f))/binary, NPCid:16/little, 1, PartyPos:8, PartyPos:32/little >>).
%% @doc Send the mission's quest file when starting a new mission.
%% @todo Handle correctly. 0:32 is actually a missing value. Value before that is unknown too.
send_1015(QuestID) ->
QuestData = egs_quests_db:quest_nbl(QuestID),
Size = byte_size(QuestData),
send(<< (header(16#1015))/binary, QuestID:32/little, 16#01010000:32, 0:32, Size:32/little, QuestData/binary >>).
%% @todo No idea.
send_1016(PartyPos) ->
GID = get(gid),
send(<< 16#10160300:32, 16#ffff0000:32, 0:128, 16#00011300:32, GID:32/little, 0:64, PartyPos:32/little >>).
%% @todo No idea.
send_101a(NPCid, PartyPos) ->
send(<< (header(16#101a))/binary, NPCid:16/little, PartyPos:16/little, 16#ffffffff:32 >>).
%% @todo Boss related command.
send_110e(Data) ->
send(<< (header(16#110e))/binary, Data/binary, 0:32, 5:16/little, 12:16/little, 0:32, 260:32/little >>).
%% @todo Boss related command.
send_1113(Data) ->
send(<< (header(16#1113))/binary, Data/binary >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
send_1202() ->
send(<< (header(16#1202))/binary, 0:32, 16#10000000:32, 0:64, 16#14000000:32, 0:32 >>).
%% @doc Object events response?
%% @todo Not sure what Value does exactly. It's either 0 or 1.
send_1205(EventID, BlockID, Value) ->
send(<< (header(16#1205))/binary, EventID, BlockID, 0:16, Value, 0:24 >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
send_1206() ->
send(<< (header(16#1206))/binary, 0:32, 16#80020000:32, 0:5120 >>).
%% @todo Figure out what this packet does. Sane values for counter and missions for now.
send_1207() ->
Chunk = << 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 0:224, 16#0000ffff:32, 16#ff000000:32, 16#64000a00:32 >>,
send(<< (header(16#1207))/binary, Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary, Chunk/binary >>).
%% @todo Object interaction? Figure out. C probably the interaction type.
%% @todo Apparently A would be TargetID/ffffffff, B would be the player LID, C would be the object type? D still completely unknown.
send_1211(A, B, C, D) ->
send(<< (header(16#1211))/binary, A:32/little, B:32/little, C:32/little, D:32/little >>).
%% @doc Make the client load the quest previously sent.
send_1212() ->
send(<< (header(16#1212))/binary, 0:19200 >>).
%% @todo Not sure. Related to keys.
send_1213(A, B) ->
send(<< (header(16#1213))/binary, A:32/little, B:32/little >>).
%% @todo Related to boss gates.
send_1215(A, B) ->
send(<< (header(16#1215))/binary, A:32/little, 0:16, B:16/little >>).
%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race.
send_1216(Value) ->
GID = get(gid),
send(<< 16#12160300:32, 0:32, 16#00011300:32, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, Value:32/little >>).
%% @todo Figure out this room packet.
send_1309() ->
{ok, << _Size:32, Packet/bits >>} = file:read_file("p/packet1309.bin"),
send(Packet).
%% @todo Figure out this room packet.
send_1332() ->
{ok, << _Size:32, Packet/bits >>} = file:read_file("p/packet1332.bin"),
send(Packet).
%% @todo Send an empty partner card list.
send_1501() ->
GID = get(gid),
send(<< 16#15010300:32, 16#ffff:16, 0:144, 16#00011300:32, GID:32/little, 0:96 >>).
%% @todo Send an empty blacklist.
send_1512() ->
GID = get(gid),
send(<< 16#15120300:32, 16#ffff:16, 0:144, 16#00011300:32, GID:32/little, 0:46144 >>).
%% @todo NPC related packet, sent when there's an NPC in the area.
send_1601(PartyPos) ->
{ok, << _:32, Bin/bits >>} = file:read_file("p/packet1601.bin"),
send(<< (header(16#1601))/binary, PartyPos:32/little, Bin/binary >>).
%% @doc Send the player's NPC and PM information.
%% @todo The value 4 is the card priority. Find what 3 is. When sending, the first 0 is an unknown value.
send_1602() ->
NPCList = egs_npc_db:all(),
NbNPC = length(NPCList),
Bin = iolist_to_binary([<< NPCid:8, 0, 4, 0, 3, 0:24 >> || {NPCid, _Data} <- NPCList]),
MiddlePaddingSize = 8 * (344 - byte_size(Bin)),
PMName = "My PM",
UCS2PMName = << << X:8, 0:8 >> || X <- PMName >>,
EndPaddingSize = 8 * (64 - byte_size(UCS2PMName)),
GID = get(gid),
send(<< 16#16020300:32, 16#ffff:16, 0:144, 16#00011300:32, GID:32/little, 0:96, Bin/binary, 0:MiddlePaddingSize, NbNPC, 0:24, UCS2PMName/binary, 0:EndPaddingSize, 0:32 >>).
%% @doc Party information.
%% @todo Handle existing parties.
send_1706(CharName) ->
send(<< (header(16#1706))/binary, 16#00000300:32, 16#d5c0faff:32, 0:64, CharName/binary,
16#78000000:32, 16#01010000:32, 0:1536, 16#0100c800:32, 16#0601010a:32, 16#ffffffff:32, 0:32 >>).
%% @doc Party settings. Item distribution is random for now.
%% @todo Handle correctly.
send_170a() ->
send(<< (header(16#170a))/binary, 16#01010c08:32 >>).
%% @todo Find what the heck this packet is.
send_170c() ->
{ok, File} = file:read_file("p/packet170c.bin"),
send(<< (header(16#170c))/binary, File/binary >>).
%% @doc PP cube handler.
%% @todo The 4 bytes before the file may vary. Everything past that is the same. Figure things out.
send_1a04() ->
{ok, File} = file:read_file("p/ppcube.bin"),
send(<< (header(16#1a04))/binary, 0:32, File/binary >>).

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#!/bin/sh
echo "EGS is free software available under the GNU GPL version 3"
echo "Copyright (C) 2010 Loic Hoguin"
echo "Copyright (C) 2010-2011 Loic Hoguin"
echo
erl -sname egs -pa ebin -pa deps/*/ebin -boot start_sasl -s ex_reloader -s egs
erl -sname egs -pa apps/*/ebin -pa deps/*/ebin -boot start_sasl -s ex_reloader -s egs