Compare commits
148 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ceea04c3b4 | ||
![]() |
9adab0ea87 | ||
![]() |
6579c26f98 | ||
![]() |
368bb0f7b4 | ||
![]() |
807510f669 | ||
![]() |
c69513073d | ||
![]() |
35b86b58ba | ||
![]() |
ac0c3c551b | ||
![]() |
289ce855ad | ||
![]() |
bc47fcb049 | ||
![]() |
408e7e99e6 | ||
![]() |
c42b1a85f8 | ||
![]() |
e2274666b8 | ||
![]() |
6fd1119777 | ||
![]() |
eab53bc3a7 | ||
![]() |
2325c7cf63 | ||
![]() |
8889ca8332 | ||
![]() |
23e781f498 | ||
![]() |
60b8009382 | ||
![]() |
325c1a4c10 | ||
![]() |
8f3db6480a | ||
![]() |
8363741b3a | ||
![]() |
935b490461 | ||
![]() |
948873ddec | ||
![]() |
72726bdf6c | ||
![]() |
0a399238f4 | ||
![]() |
c957d9a8b9 | ||
![]() |
d9cde30b0b | ||
![]() |
c7cb5ab589 | ||
![]() |
dc26bb82f7 | ||
![]() |
d8906226a5 | ||
![]() |
730f47d837 | ||
![]() |
d2f7c9e83f | ||
![]() |
6466f05728 | ||
![]() |
3821a1e7bf | ||
![]() |
a1bf3e43f3 | ||
![]() |
3290aba95d | ||
![]() |
72989f5332 | ||
![]() |
6cbb987ab0 | ||
![]() |
9c8ad80a07 | ||
![]() |
b5d6b3934b | ||
![]() |
cdc2c56d5c | ||
![]() |
5b255b211b | ||
![]() |
edb061662f | ||
![]() |
9268f3f7ae | ||
![]() |
dd4a228b01 | ||
![]() |
34b4a21ce7 | ||
![]() |
2a7383b9a1 | ||
![]() |
3f1d1d2fb0 | ||
![]() |
18429b8d76 | ||
![]() |
82aca844f2 | ||
![]() |
1b36ff2589 | ||
![]() |
4ff28e0939 | ||
![]() |
f21f23a92f | ||
![]() |
979e99f6a5 | ||
![]() |
58cfb8a61e | ||
![]() |
9aca48a697 | ||
![]() |
722bcf7c9e | ||
![]() |
dc77b8e804 | ||
![]() |
eaafdc213f | ||
![]() |
f8524ca9c4 | ||
![]() |
8c95aab709 | ||
![]() |
87c256edba | ||
![]() |
9c7f8f6eaa | ||
![]() |
5c234257dc | ||
![]() |
a571b9a56b | ||
![]() |
dfa5634adb | ||
![]() |
53a4b3dbbe | ||
![]() |
4faabbda8f | ||
![]() |
023214793c | ||
![]() |
250a22dea8 | ||
![]() |
f40ba44364 | ||
![]() |
d0e15316b8 | ||
![]() |
4848b3c218 | ||
![]() |
7415da1c89 | ||
![]() |
e8c1c98824 | ||
![]() |
fde0f8b3fe | ||
![]() |
82e82503fa | ||
![]() |
fde008c7a8 | ||
![]() |
564ab8749f | ||
![]() |
6dc9fb52dc | ||
![]() |
0b02718faa | ||
![]() |
333e898bb4 | ||
![]() |
64d8bf7c25 | ||
![]() |
a44fc4274f | ||
![]() |
5f0bd73303 | ||
![]() |
19350ba1ff | ||
![]() |
998263b417 | ||
![]() |
b6c1bf277d | ||
![]() |
86bb5c81b3 | ||
![]() |
d69fe073a8 | ||
![]() |
6c8b831fd2 | ||
![]() |
953da28a3e | ||
![]() |
344c534812 | ||
![]() |
3280e79743 | ||
![]() |
6e922a7ec9 | ||
![]() |
823ee73e7d | ||
![]() |
40d2eed01b | ||
![]() |
ac8d6858cd | ||
![]() |
e409241a50 | ||
![]() |
25c9548ec3 | ||
![]() |
0b8c4dbd85 | ||
![]() |
8eae404797 | ||
![]() |
840db6b7b3 | ||
![]() |
d5b5afa0a7 | ||
![]() |
344b88eec4 | ||
![]() |
69a07dfad2 | ||
![]() |
57e4e91187 | ||
![]() |
722e0a53f4 | ||
![]() |
0f64bea72d | ||
![]() |
b380fe9d23 | ||
![]() |
ecee1226aa | ||
![]() |
378e9a9927 | ||
![]() |
2de4359c32 | ||
![]() |
b728731830 | ||
![]() |
1be3c4f5c6 | ||
![]() |
e7246271ad | ||
![]() |
ad7071a61b | ||
![]() |
50c2a2615f | ||
![]() |
afb888e566 | ||
![]() |
326b356d02 | ||
![]() |
9d10c28504 | ||
![]() |
06a032652a | ||
![]() |
a4d9c5a35d | ||
![]() |
526f281e2b | ||
![]() |
bf7fa44897 | ||
![]() |
11a03f3a96 | ||
![]() |
7e42b9b26a | ||
![]() |
a1b8b1909e | ||
![]() |
8f069e72d8 | ||
![]() |
9247e51fb7 | ||
![]() |
ea25d1bd74 | ||
![]() |
ffd27bda46 | ||
![]() |
fdfd49179f | ||
![]() |
0989664035 | ||
![]() |
74f54e7e71 | ||
![]() |
a484de77e0 | ||
![]() |
098ad5243a | ||
![]() |
05149483c8 | ||
![]() |
4dcaa79371 | ||
![]() |
4171f2eba4 | ||
![]() |
bd4a296b0a | ||
![]() |
d022584300 | ||
![]() |
1957ad0f1d | ||
![]() |
3a2bb34cbe | ||
![]() |
cedc2f50d7 | ||
![]() |
8526b5ab8f | ||
![]() |
d043ab4d3d |
7
.gitignore
vendored
7
.gitignore
vendored
@ -1 +1,6 @@
|
||||
Mnesia*
|
||||
apps/*/c_src/*.o
|
||||
apps/*/ebin
|
||||
apps/*/priv/*.so
|
||||
apps/egs/src/egs_script_lexer.erl
|
||||
apps/egs/src/egs_script_parser.erl
|
||||
deps
|
||||
|
32
Makefile
32
Makefile
@ -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,29 +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/>.
|
||||
|
||||
ERL ?= erl
|
||||
ERLC ?= erlc
|
||||
APP := egs
|
||||
REBAR = rebar
|
||||
|
||||
all: clean missions server
|
||||
all: app
|
||||
|
||||
server:
|
||||
@./rebar compile
|
||||
app: deps
|
||||
@$(REBAR) compile
|
||||
|
||||
missions:
|
||||
$(ERLC) src/psu/psu_parser.erl
|
||||
$(ERL) -noshell -noinput -sname missions -pa ebin -run psu_parser run -run init stop
|
||||
rm psu_parser.beam
|
||||
deps:
|
||||
@$(REBAR) get-deps
|
||||
|
||||
clean:
|
||||
@./rebar clean
|
||||
@$(REBAR) clean
|
||||
rm -f erl_crash.dump
|
||||
|
||||
fclean: clean
|
||||
rm -rf Mnesia.egs*
|
||||
tests:
|
||||
@$(REBAR) eunit
|
||||
@$(REBAR) ct
|
||||
|
||||
run:
|
||||
@echo "EGS is free software available under the GNU GPL version 3"
|
||||
@echo "Copyright (C) 2010 Loic Hoguin"
|
||||
@echo
|
||||
$(ERL) -sname egs -pa ebin -boot start_sasl -s reloader -s egs
|
||||
dialyze:
|
||||
@$(REBAR) dialyze
|
||||
|
@ -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,13 +17,67 @@
|
||||
%% 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 Per-process state used by the various EGS modules.
|
||||
-record(state, {socket, gid, slot, lid=16#ffff, areanb=0}).
|
||||
%% Records.
|
||||
|
||||
%% @doc Accounts. So far only used for storing temporary information.
|
||||
%% @todo Hash the password.
|
||||
%% @todo Add email, password_salt, is_ingame, register_time, last_login_time, etc.
|
||||
-record(accounts, {gid, username, password, auth_state}).
|
||||
%% @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()
|
||||
}).
|
||||
|
||||
%% @doc Table containing the users currently logged in.
|
||||
%% @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 :: egs:gid(),
|
||||
lid = 16#ffff :: egs:lid(),
|
||||
pid :: pid(),
|
||||
%% 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 :: egs:area(),
|
||||
entryid :: egs:entryid(),
|
||||
pos = {0.0, 0.0, 0.0, 0.0} :: egs:position(),
|
||||
shopid :: integer(),
|
||||
prev_area = {0, 0, 0} :: egs:area(),
|
||||
prev_entryid = 0 :: egs:entryid(),
|
||||
%% To be moved or deleted later on.
|
||||
instancepid :: pid(),
|
||||
char
|
||||
}).
|
||||
|
||||
%% Past this point needs to be reviewed.
|
||||
|
||||
%% @doc NPC configuration data.
|
||||
%% @todo Add inventory, AI parameters.
|
||||
@ -57,36 +111,6 @@
|
||||
faceboxx=65535, faceboxy=65535
|
||||
}).
|
||||
|
||||
%% Past this point needs to be reviewed.
|
||||
|
||||
%% @doc Table containing counters current values.
|
||||
-record(counters, {name, id}).
|
||||
|
||||
%% @doc Character position data structure.
|
||||
|
||||
-record(pos, {x, y, z, dir}).
|
||||
|
||||
%% @doc Character area location data structure.
|
||||
|
||||
-record(psu_area, {questid, zoneid, mapid}).
|
||||
|
||||
%% @doc Table containing the users currently logged in.
|
||||
%% @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(egs_user_model, {
|
||||
%% General information.
|
||||
id, lid, pid, time, character,
|
||||
%% Location/state related information.
|
||||
uni, instancepid, partypid, areatype, area, entryid, pos=#pos{x=0.0, y=0.0, z=0.0, dir=0.0}, shopid,
|
||||
prev_area=#psu_area{questid=0, zoneid=0, mapid=0}, prev_entryid=0, %% universeid
|
||||
%% To be moved or deleted later on.
|
||||
setid=0 %% @todo Current area's set number. Move that to psu_instance probably.
|
||||
}).
|
||||
|
||||
%% @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}).
|
||||
@ -97,38 +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 Table containing all mission objects.
|
||||
|
||||
-record(psu_object, {id, instancepid, type, args}).
|
||||
|
||||
%% @doc Hit response data.
|
||||
|
||||
-record(hit_response, {type, user, exp, damage, targethp, targetse, events}).
|
6
apps/egs/rebar.config
Normal file
6
apps/egs/rebar.config
Normal 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]}.
|
@ -1,7 +1,7 @@
|
||||
%%-*- mode: erlang -*-
|
||||
{application, egs, [
|
||||
{description, "EGS online action-RPG game server"},
|
||||
{vsn, "0.9"},
|
||||
{vsn, "0.14"},
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
@ -9,7 +9,7 @@
|
||||
stdlib,
|
||||
crypto,
|
||||
ssl,
|
||||
mnesia
|
||||
cowboy
|
||||
]},
|
||||
{mod, {egs_app, []}},
|
||||
{env, []}
|
85
apps/egs/src/egs.erl
Normal file
85
apps/egs/src/egs.erl
Normal 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.
|
143
apps/egs/src/egs_accounts.erl
Normal file
143
apps/egs/src/egs_accounts.erl
Normal file
@ -0,0 +1,143 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010-2011 Loïc Hoguin.
|
||||
%% @doc Accounts 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_accounts).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0, stop/0, get_folder/1, key_auth/2, key_auth_init/1, key_auth_timeout/1, login_auth/2, tmp_gid/0]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%% @todo Make accounts permanent.
|
||||
%% @todo Hash the password.
|
||||
%% @todo Add email, password_salt, is_ingame, register_time, last_login_time, etc.
|
||||
-record(accounts, {
|
||||
gid :: egs:gid(),
|
||||
username :: string(),
|
||||
password :: string(),
|
||||
auth_state :: undefined | {wait_for_authentication, binary(), any()}
|
||||
}).
|
||||
|
||||
-record(state, {
|
||||
accounts = [] :: list({egs:gid(), #accounts{}}),
|
||||
next_gid = 10000001 :: integer(),
|
||||
tmp_gid = 16#ff000001 :: integer()
|
||||
}).
|
||||
|
||||
%% API.
|
||||
|
||||
-spec start_link() -> {ok, Pid::pid()}.
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
-spec stop() -> stopped.
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @todo Temporary code until we properly save the player data.
|
||||
get_folder(GID) ->
|
||||
gen_server:call(?SERVER, {get_folder, GID}).
|
||||
|
||||
-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(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(egs:gid()) -> ok.
|
||||
%% @doc Key authentication timeout handling.
|
||||
%% @todo Probably handle the authentication in a gen_fsm properly.
|
||||
key_auth_timeout(GID) ->
|
||||
gen_server:cast(?SERVER, {key_auth_timeout, GID}).
|
||||
|
||||
-spec login_auth(Username::binary(), Password::binary()) -> {ok, GID::integer()}.
|
||||
%% @doc Authenticate using the given username and password.
|
||||
%% @todo Properly handle login authentication when accounts are saved.
|
||||
login_auth(Username, Password) ->
|
||||
gen_server:call(?SERVER, {login_auth, Username, Password}).
|
||||
|
||||
-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).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({get_folder, GID}, _From, State) ->
|
||||
{_, #accounts{username=Username, password=Password}} = lists:keyfind(GID, 1, State#state.accounts),
|
||||
{reply, << Username/binary, "-", Password/binary >>, State};
|
||||
|
||||
handle_call({key_auth, GID, AuthKey}, _From, State) ->
|
||||
{_, Account = #accounts{auth_state=AuthState}} = lists:keyfind(GID, 1, State#state.accounts),
|
||||
case AuthState of
|
||||
{wait_for_authentication, AuthKey, TRef} ->
|
||||
timer:cancel(TRef),
|
||||
Accounts = lists:delete({GID, Account}, State#state.accounts),
|
||||
{reply, ok, State#state{accounts=[{GID, Account#accounts{auth_state=undefined}}|Accounts]}};
|
||||
undefined ->
|
||||
{reply, {error, badarg}, State}
|
||||
end;
|
||||
|
||||
handle_call({key_auth_init, GID}, _From, State) ->
|
||||
AuthKey = crypto:rand_bytes(4),
|
||||
TRef = timer:apply_after(10000, ?MODULE, key_auth_timeout, [GID]),
|
||||
{_, Account} = lists:keyfind(GID, 1, State#state.accounts),
|
||||
Accounts = lists:delete({GID, Account}, State#state.accounts),
|
||||
{reply, {ok, AuthKey}, State#state{accounts=
|
||||
[{GID, Account#accounts{auth_state={wait_for_authentication, AuthKey, TRef}}}|Accounts]}};
|
||||
|
||||
handle_call({login_auth, Username, Password}, _From, State) ->
|
||||
GID = State#state.next_gid,
|
||||
Account = #accounts{gid=GID, username=Username, password=Password},
|
||||
{reply, {ok, GID}, State#state{next_gid=GID + 1, accounts=[{GID, Account}|State#state.accounts]}};
|
||||
|
||||
handle_call(tmp_gid, _From, State) ->
|
||||
GID = State#state.tmp_gid,
|
||||
{reply, GID, State#state{tmp_gid=GID + 1}};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast({key_auth_timeout, GID}, State) ->
|
||||
{_, Account} = lists:keyfind(GID, 1, State#state.accounts),
|
||||
Accounts = lists:delete({GID, Account}, State#state.accounts),
|
||||
{noreply, State#state{accounts= [{GID, Account#accounts{auth_state=undefined}}|Accounts]}};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
58
apps/egs/src/egs_app.erl
Normal file
58
apps/egs/src/egs_app.erl
Normal 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
64
apps/egs/src/egs_char.erl
Normal file
@ -0,0 +1,64 @@
|
||||
-module(egs_char).
|
||||
|
||||
-export([new/6]).
|
||||
|
||||
-include_lib("erlson/include/erlson.hrl").
|
||||
|
||||
%% @todo Add the current location for backtopreviousfield
|
||||
new(Slot, Name, Race, Gender, Class, Appearance) ->
|
||||
#{
|
||||
type=player,
|
||||
|
||||
slot=Slot,
|
||||
name=Name,
|
||||
race=Race,
|
||||
gender=Gender,
|
||||
class=Class,
|
||||
appearance=Appearance,
|
||||
|
||||
level=1,
|
||||
exp=0,
|
||||
|
||||
hunter_level=1,
|
||||
hunter_exp=0,
|
||||
ranger_level=1,
|
||||
ranger_exp=0,
|
||||
force_level=1,
|
||||
force_exp=0,
|
||||
acro_level=1,
|
||||
acro_level=0,
|
||||
|
||||
blast_bar=0,
|
||||
luck=3,
|
||||
playtime=0,
|
||||
|
||||
%% current_uni,
|
||||
%% current location + entryid
|
||||
%% previous location + entryid
|
||||
%% pids for such
|
||||
|
||||
money=1000000,
|
||||
inventory=[],
|
||||
|
||||
card_comment= <<>>,
|
||||
|
||||
options=#{
|
||||
brightness=4,
|
||||
buttonhelp=0,
|
||||
cam1stx=0,
|
||||
cam1sty=0,
|
||||
cam3rdx=0,
|
||||
cam3rdy=0,
|
||||
controller=0,
|
||||
cursorpos=0,
|
||||
cutin=0,
|
||||
fnkeys=0,
|
||||
lockon=0,
|
||||
musicvolume=0,
|
||||
radarmap=0,
|
||||
sfxvolume=0,
|
||||
sound=0,
|
||||
textspeed=0,
|
||||
vibration=0,
|
||||
weaponswap=0
|
||||
}}.
|
86
apps/egs/src/egs_char_select.erl
Normal file
86
apps/egs/src/egs_char_select.erl
Normal 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.
|
@ -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};
|
@ -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.
|
||||
|
@ -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.
|
||||
@ -62,8 +62,7 @@ load_counter_pack_quests([{_QuestID, Filename}|Tail], PosList, SizeList, Acc) ->
|
||||
load_counter_pack_quests(Tail, [Pos|PosList], [Size|SizeList], [File|Acc]).
|
||||
|
||||
%% @doc Load a quest configuration file and return a quest.xnr binary along with its pointers array.
|
||||
load_quest_xnr(ConfFilename) ->
|
||||
{ok, Settings} = file:consult(ConfFilename),
|
||||
load_quest_xnr(Settings) ->
|
||||
QuestID = proplists:get_value(questid, Settings),
|
||||
%% Temp flags.
|
||||
TmpFlagsList = proplists:get_value(temp_flags, Settings),
|
||||
@ -279,6 +278,7 @@ load_set_rel_object_id(npc) -> {2, 18};
|
||||
load_set_rel_object_id(door) -> {5, 20};
|
||||
load_set_rel_object_id(entrance) -> {2, 26};
|
||||
load_set_rel_object_id(exit) -> {6, 27};
|
||||
load_set_rel_object_id(type_counter) -> {1, 47};
|
||||
load_set_rel_object_id(label) -> {2, 53};
|
||||
load_set_rel_object_id(chair) -> {2, 56};
|
||||
load_set_rel_object_id(uni_cube) -> {0, 60};
|
||||
@ -343,6 +343,10 @@ load_set_rel_object_params(exit, Params) ->
|
||||
map ->
|
||||
<< EntryID:16/little, 0:32, AnimationID:8, 1:8, 255:8, 0:16, 1:8, CommonBin/binary >>
|
||||
end;
|
||||
%% @todo Mostly unknown values!
|
||||
load_set_rel_object_params(type_counter, Params) ->
|
||||
TalkRadius = proplists:get_value(talk_radius, Params),
|
||||
<< TalkRadius:32/little-float, 0:384, 16#ffffffff:32 >>;
|
||||
%% @todo Not sure about the box. It's probably wrong.
|
||||
%% @todo Can only have up to 10 different label ids.
|
||||
load_set_rel_object_params(label, Params) ->
|
||||
@ -502,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,
|
857
apps/egs/src/egs_game.erl
Normal file
857
apps/egs/src/egs_game.erl
Normal file
@ -0,0 +1,857 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010-2011 Loïc Hoguin.
|
||||
%% @doc Game 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_game).
|
||||
-export([info/2, cast/2, raw/3, event/2]).
|
||||
-export([char_load/2]). %% Hopefully temporary export.
|
||||
|
||||
-include("include/records.hrl").
|
||||
|
||||
%% @doc Forward the broadcasted command to the client.
|
||||
info({egs, cast, Command}, Client=#egs_net{gid=GID}) ->
|
||||
<< A:64/bits, _:32, B:96/bits, _:64, C/bits >> = Command,
|
||||
egs_proto:packet_send(Client, << A/binary, 16#00011300:32, B/binary, 16#00011300:32, GID:32/little, C/binary >>);
|
||||
|
||||
%% @doc Forward the chat message to the client.
|
||||
info({egs, chat, FromGID, ChatTypeID, ChatGID, ChatName, ChatModifiers, ChatMessage}, Client) ->
|
||||
egs_proto:send_0304(FromGID, ChatTypeID, ChatGID, ChatName, ChatModifiers, ChatMessage, Client);
|
||||
|
||||
info({egs, notice, Type, Message}, Client) ->
|
||||
egs_proto:send_0228(Type, 2, Message, Client);
|
||||
|
||||
%% @doc Inform the client that a player has spawn.
|
||||
%% @todo Not sure what IsSeasonal or the AreaNb in 0205 should be for other spawns.
|
||||
info({egs, player_spawn, Player}, Client) ->
|
||||
egs_proto:send_0111(Player, 6, Client),
|
||||
egs_proto:send_010d(Player, Client),
|
||||
egs_proto:send_0205(Player, 0, Client),
|
||||
egs_proto:send_0203(Player, Client),
|
||||
egs_proto:send_0201(Player, Client);
|
||||
|
||||
%% @doc Inform the client that a player has unspawn.
|
||||
info({egs, player_unspawn, Player}, Client) ->
|
||||
egs_proto:send_0204(Player, Client);
|
||||
|
||||
%% @doc Warp the player to the given location.
|
||||
info({egs, warp, QuestID, ZoneID, MapID, EntryID}, Client) ->
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID}, Client).
|
||||
|
||||
%% Broadcasts.
|
||||
|
||||
%% @todo Handle broadcasting better than that. Review the commands at the same time.
|
||||
%% @doc Position change. Save the position and then dispatch it.
|
||||
cast({16#0503, Data}, Client=#egs_net{gid=GID}) ->
|
||||
<< _:424, Dir:24/little, _PrevCoords:96, X:32/little-float, Y:32/little-float, Z:32/little-float,
|
||||
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, _:32 >> = Data,
|
||||
FloatDir = Dir / 46603.375,
|
||||
{ok, User} = egs_users:read(GID),
|
||||
NewUser = User#users{pos={X, Y, Z, FloatDir}, area={QuestID, ZoneID, MapID}, entryid=EntryID},
|
||||
egs_users:write(NewUser),
|
||||
cast({valid, Data}, Client);
|
||||
|
||||
%% @doc Stand still. Save the position and then dispatch it.
|
||||
cast({16#0514, Data}, Client=#egs_net{gid=GID}) ->
|
||||
<< _:424, Dir:24/little, X:32/little-float, Y:32/little-float, Z:32/little-float,
|
||||
QuestID:32/little, ZoneID:32/little, MapID:32/little, EntryID:32/little, _/bits >> = Data,
|
||||
FloatDir = Dir / 46603.375,
|
||||
{ok, User} = egs_users:read(GID),
|
||||
NewUser = User#users{pos={X, Y, Z, FloatDir}, area={QuestID, ZoneID, MapID}, entryid=EntryID},
|
||||
egs_users:write(NewUser),
|
||||
cast({valid, Data}, Client);
|
||||
|
||||
%% @doc Default broadcast handler. Dispatch the command to everyone.
|
||||
%% We clean up the command and use the real GID and LID of the user, disregarding what was sent and possibly tampered with.
|
||||
%% Only a handful of commands are allowed to broadcast. An user tampering with it would get disconnected instantly.
|
||||
%% @todo Don't query the user data everytime! Keep the needed information in the Client.
|
||||
cast({Command, Data}, #egs_net{gid=GID, lid=LID})
|
||||
when Command =:= 16#0101;
|
||||
Command =:= 16#0102;
|
||||
Command =:= 16#0104;
|
||||
Command =:= 16#0107;
|
||||
Command =:= 16#010f;
|
||||
Command =:= 16#050f;
|
||||
Command =:= valid ->
|
||||
<< _:32, A:64/bits, _:64, B:192/bits, _:64, C/bits >> = Data,
|
||||
{ok, User} = egs_users:read(GID),
|
||||
Packet = << A/binary, 16#00011300:32, GID:32/little, B/binary, GID:32/little, LID:32/little, C/binary >>,
|
||||
egs_zones:broadcast(User#users.zonepid, GID, Packet).
|
||||
|
||||
%% Raw commands.
|
||||
|
||||
%% @todo Handle this packet properly.
|
||||
%% @todo Spawn cleared response event shouldn't be handled following this packet but when we see the spawn actually dead HP-wise.
|
||||
%% @todo Type shouldn't be :32 but it seems when the later 16 have something it's not a spawn event.
|
||||
raw(16#0402, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
|
||||
<< SpawnID:32/little, _:64, Type:32/little, _:64 >> = Data,
|
||||
case Type of
|
||||
7 -> % spawn cleared @todo 1201 sent back with same values apparently, but not always
|
||||
io:format("~p: cleared spawn ~b~n", [GID, SpawnID]),
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, EventID} = psu_instance:spawn_cleared_event(User#users.instancepid, element(2, User#users.area), SpawnID),
|
||||
if EventID =:= false -> ignore;
|
||||
true -> egs_proto:send_1205(EventID, BlockID, 0, Client)
|
||||
end;
|
||||
_ ->
|
||||
ignore
|
||||
end;
|
||||
|
||||
%% @todo Handle this packet.
|
||||
%% @todo 3rd Unsafe Passage C, EventID 10 BlockID 2 = mission cleared?
|
||||
raw(16#0404, << _:352, Data/bits >>, Client) ->
|
||||
<< EventID:8, BlockID:8, _:16, Value:8, _/bits >> = Data,
|
||||
io:format("~p: unknown command 0404: eventid ~b blockid ~b value ~b~n", [Client#egs_net.gid, EventID, BlockID, Value]),
|
||||
egs_proto:send_1205(EventID, BlockID, Value, Client);
|
||||
|
||||
%% @todo Used in the tutorial. Not sure what it does. Give an item (the PA) maybe?
|
||||
%% @todo Probably should ignore that until more is known.
|
||||
raw(16#0a09, _Data, Client=#egs_net{gid=GID}) ->
|
||||
egs_proto:packet_send(Client, << 16#0a090300:32, 0:32, 16#00011300:32, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64, 16#00003300:32, 0:32 >>);
|
||||
|
||||
%% @todo Figure out this command.
|
||||
raw(16#0c11, << _:352, A:32/little, B:32/little >>, Client=#egs_net{gid=GID}) ->
|
||||
io:format("~p: 0c11 ~p ~p~n", [GID, A, B]),
|
||||
egs_proto:packet_send(Client, << 16#0c120300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, A:32/little, 1:32/little >>);
|
||||
|
||||
%% @doc Set flag handler. Associate a new flag with the character.
|
||||
%% Just reply with a success value for now.
|
||||
%% @todo God save the flags.
|
||||
raw(16#0d04, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
|
||||
<< Flag:128/bits, A:16/bits, _:8, B/bits >> = Data,
|
||||
io:format("~p: flag handler for ~s~n", [GID, re:replace(Flag, "\\0+", "", [global, {return, binary}])]),
|
||||
egs_proto:packet_send(Client, << 16#0d040300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, Flag/binary, A/binary, 1, B/binary >>);
|
||||
|
||||
%% @doc Initialize a vehicle object.
|
||||
%% @todo Find what are the many values, including the odd Whut value (and whether it's used in the reply).
|
||||
%% @todo Separate the reply.
|
||||
raw(16#0f00, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
|
||||
<< A:32/little, 0:16, B:16/little, 0:16, C:16/little, 0, Whut:8, D:16/little, 0:16,
|
||||
E:16/little, 0:16, F:16/little, G:16/little, H:16/little, I:32/little >> = Data,
|
||||
io:format("~p: init vehicle: ~b ~b ~b ~b ~b ~b ~b ~b ~b ~b~n", [GID, A, B, C, Whut, D, E, F, G, H, I]),
|
||||
egs_proto:packet_send(Client, << 16#12080300:32, 0:160, 16#00011300:32, GID:32/little, 0:64,
|
||||
A:32/little, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32,
|
||||
0:16, B:16/little, 0:16, C:16/little, 0:16, D:16/little, 0:112,
|
||||
E:16/little, 0:16, F:16/little, H:16/little, 1, 0, 100, 0, 10, 0, G:16/little, 0:16 >>);
|
||||
|
||||
%% @doc Enter vehicle.
|
||||
%% @todo Separate the reply.
|
||||
raw(16#0f02, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
|
||||
<< A:32/little, B:32/little, C:32/little >> = Data,
|
||||
io:format("~p: enter vehicle: ~b ~b ~b~n", [GID, A, B, C]),
|
||||
HP = 100,
|
||||
egs_proto:packet_send(Client, << 16#120a0300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, A:32/little, B:32/little, C:32/little, HP:32/little >>);
|
||||
|
||||
%% @doc Sent right after entering the vehicle. Can't move without it.
|
||||
%% @todo Separate the reply.
|
||||
raw(16#0f07, << _:352, Data/bits >>, Client=#egs_net{gid=GID}) ->
|
||||
<< A:32/little, B:32/little >> = Data,
|
||||
io:format("~p: after enter vehicle: ~b ~b~n", [GID, A, B]),
|
||||
egs_proto:packet_send(Client, << 16#120f0300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, A:32/little, B:32/little >>);
|
||||
|
||||
%% @todo Not sure yet.
|
||||
raw(16#1019, _Data, _Client) ->
|
||||
ignore;
|
||||
%~ egs_proto:packet_send(Client, << 16#10190300:32, 0:160, 16#00011300:32, GID:32/little, 0:64, 0:192, 16#00200000:32, 0:32 >>);
|
||||
|
||||
%% @todo Not sure about that one though. Probably related to 1112 still.
|
||||
raw(16#1106, << _:352, Data/bits >>, Client) ->
|
||||
egs_proto:send_110e(Data, Client);
|
||||
|
||||
%% @doc Probably asking permission to start the video (used for syncing?).
|
||||
raw(16#1112, << _:352, Data/bits >>, Client) ->
|
||||
egs_proto:send_1113(Data, Client);
|
||||
|
||||
%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race.
|
||||
raw(16#1216, << _:352, Data/bits >>, Client) ->
|
||||
<< Value:32/little >> = Data,
|
||||
io:format("~p: command 1216 with value ~b~n", [Client#egs_net.gid, Value]),
|
||||
egs_proto:send_1216(Value, Client);
|
||||
|
||||
%% @doc Dismiss all unknown raw commands with a log notice.
|
||||
%% @todo Have a log event handler instead.
|
||||
raw(Command, _Data, Client) ->
|
||||
io:format("~p (~p): dismissed command ~4.16.0b~n", [?MODULE, Client#egs_net.gid, Command]).
|
||||
|
||||
%% Events.
|
||||
|
||||
%% @doc Load the given map as a standard lobby.
|
||||
%% @todo When changing lobby to the room, or room to lobby, we must perform an universe change.
|
||||
%% @todo Handle area_change event for APCs in story missions (characters with PartyPos defined).
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID}, Client) ->
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID, 16#ffffffff}, Client);
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos=16#ffffffff}, Client) ->
|
||||
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#egs_net.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
|
||||
{ok, OldUser} = egs_users:read(Client#egs_net.gid),
|
||||
{OldQuestID, OldZoneID, _OldMapID} = OldUser#users.area,
|
||||
QuestChange = OldQuestID /= QuestID,
|
||||
ZoneChange = if OldQuestID =:= QuestID, OldZoneID =:= ZoneID -> false; true -> true end,
|
||||
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 ->
|
||||
egs_proto:send_0c00(User, Client),
|
||||
egs_proto:send_020e(egs_quests_db:quest_nbl(QuestID), Client),
|
||||
User#users{questpid=egs_universes:lobby_pid(User#users.uni, QuestID)};
|
||||
true -> User
|
||||
end,
|
||||
%% Load the zone.
|
||||
Client1 = 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),
|
||||
NewClient = Client#egs_net{lid=NewLID},
|
||||
{ok, User3} = egs_users:read(User2#users.gid),
|
||||
egs_proto:send_0a05(NewClient),
|
||||
egs_proto:send_0111(User3, 6, NewClient),
|
||||
egs_proto:send_010d(User3, NewClient),
|
||||
egs_proto:send_0200(ZoneID, AreaType, NewClient),
|
||||
egs_proto:send_020f(egs_quests_db:zone_nbl(QuestID, ZoneID), egs_zones:setid(ZonePid), SeasonID, NewClient),
|
||||
NewClient;
|
||||
true ->
|
||||
User3 = User2,
|
||||
Client
|
||||
end,
|
||||
%% Save the user.
|
||||
egs_users:write(User3),
|
||||
%% Load the player location.
|
||||
Client2 = Client1#egs_net{areanb=Client#egs_net.areanb + 1},
|
||||
egs_proto:send_0205(User3, IsSeasonal, Client2),
|
||||
egs_proto:send_100e(User3#users.area, User3#users.entryid, AreaShortName, Client2),
|
||||
%% Load the zone objects.
|
||||
if ZoneChange ->
|
||||
egs_proto:send_1212(Client2); %% @todo Only sent if there is a set file.
|
||||
true -> ignore
|
||||
end,
|
||||
%% Load the player.
|
||||
egs_proto:send_0201(User3, Client2),
|
||||
if ZoneChange ->
|
||||
egs_proto:send_0a06(User3, Client2),
|
||||
%% 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),
|
||||
egs_proto:send_0233(OtherPlayers, Client)
|
||||
end;
|
||||
true -> ignore
|
||||
end,
|
||||
%% End of loading.
|
||||
Client3 = Client2#egs_net{areanb=Client2#egs_net.areanb + 1},
|
||||
egs_proto:send_0208(Client3),
|
||||
egs_proto:send_0236(Client3),
|
||||
%% @todo Load APC characters.
|
||||
{ok, Client3};
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID, PartyPos}, Client) ->
|
||||
io:format("~p: area change (~b,~b,~b,~b,~b)~n", [Client#egs_net.gid, QuestID, ZoneID, MapID, EntryID, PartyPos]),
|
||||
ignore;
|
||||
|
||||
%% @doc After the character has been (re)loaded, change the area he's in.
|
||||
%% @todo The area_change event should probably not change the user's values.
|
||||
%% @todo Remove that ugly code when the above is done.
|
||||
event(system_character_load_complete, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User=#users{area={QuestID, ZoneID, MapID}, entryid=EntryID}} = egs_users:read(GID),
|
||||
egs_users:write(User#users{area={0, 0, 0}, entryid=0}),
|
||||
event({area_change, QuestID, ZoneID, MapID, EntryID}, Client);
|
||||
|
||||
%% @doc Chat broadcast handler. Dispatch the message to everyone (for now).
|
||||
%% Disregard the name sent by the server. Use the name saved in memory instead, to prevent client-side editing.
|
||||
%% @todo Only broadcast to people in the same map.
|
||||
%% @todo In the case of NPC characters, when FromTypeID is 00001d00, check that the NPC is in the party and broadcast only to the party (probably).
|
||||
%% @todo When the game doesn't find an NPC (probably) and forces it to talk like in the tutorial mission it seems FromTypeID, FromGID and Name are all 0.
|
||||
%% @todo Make sure modifiers have correct values.
|
||||
event({chat, _, FromGID, _, Modifiers, _, ChatMsg}, #egs_net{gid=UserGID}) ->
|
||||
[BcastTypeID, BcastGID, BcastName] = case FromGID of
|
||||
0 -> %% This probably shouldn't happen. Just make it crash on purpose.
|
||||
io:format("~p: chat FromGID=0~n", [UserGID]),
|
||||
ignore;
|
||||
UserGID -> %% player chat: disregard whatever was sent except modifiers and message.
|
||||
{ok, User} = egs_users:read(UserGID),
|
||||
[16#00001200, User#users.gid, User#users.name];
|
||||
NPCGID -> %% npc chat: @todo Check that the player is the party leader and this npc is in his party.
|
||||
{ok, User} = egs_users:read(NPCGID),
|
||||
[16#00001d00, FromGID, User#users.name]
|
||||
end,
|
||||
%% log the message as ascii to the console
|
||||
[LogName|_] = re:split(BcastName, "\\0\\0", [{return, binary}]),
|
||||
[TmpMessage|_] = re:split(ChatMsg, "\\0\\0", [{return, binary}]),
|
||||
LogMessage = re:replace(TmpMessage, "\\n", " ", [global, {return, binary}]),
|
||||
io:format("~p: chat from ~s: ~s~n", [UserGID, [re:replace(LogName, "\\0", "", [global, {return, binary}])], [re:replace(LogMessage, "\\0", "", [global, {return, binary}])]]),
|
||||
egs_users:broadcast_all({egs, chat, UserGID, BcastTypeID, BcastGID, BcastName, Modifiers, ChatMsg});
|
||||
|
||||
%% @todo There's at least 9 different sets of locations. Handle all of them correctly.
|
||||
event(counter_background_locations_request, Client) ->
|
||||
egs_proto:send_170c(Client);
|
||||
|
||||
%% @todo Make sure non-mission counters follow the same loading process.
|
||||
%% @todo Probably validate the From* values, to not send the player back inside a mission.
|
||||
%% @todo Handle the LID change when entering counters.
|
||||
event({counter_enter, CounterID, FromZoneID, FromMapID, FromEntryID}, Client=#egs_net{gid=GID}) ->
|
||||
io:format("~p: counter load ~b~n", [GID, CounterID]),
|
||||
{ok, OldUser} = egs_users:read(GID),
|
||||
FromArea = {element(1, OldUser#users.area), FromZoneID, FromMapID},
|
||||
egs_zones:leave(OldUser#users.zonepid, OldUser#users.gid),
|
||||
User = OldUser#users{questpid=undefined, zonepid=undefined, areatype=counter,
|
||||
area={16#7fffffff, 0, 0}, entryid=0, prev_area=FromArea, prev_entryid=FromEntryID},
|
||||
egs_users:write(User),
|
||||
QuestData = egs_quests_db:quest_nbl(0),
|
||||
ZoneData = << 0:16000 >>, %% Doing like official just in case.
|
||||
%% load counter
|
||||
egs_proto:send_0c00(User, Client),
|
||||
egs_proto:send_020e(QuestData, Client),
|
||||
egs_proto:send_0a05(Client),
|
||||
egs_proto:send_010d(User, Client),
|
||||
egs_proto:send_0200(0, mission, Client),
|
||||
egs_proto:send_020f(ZoneData, 0, 255, Client),
|
||||
Client2 = Client#egs_net{areanb=Client#egs_net.areanb + 1},
|
||||
egs_proto:send_0205(User, 0, Client2),
|
||||
egs_proto:send_100e(CounterID, "Counter", Client2),
|
||||
egs_proto:send_0215(0, Client2),
|
||||
egs_proto:send_0215(0, Client2),
|
||||
egs_proto:send_020c(Client2),
|
||||
egs_proto:send_1202(Client2),
|
||||
egs_proto:send_1204(Client2),
|
||||
egs_proto:send_1206(Client2),
|
||||
egs_proto:send_1207(Client2),
|
||||
egs_proto:send_1212(Client2),
|
||||
egs_proto:send_0201(User, Client2),
|
||||
egs_proto:send_0a06(User, Client2),
|
||||
case User#users.partypid of
|
||||
undefined -> ignore;
|
||||
_ -> egs_proto:send_022c(0, 16#12, Client)
|
||||
end,
|
||||
Client3 = Client2#egs_net{areanb=Client2#egs_net.areanb + 1},
|
||||
egs_proto:send_0208(Client3),
|
||||
egs_proto:send_0236(Client3),
|
||||
{ok, Client3};
|
||||
|
||||
%% @todo Handle parties to join.
|
||||
event(counter_join_party_request, Client) ->
|
||||
egs_proto:send_1701(Client);
|
||||
|
||||
%% @doc Leave mission counter handler.
|
||||
event(counter_leave, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
PrevArea = User#users.prev_area,
|
||||
event({area_change, element(1, PrevArea), element(2, PrevArea), element(3, PrevArea), User#users.prev_entryid}, Client);
|
||||
|
||||
%% @doc Send the code for the background image to use. But there's more that should be sent though.
|
||||
%% @todo Apparently background values 1 2 3 are never used on official servers. Find out why.
|
||||
%% @todo Rename to counter_bg_request.
|
||||
event({counter_options_request, CounterID}, Client) ->
|
||||
io:format("~p: counter options request ~p~n", [Client#egs_net.gid, CounterID]),
|
||||
egs_proto:send_1711(egs_counters_db:bg(CounterID), Client);
|
||||
|
||||
%% @todo Handle when the party already exists! And stop doing it wrong.
|
||||
event(counter_party_info_request, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
egs_proto:send_1706(User#users.name, Client);
|
||||
|
||||
%% @todo Item distribution is always set to random for now.
|
||||
event(counter_party_options_request, Client) ->
|
||||
egs_proto:send_170a(Client);
|
||||
|
||||
%% @doc Request the counter's quest files.
|
||||
event({counter_quest_files_request, CounterID}, Client) ->
|
||||
io:format("~p: counter quest files request ~p~n", [Client#egs_net.gid, CounterID]),
|
||||
egs_proto:send_0c06(egs_counters_db:pack(CounterID), Client);
|
||||
|
||||
%% @doc Counter available mission list request handler.
|
||||
event({counter_quest_options_request, CounterID}, Client) ->
|
||||
io:format("~p: counter quest options request ~p~n", [Client#egs_net.gid, CounterID]),
|
||||
egs_proto:send_0c10(egs_counters_db:opts(CounterID), Client);
|
||||
|
||||
%% @todo A and B are mostly unknown. Like most of everything else from the command 0e00...
|
||||
event({hit, FromTargetID, ToTargetID, A, B}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
%% hit!
|
||||
#hit_response{type=Type, user=NewUser, exp=HasEXP, damage=Damage, targethp=TargetHP, targetse=TargetSE, events=Events} = psu_instance:hit(User, FromTargetID, ToTargetID),
|
||||
case Type of
|
||||
box ->
|
||||
%% @todo also has a hit sent, we should send it too
|
||||
events(Events, Client);
|
||||
_ ->
|
||||
PlayerHP = NewUser#users.currenthp,
|
||||
case lists:member(death, TargetSE) of
|
||||
true -> SE = 16#01000200;
|
||||
false -> SE = 16#01000000
|
||||
end,
|
||||
egs_proto:packet_send(Client, << 16#0e070300:32, 0:160, 16#00011300:32, GID:32/little, 0:64,
|
||||
1:32/little, 16#01050000:32, Damage:32/little,
|
||||
A/binary, 0:64, PlayerHP:32/little, 0:32, SE:32,
|
||||
0:32, TargetHP:32/little, 0:32, B/binary,
|
||||
16#04320000:32, 16#80000000:32, 16#26030000:32, 16#89068d00:32, 16#0c1c0105:32, 0:64 >>)
|
||||
% after TargetHP is SE-related too?
|
||||
end,
|
||||
%% exp
|
||||
if HasEXP =:= true ->
|
||||
egs_proto:send_0115(NewUser, ToTargetID, Client);
|
||||
true -> ignore
|
||||
end,
|
||||
%% save
|
||||
egs_users:write(NewUser);
|
||||
|
||||
event({hits, Hits}, Client) ->
|
||||
events(Hits, Client);
|
||||
|
||||
event({item_description_request, ItemID}, Client) ->
|
||||
egs_proto:send_0a11(ItemID, egs_items_db:desc(ItemID), Client);
|
||||
|
||||
%% @todo A and B are unknown.
|
||||
%% Melee uses a format similar to: AAAA--BBCCCC----DDDDDDDDEE----FF with
|
||||
%% AAAA the attack sound effect, BB the range, CCCC and DDDDDDDD unknown but related to angular range or similar, EE number of targets and FF the model.
|
||||
%% Bullets and tech weapons formats are unknown but likely use a slightly different format.
|
||||
%% @todo Others probably want to see that you changed your weapon.
|
||||
%% @todo Apparently B is always ItemID+1. Not sure why.
|
||||
%% @todo Currently use a separate file for the data sent for the weapons.
|
||||
%% @todo TargetGID and TargetLID must be validated, they're either the player's or his NPC characters.
|
||||
%% @todo Handle NPC characters properly.
|
||||
event({item_equip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
|
||||
case egs_users:item_nth(GID, ItemIndex) of
|
||||
{ItemID, Variables} when element(1, Variables) =:= psu_special_item_variables ->
|
||||
<< Category:8, _:24 >> = << ItemID:32 >>,
|
||||
egs_proto:packet_send(Client, << 16#01050300:32, 0:64, TargetGID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
TargetGID:32/little, TargetLID:32/little, ItemIndex:8, 1:8, Category:8, A:8, B:32/little >>);
|
||||
{ItemID, Variables} when element(1, Variables) =:= psu_striking_weapon_item_variables ->
|
||||
#psu_item{data=Constants} = egs_items_db:read(ItemID),
|
||||
#psu_striking_weapon_item{attack_sound=Sound, hitbox_a=HitboxA, hitbox_b=HitboxB,
|
||||
hitbox_c=HitboxC, hitbox_d=HitboxD, nb_targets=NbTargets, effect=Effect, model=Model} = Constants,
|
||||
<< Category:8, _:24 >> = << ItemID:32 >>,
|
||||
{SoundInt, SoundType} = case Sound of
|
||||
{default, Val} -> {Val, 0};
|
||||
{custom, Val} -> {Val, 8}
|
||||
end,
|
||||
egs_proto:packet_send(Client, << 16#01050300:32, 0:64, TargetGID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
TargetGID:32/little, TargetLID:32/little, ItemIndex:8, 1:8, Category:8, A:8, B:32/little,
|
||||
SoundInt:32/little, HitboxA:16, HitboxB:16, HitboxC:16, HitboxD:16, SoundType:4, NbTargets:4, 0:8, Effect:8, Model:8 >>);
|
||||
{ItemID, Variables} when element(1, Variables) =:= psu_trap_item_variables ->
|
||||
#psu_item{data=#psu_trap_item{effect=Effect, type=Type}} = egs_items_db:read(ItemID),
|
||||
<< Category:8, _:24 >> = << ItemID:32 >>,
|
||||
Bin = case Type of
|
||||
damage -> << Effect:8, 16#0c0a05:24, 16#20140500:32, 16#0001c800:32, 16#10000000:32 >>;
|
||||
damage_g -> << Effect:8, 16#2c0505:24, 16#0c000600:32, 16#00049001:32, 16#10000000:32 >>;
|
||||
trap -> << Effect:8, 16#0d0a05:24, 16#61140000:32, 16#0001c800:32, 16#10000000:32 >>;
|
||||
trap_g -> << Effect:8, 16#4d0505:24, 16#4d000000:32, 16#00049001:32, 16#10000000:32 >>;
|
||||
trap_ex -> << Effect:8, 16#490a05:24, 16#4500000f:32, 16#4b055802:32, 16#10000000:32 >>
|
||||
end,
|
||||
egs_proto:packet_send(Client, << 16#01050300:32, 0:64, TargetGID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
TargetGID:32/little, TargetLID:32/little, ItemIndex:8, 1:8, Category:8, A:8, B:32/little, Bin/binary >>);
|
||||
undefined ->
|
||||
%% @todo Shouldn't be needed later when NPCs are handled correctly.
|
||||
ignore
|
||||
end;
|
||||
|
||||
event({item_set_trap, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
|
||||
{ItemID, _Variables} = egs_users:item_nth(GID, ItemIndex),
|
||||
egs_users:item_qty_add(GID, ItemIndex, -1),
|
||||
<< Category:8, _:24 >> = << ItemID:32 >>,
|
||||
egs_proto:packet_send(Client, << 16#01050300:32, 0:64, TargetGID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
TargetGID:32/little, TargetLID:32/little, ItemIndex:8, 9:8, Category:8, A:8, B:32/little >>);
|
||||
|
||||
%% @todo A and B are unknown.
|
||||
%% @see item_equip
|
||||
event({item_unequip, ItemIndex, TargetGID, TargetLID, A, B}, Client=#egs_net{gid=GID}) ->
|
||||
Category = case ItemIndex of
|
||||
% units would be 8, traps would be 12
|
||||
19 -> 2; % armor
|
||||
Y when Y =:= 5; Y =:= 6; Y =:= 7 -> 0; % clothes
|
||||
_ -> 1 % weapons
|
||||
end,
|
||||
egs_proto:packet_send(Client, << 16#01050300:32, 0:64, GID:32/little, 0:64, 16#00011300:32, GID:32/little,
|
||||
0:64, TargetGID:32/little, TargetLID:32/little, ItemIndex, 2, Category, A, B:32/little >>);
|
||||
|
||||
%% @todo Just ignore the meseta price for now and send the player where he wanna be!
|
||||
event(lobby_transport_request, Client) ->
|
||||
egs_proto:send_0c08(Client);
|
||||
|
||||
event(lumilass_options_request, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
egs_proto:send_1a03(User, Client);
|
||||
|
||||
%% @todo Probably replenish the player HP when entering a non-mission area rather than when aborting the mission?
|
||||
event(mission_abort, Client=#egs_net{gid=GID}) ->
|
||||
egs_proto:send_1006(11, Client),
|
||||
{ok, User} = egs_users:read(GID),
|
||||
%% delete the mission
|
||||
if User#users.instancepid =:= undefined -> ignore;
|
||||
true -> psu_instance:stop(User#users.instancepid)
|
||||
end,
|
||||
%% full hp
|
||||
User2 = User#users{currenthp=User#users.maxhp, instancepid=undefined},
|
||||
egs_users:write(User2),
|
||||
%% map change
|
||||
if User2#users.areatype =:= mission ->
|
||||
PrevArea = User2#users.prev_area,
|
||||
event({area_change, element(1, PrevArea), element(2, PrevArea), element(3, PrevArea), User2#users.prev_entryid}, Client);
|
||||
true -> ignore
|
||||
end;
|
||||
|
||||
%% @todo Forward the mission start to other players of the same party, whatever their location is.
|
||||
event({mission_start, QuestID}, Client) ->
|
||||
io:format("~p: mission start ~b~n", [Client#egs_net.gid, QuestID]),
|
||||
egs_proto:send_1020(Client),
|
||||
egs_proto:send_1015(QuestID, Client),
|
||||
egs_proto:send_0c02(Client);
|
||||
|
||||
%% @doc Force the invite of an NPC character while inside a mission. Mostly used by story missions.
|
||||
%% Note that the NPC is often removed and reinvited between block/cutscenes.
|
||||
event({npc_force_invite, NPCid}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
%% Create NPC.
|
||||
io:format("~p: npc force invite ~p~n", [GID, NPCid]),
|
||||
TmpNPCUser = egs_npc_db:create(NPCid, User#users.level),
|
||||
%% Create and join party.
|
||||
case User#users.partypid of
|
||||
undefined ->
|
||||
{ok, PartyPid} = psu_party:start_link(GID);
|
||||
PartyPid ->
|
||||
ignore
|
||||
end,
|
||||
{ok, PartyPos} = psu_party:join(PartyPid, npc, TmpNPCUser#users.gid),
|
||||
#users{instancepid=InstancePid, area=Area, entryid=EntryID, pos=Pos} = User,
|
||||
NPCUser = TmpNPCUser#users{lid=PartyPos, partypid=PartyPid, instancepid=InstancePid, areatype=mission, area=Area, entryid=EntryID, pos=Pos},
|
||||
egs_users:write(NPCUser),
|
||||
egs_users:write(User#users{partypid=PartyPid}),
|
||||
%% Send stuff.
|
||||
egs_proto:send_010d(NPCUser, Client),
|
||||
egs_proto:send_0201(NPCUser, Client),
|
||||
egs_proto:send_0215(0, Client),
|
||||
egs_proto:send_0a04(NPCUser#users.gid, Client),
|
||||
egs_proto:send_022c(0, 16#12, Client),
|
||||
egs_proto:send_1004(npc_mission, NPCUser, PartyPos, Client),
|
||||
egs_proto:send_100f(NPCUser#users.npcid, PartyPos, Client),
|
||||
egs_proto:send_1601(PartyPos, Client);
|
||||
|
||||
%% @todo Also at the end send a 101a (NPC:16, PartyPos:16, ffffffff). Not sure about PartyPos.
|
||||
event({npc_invite, NPCid}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
%% Create NPC.
|
||||
io:format("~p: invited npcid ~b~n", [GID, NPCid]),
|
||||
TmpNPCUser = egs_npc_db:create(NPCid, User#users.level),
|
||||
%% Create and join party.
|
||||
case User#users.partypid of
|
||||
undefined ->
|
||||
{ok, PartyPid} = psu_party:start_link(GID),
|
||||
egs_proto:send_022c(0, 16#12, Client);
|
||||
PartyPid ->
|
||||
ignore
|
||||
end,
|
||||
{ok, PartyPos} = psu_party:join(PartyPid, npc, TmpNPCUser#users.gid),
|
||||
NPCUser = TmpNPCUser#users{lid=PartyPos, partypid=PartyPid},
|
||||
egs_users:write(NPCUser),
|
||||
egs_users:write(User#users{partypid=PartyPid}),
|
||||
%% Send stuff.
|
||||
egs_proto:send_1004(npc_invite, NPCUser, PartyPos, Client),
|
||||
egs_proto:send_101a(NPCid, PartyPos, Client);
|
||||
|
||||
%% @todo Should be 0115(money) 010a03(confirm sale).
|
||||
event({npc_shop_buy, ShopItemIndex, QuantityOrColor}, Client=#egs_net{gid=GID}) ->
|
||||
ShopID = egs_users:shop_get(GID),
|
||||
ItemID = egs_shops_db:nth(ShopID, ShopItemIndex + 1),
|
||||
io:format("~p: npc shop ~p buy itemid ~8.16.0b quantity/color+1 ~p~n", [GID, ShopID, ItemID, QuantityOrColor]),
|
||||
#psu_item{name=Name, rarity=Rarity, buy_price=BuyPrice, sell_price=SellPrice, data=Constants} = egs_items_db:read(ItemID),
|
||||
{Quantity, Variables} = case element(1, Constants) of
|
||||
psu_clothing_item ->
|
||||
if QuantityOrColor >= 1, QuantityOrColor =< 10 ->
|
||||
{1, #psu_clothing_item_variables{color=QuantityOrColor - 1}}
|
||||
end;
|
||||
psu_consumable_item ->
|
||||
{QuantityOrColor, #psu_consumable_item_variables{quantity=QuantityOrColor}};
|
||||
psu_parts_item ->
|
||||
{1, #psu_parts_item_variables{}};
|
||||
psu_special_item ->
|
||||
{1, #psu_special_item_variables{}};
|
||||
psu_striking_weapon_item ->
|
||||
#psu_striking_weapon_item{pp=PP, shop_element=Element} = Constants,
|
||||
{1, #psu_striking_weapon_item_variables{current_pp=PP, max_pp=PP, element=Element}};
|
||||
psu_trap_item ->
|
||||
{QuantityOrColor, #psu_trap_item_variables{quantity=QuantityOrColor}}
|
||||
end,
|
||||
egs_users:money_add(GID, -1 * BuyPrice * Quantity),
|
||||
ItemUUID = egs_users:item_add(GID, ItemID, Variables),
|
||||
{ok, User} = egs_users:read(GID),
|
||||
egs_proto:send_0115(User, Client), %% @todo This one is apparently broadcast to everyone in the same zone.
|
||||
%% @todo Following command isn't done 100% properly.
|
||||
UCS2Name = << << X:8, 0:8 >> || X <- Name >>,
|
||||
NamePadding = 8 * (46 - byte_size(UCS2Name)),
|
||||
<< Category:8, _:24 >> = << ItemID:32 >>,
|
||||
RarityInt = Rarity - 1,
|
||||
egs_proto:packet_send(Client, << 16#010a0300:32, 0:64, GID:32/little, 0:64, 16#00011300:32, GID:32/little, 0:64,
|
||||
GID:32/little, 0:32, 2:16/little, 0:16, (egs_proto:build_item_variables(ItemID, ItemUUID, Variables))/binary,
|
||||
UCS2Name/binary, 0:NamePadding, RarityInt:8, Category:8, SellPrice:32/little, (egs_proto:build_item_constants(Constants))/binary >>);
|
||||
|
||||
%% @todo Currently send the normal items shop for all shops, differentiate.
|
||||
event({npc_shop_enter, ShopID}, Client=#egs_net{gid=GID}) ->
|
||||
io:format("~p: npc shop enter ~p~n", [GID, ShopID]),
|
||||
egs_users:shop_enter(GID, ShopID),
|
||||
egs_proto:send_010a(egs_shops_db:read(ShopID), Client);
|
||||
|
||||
event({npc_shop_leave, ShopID}, Client=#egs_net{gid=GID}) ->
|
||||
io:format("~p: npc shop leave ~p~n", [GID, ShopID]),
|
||||
egs_users:shop_leave(GID),
|
||||
egs_proto:packet_send(Client, << 16#010a0300:32, 0:64, GID:32/little, 0:64, 16#00011300:32,
|
||||
GID:32/little, 0:64, GID:32/little, 0:32 >>);
|
||||
|
||||
%% @todo Should be 0115(money) 010a03(confirm sale).
|
||||
event({npc_shop_sell, InventoryItemIndex, Quantity}, Client) ->
|
||||
io:format("~p: npc shop sell itemindex ~p quantity ~p~n", [Client#egs_net.gid, InventoryItemIndex, Quantity]);
|
||||
|
||||
%% @todo First 1a02 value should be non-0.
|
||||
%% @todo Could the 2nd 1a02 parameter simply be the shop type or something?
|
||||
%% @todo Although the values replied should be right, they seem mostly ignored by the client.
|
||||
event({npc_shop_request, ShopID}, Client) ->
|
||||
io:format("~p: npc shop request ~p~n", [Client#egs_net.gid, ShopID]),
|
||||
case ShopID of
|
||||
80 -> egs_proto:send_1a02(17, 17, 3, 9, Client); %% lumilass
|
||||
90 -> egs_proto:send_1a02(5, 1, 4, 5, Client); %% parum weapon grinding
|
||||
91 -> egs_proto:send_1a02(5, 5, 4, 7, Client); %% tenora weapon grinding
|
||||
92 -> egs_proto:send_1a02(5, 8, 4, 0, Client); %% yohmei weapon grinding
|
||||
93 -> egs_proto:send_1a02(5, 18, 4, 0, Client); %% kubara weapon grinding
|
||||
_ -> egs_proto:send_1a02(0, 1, 0, 0, Client)
|
||||
end;
|
||||
|
||||
%% @todo Not sure what are those hardcoded values.
|
||||
event({object_boss_gate_activate, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 0, Client),
|
||||
egs_proto:send_1215(2, 16#7008, Client),
|
||||
%% @todo Following sent after the warp?
|
||||
egs_proto:send_1213(37, 0, Client),
|
||||
%% @todo Why resend this?
|
||||
egs_proto:send_1213(ObjectID, 0, Client);
|
||||
|
||||
event({object_boss_gate_enter, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
%% @todo Do we need to send something back here?
|
||||
event({object_boss_gate_leave, _ObjectID}, _Client) ->
|
||||
ignore;
|
||||
|
||||
event({object_box_destroy, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 3, Client);
|
||||
|
||||
%% @todo Second send_1211 argument should be User#users.lid. Fix when it's correctly handled.
|
||||
event({object_chair_sit, ObjectTargetID}, Client) ->
|
||||
egs_proto:send_1211(ObjectTargetID, 0, 8, 0, Client);
|
||||
|
||||
%% @todo Second send_1211 argument should be User#users.lid. Fix when it's correctly handled.
|
||||
event({object_chair_stand, ObjectTargetID}, Client) ->
|
||||
egs_proto:send_1211(ObjectTargetID, 0, 8, 2, Client);
|
||||
|
||||
event({object_crystal_activate, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
%% @doc Server-side event.
|
||||
event({object_event_trigger, BlockID, EventID}, Client) ->
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client);
|
||||
|
||||
event({object_goggle_target_activate, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client),
|
||||
egs_proto:send_1213(ObjectID, 8, Client);
|
||||
|
||||
%% @todo Make NPC characters heal too.
|
||||
event({object_healing_pad_tick, [_PartyPos]}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
if User#users.currenthp =:= User#users.maxhp -> ignore;
|
||||
true ->
|
||||
NewHP = User#users.currenthp + User#users.maxhp div 10,
|
||||
NewHP2 = if NewHP > User#users.maxhp -> User#users.maxhp; true -> NewHP end,
|
||||
User2 = User#users{currenthp=NewHP2},
|
||||
egs_users:write(User2),
|
||||
egs_proto:send_0117(User2, Client),
|
||||
egs_proto:send_0111(User2, 4, Client)
|
||||
end;
|
||||
|
||||
event({object_key_console_enable, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, [EventID|_]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client),
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
event({object_key_console_init, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, [_, EventID, _]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client);
|
||||
|
||||
event({object_key_console_open_gate, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, [_, _, EventID]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client),
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
%% @todo Now that it's separate from object_key_console_enable, handle it better than that, don't need a list of events.
|
||||
event({object_key_enable, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, [EventID|_]} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client),
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
%% @todo Some switch objects apparently work differently, like the light switch in Mines in MAG'.
|
||||
event({object_switch_off, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 1, Client),
|
||||
egs_proto:send_1213(ObjectID, 0, Client);
|
||||
|
||||
event({object_switch_on, ObjectID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
{BlockID, EventID} = psu_instance:std_event(User#users.instancepid, element(2, User#users.area), ObjectID),
|
||||
egs_proto:send_1205(EventID, BlockID, 0, Client),
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
event({object_vehicle_boost_enable, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 1, Client);
|
||||
|
||||
event({object_vehicle_boost_respawn, ObjectID}, Client) ->
|
||||
egs_proto:send_1213(ObjectID, 0, Client);
|
||||
|
||||
%% @todo Second send_1211 argument should be User#users.lid. Fix when it's correctly handled.
|
||||
event({object_warp_take, BlockID, ListNb, ObjectNb}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
Pos = psu_instance:warp_event(User#users.instancepid, element(2, User#users.area), BlockID, ListNb, ObjectNb),
|
||||
NewUser = User#users{pos=Pos},
|
||||
egs_users:write(NewUser),
|
||||
egs_proto:send_0503(User#users.pos, Client),
|
||||
egs_proto:send_1211(16#ffffffff, 0, 14, 0, Client);
|
||||
|
||||
%% @todo Don't send_0204 if the player is removed from the party while in the lobby I guess.
|
||||
event({party_remove_member, PartyPos}, Client=#egs_net{gid=GID}) ->
|
||||
io:format("~p: party remove member ~b~n", [GID, PartyPos]),
|
||||
{ok, DestUser} = egs_users:read(GID),
|
||||
{ok, RemovedGID} = psu_party:get_member(DestUser#users.partypid, PartyPos),
|
||||
psu_party:remove_member(DestUser#users.partypid, PartyPos),
|
||||
{ok, RemovedUser} = egs_users:read(RemovedGID),
|
||||
case RemovedUser#users.type of
|
||||
npc -> egs_users:delete(RemovedGID);
|
||||
_ -> ignore
|
||||
end,
|
||||
egs_proto:send_1006(8, PartyPos, Client),
|
||||
egs_proto:send_0204(RemovedUser, Client),
|
||||
egs_proto:send_0215(0, Client);
|
||||
|
||||
event({player_options_change, Options}, #egs_net{gid=GID, slot=Slot}) ->
|
||||
Folder = egs_accounts:get_folder(GID),
|
||||
file:write_file(io_lib:format("save/~s/~b-character.options", [Folder, Slot]), Options);
|
||||
|
||||
%% @todo If the player has a scape, use it! Otherwise red screen.
|
||||
%% @todo Right now we force revive with a dummy HP value.
|
||||
event(player_death, Client=#egs_net{gid=GID}) ->
|
||||
% @todo send_0115(GID, 16#ffffffff, LV=1, EXP=idk, Money=1000), % apparently sent everytime you die...
|
||||
%% use scape:
|
||||
NewHP = 10,
|
||||
{ok, User} = egs_users:read(GID),
|
||||
User2 = User#users{currenthp=NewHP},
|
||||
egs_users:write(User2),
|
||||
egs_proto:send_0117(User2, Client),
|
||||
egs_proto:send_1022(User2, Client);
|
||||
%% red screen with return to lobby choice:
|
||||
%~ egs_proto:send_0111(User2, 3, 1, Client);
|
||||
|
||||
%% @todo Refill the player's HP to maximum, remove SEs etc.
|
||||
event(player_death_return_to_lobby, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
PrevArea = User#users.prev_area,
|
||||
event({area_change, element(1, PrevArea), element(2, PrevArea), element(3, PrevArea), User#users.prev_entryid}, Client);
|
||||
|
||||
event(player_type_availability_request, Client) ->
|
||||
egs_proto:send_1a07(Client);
|
||||
|
||||
event(character_type_capabilities_request, Client) ->
|
||||
egs_proto:send_0113(Client);
|
||||
|
||||
event(ppcube_request, Client) ->
|
||||
egs_proto:send_1a04(Client);
|
||||
|
||||
event(unicube_request, Client) ->
|
||||
egs_proto:send_021e(egs_universes:all(), Client);
|
||||
|
||||
%% @todo When selecting 'Your room', don't load a default room that's not yours.
|
||||
event({unicube_select, cancel, _EntryID}, _Client) ->
|
||||
ignore;
|
||||
event({unicube_select, Selection, EntryID}, Client=#egs_net{gid=GID}) ->
|
||||
{ok, User} = egs_users:read(GID),
|
||||
case Selection of
|
||||
16#ffffffff ->
|
||||
UniID = egs_universes:myroomid(),
|
||||
User2 = User#users{uni=UniID, area={1120000, 0, 100}, entryid=0};
|
||||
_ ->
|
||||
UniID = Selection,
|
||||
User2 = User#users{uni=UniID, entryid=EntryID}
|
||||
end,
|
||||
egs_proto:send_0230(Client),
|
||||
%% 0220
|
||||
case User#users.partypid of
|
||||
undefined -> ignore;
|
||||
PartyPid ->
|
||||
%% @todo Replace stop by leave when leaving stops the party correctly when nobody's there anymore.
|
||||
%~ psu_party:leave(User#users.partypid, User#users.gid)
|
||||
{ok, NPCList} = psu_party:get_npc(PartyPid),
|
||||
[egs_users:delete(NPCGID) || {_Spot, NPCGID} <- NPCList],
|
||||
psu_party:stop(PartyPid)
|
||||
end,
|
||||
egs_users:write(User2),
|
||||
egs_universes:leave(User#users.uni),
|
||||
egs_universes:enter(UniID),
|
||||
char_load(User2, Client).
|
||||
|
||||
%% Internal.
|
||||
|
||||
%% @doc Trigger many events.
|
||||
events(Events, Client) ->
|
||||
[event(Event, Client) || Event <- Events],
|
||||
ok.
|
||||
|
||||
%% @doc Load and send the character information to the client.
|
||||
char_load(User, Client) ->
|
||||
egs_net:account_character(User#users.char, Client),
|
||||
%% 0246
|
||||
egs_proto:send_0a0a(User#users.inventory, Client),
|
||||
egs_proto:send_1006(5, 0, Client), %% @todo The 0 here is PartyPos, save it in User.
|
||||
egs_proto:send_1005(User, Client),
|
||||
egs_proto:send_1006(12, Client),
|
||||
egs_proto:send_0210(Client),
|
||||
egs_proto:send_0222(User#users.uni, Client),
|
||||
egs_net:comm_own_card(User#users.char, Client),
|
||||
egs_proto:send_1501(Client),
|
||||
egs_proto:send_1512(Client),
|
||||
%% 0303
|
||||
egs_proto:send_1602(Client),
|
||||
egs_proto:send_021b(Client).
|
||||
|
||||
%% @todo Don't change the NPC info unless you are the leader!
|
||||
npc_load(_Leader, [], _Client) ->
|
||||
ok;
|
||||
npc_load(Leader, [{PartyPos, NPCGID}|NPCList], Client) ->
|
||||
{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),
|
||||
egs_proto:send_010d(NPCUser, Client),
|
||||
egs_proto:send_0201(NPCUser, Client),
|
||||
egs_proto:send_0215(0, Client),
|
||||
egs_proto:send_0a04(NPCUser#users.gid, Client),
|
||||
egs_proto:send_1004(npc_mission, NPCUser, PartyPos, Client),
|
||||
egs_proto:send_100f(NPCUser#users.npcid, PartyPos, Client),
|
||||
egs_proto:send_1601(PartyPos, Client),
|
||||
egs_proto:send_1016(PartyPos, Client),
|
||||
npc_load(Leader, NPCList, Client).
|
62
apps/egs/src/egs_game_protocol.erl
Normal file
62
apps/egs/src/egs_game_protocol.erl
Normal 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.
|
@ -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.
|
||||
|
81
apps/egs/src/egs_login.erl
Normal file
81
apps/egs/src/egs_login.erl
Normal file
@ -0,0 +1,81 @@
|
||||
%% @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.
|
||||
%%
|
||||
%% 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_login).
|
||||
-export([info/2, cast/3, event/2]).
|
||||
|
||||
-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 Reject version < 2.0009.2.
|
||||
%% @todo Reject wrong platforms too.
|
||||
%% @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, Client) ->
|
||||
{ServerIP, ServerPort} = egs_conf:read(game_server),
|
||||
egs_net:system_game_server_response(ServerIP, ServerPort, Client),
|
||||
egs_net:terminate(Client),
|
||||
closed;
|
||||
|
||||
%% @doc Authenticate the user by pattern matching its saved state against the key received.
|
||||
%% If the user is authenticated, send him the character flags list.
|
||||
event({system_key_auth, AuthGID, AuthKey}, Client) ->
|
||||
egs_accounts:key_auth(AuthGID, AuthKey),
|
||||
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, Username, Password}, Client) ->
|
||||
{ok, AuthGID} = egs_accounts:login_auth(Username, Password),
|
||||
{ok, AuthKey} = egs_accounts:key_auth_init(AuthGID),
|
||||
io:format("auth success for ~s ~s~n", [Username, Password]),
|
||||
egs_net:system_key_auth_info(AuthGID, AuthKey, Client);
|
||||
|
||||
%% @doc MOTD request handler. Page number starts at 0.
|
||||
%% @todo Currently ignore the language and send the same MOTD file to everyone.
|
||||
event({system_motd_request, Page, _Language}, Client) ->
|
||||
{ok, MOTD} = file:read_file("priv/login/motd.txt"),
|
||||
egs_net:system_motd_response(MOTD, Page, Client).
|
@ -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,25 +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, on_exit/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}.
|
||||
|
||||
%% @spec on_exit(Pid) -> ok
|
||||
%% @doc Nothing to do for the login server.
|
||||
on_exit(_Pid) ->
|
||||
ok.
|
||||
|
||||
%% @doc Initialize the game state and start receiving messages.
|
||||
init(Socket) ->
|
||||
TmpGID = 16#ff000000 + mnesia:dirty_update_counter(counters, tmpgid, 1),
|
||||
State = #state{socket=Socket, gid=TmpGID},
|
||||
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).
|
@ -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.
|
||||
|
||||
@ -61,14 +62,15 @@ handle_call(all, _From, State) ->
|
||||
|
||||
%% @todo Handle stats, experience, based on level.
|
||||
handle_call({create, NPCid, BaseLevel}, _From, State) ->
|
||||
NPCGID = 16#ff000000 + mnesia:dirty_update_counter(counters, tmpgid, 1),
|
||||
NPCGID = egs_accounts:tmp_gid(),
|
||||
#npc{name=Name, race=Race, gender=Gender, class=Class, level_diff=LevelDiff, appearance=Appearance} = proplists:get_value(NPCid, ?NPC),
|
||||
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 = #egs_user_model{id=NPCGID, character=Character, areatype=lobby, area={psu_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
726
apps/egs/src/egs_proto.erl
Normal 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).
|
71
apps/egs/src/egs_quests.erl
Normal file
71
apps/egs/src/egs_quests.erl
Normal file
@ -0,0 +1,71 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Quest handler.
|
||||
%%
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_quests).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/2, stop/1, zone_pid/2]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
-record(state, {zones}).
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link(UniID, QuestID) -> {ok,Pid::pid()}
|
||||
start_link(UniID, QuestID) ->
|
||||
gen_server:start_link(?MODULE, [UniID, QuestID], []).
|
||||
|
||||
%% @spec stop(Pid) -> stopped
|
||||
stop(Pid) ->
|
||||
gen_server:call(Pid, stop).
|
||||
|
||||
zone_pid(Pid, ZoneID) ->
|
||||
gen_server:call(Pid, {zone_pid, ZoneID}).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([UniID, QuestID]) ->
|
||||
Zones = egs_quests_db:quest_zones(QuestID),
|
||||
ZonesPids = lists:map(fun({ZoneID, ZoneData}) ->
|
||||
{ok, Pid} = egs_zones_sup:start_zone(UniID, QuestID, ZoneID, ZoneData),
|
||||
{ZoneID, Pid}
|
||||
end, Zones),
|
||||
{ok, #state{zones=ZonesPids}}.
|
||||
|
||||
handle_call({zone_pid, ZoneID}, _From, State) ->
|
||||
{_, Pid} = lists:keyfind(ZoneID, 1, State#state.zones),
|
||||
{reply, Pid, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, 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}.
|
@ -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/1, zone/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, {quests_bin=[], zones_bin=[]}).
|
||||
-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.
|
||||
|
||||
%% 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()}
|
||||
@ -37,13 +38,22 @@ start_link() ->
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @spec quest(QuestID) -> binary()
|
||||
quest(QuestID) ->
|
||||
gen_server:call(?SERVER, {quest, QuestID}).
|
||||
%% @spec quest_nbl(QuestID) -> binary()
|
||||
quest_nbl(QuestID) ->
|
||||
gen_server:call(?SERVER, {quest_nbl, QuestID}).
|
||||
|
||||
%% @spec zone(QuestID, ZoneID) -> binary()
|
||||
zone(QuestID, ZoneID) ->
|
||||
gen_server:call(?SERVER, {zone, QuestID, ZoneID}).
|
||||
%% @spec zone_nbl(QuestID, ZoneID) -> binary()
|
||||
zone_nbl(QuestID, ZoneID) ->
|
||||
gen_server:call(?SERVER, {zone_nbl, QuestID, ZoneID}).
|
||||
|
||||
area_type(QuestID, ZoneID) ->
|
||||
gen_server:call(?SERVER, {area_type, QuestID, ZoneID}).
|
||||
|
||||
quest_zones(QuestID) ->
|
||||
gen_server:call(?SERVER, {quest_zones, QuestID}).
|
||||
|
||||
set(QuestID, ZoneID, SetID) ->
|
||||
gen_server:call(?SERVER, {set, QuestID, ZoneID, SetID}).
|
||||
|
||||
%% @spec reload() -> ok
|
||||
reload() ->
|
||||
@ -54,13 +64,94 @@ reload() ->
|
||||
init([]) ->
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({quest, QuestID}, _From, State=#state{quests_bin=Cache}) ->
|
||||
{Quest, Cache2} = get_quest(QuestID, Cache),
|
||||
{reply, Quest, State#state{quests_bin=Cache2}};
|
||||
%% @doc Return a quest information either from the cache or from the configuration file,
|
||||
%% in which case it gets added to the cache for subsequent attempts.
|
||||
handle_call({quest_nbl, QuestID}, _From, State=#state{quests=Cache, quests_bin=BinCache}) ->
|
||||
case proplists:get_value(QuestID, BinCache) of
|
||||
undefined ->
|
||||
Dir = io_lib:format("priv/quests/~b/", [QuestID]),
|
||||
ConfFilename = Dir ++ "quest.conf",
|
||||
{ok, Settings} = file:consult(ConfFilename),
|
||||
{QuestXnrData, QuestXnrPtrs} = egs_files:load_quest_xnr(Settings),
|
||||
UnitTitleBinFiles = load_unit_title_bin_files(Dir, Settings),
|
||||
Files = [{data, "quest.xnr", QuestXnrData, QuestXnrPtrs}],
|
||||
Files2 = Files ++ case UnitTitleBinFiles of
|
||||
ignore -> [];
|
||||
_Any ->
|
||||
TablePos = egs_files:nbl_padded_size(byte_size(QuestXnrData)),
|
||||
TextSize = lists:sum([egs_files:nbl_padded_size(byte_size(D)) || {data, _F, D, _P} <- UnitTitleBinFiles]),
|
||||
TablePos2 = TablePos + TextSize,
|
||||
{UnitTitleTableRelData, UnitTitleTableRelPtrs} = egs_files:load_unit_title_table_rel(ConfFilename, TablePos2),
|
||||
UnitTitleBinFiles ++ [{data, "unit_title_table.rel", UnitTitleTableRelData, UnitTitleTableRelPtrs}]
|
||||
end,
|
||||
QuestNbl = egs_files:nbl_pack([{files, Files2}]),
|
||||
{reply, QuestNbl, State#state{quests=[{QuestID, Settings}|Cache], quests_bin=[{QuestID, QuestNbl}|BinCache]}};
|
||||
QuestNbl ->
|
||||
{reply, QuestNbl, State}
|
||||
end;
|
||||
|
||||
handle_call({zone, QuestID, ZoneID}, _From, State=#state{zones_bin=Cache}) ->
|
||||
{Zone, Cache2} = get_zone(QuestID, ZoneID, Cache),
|
||||
{reply, Zone, State#state{zones_bin=Cache2}};
|
||||
%% @doc Return a zone information either from the cache or from the configuration files.
|
||||
%% @todo FilePos, text.bin, other sets, enemies.
|
||||
handle_call({zone_nbl, QuestID, ZoneID}, _From, State=#state{quests=QuestsCache, zones_bin=BinCache}) ->
|
||||
case proplists:get_value({QuestID, ZoneID}, BinCache) of
|
||||
undefined ->
|
||||
Dir = io_lib:format("priv/quests/~b/", [QuestID]),
|
||||
ZoneDir = Dir ++ io_lib:format("zone-~b/", [ZoneID]),
|
||||
QuestSettings = proplists:get_value(QuestID, QuestsCache),
|
||||
Zones = proplists:get_value(zones, QuestSettings),
|
||||
Zone = proplists:get_value(ZoneID, Zones),
|
||||
AreaID = proplists:get_value(areaid, Zone),
|
||||
Maps = proplists:get_value(maps, Zone),
|
||||
FilePos = 0, %% @todo
|
||||
{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 = 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"),
|
||||
ZoneNbl = egs_files:nbl_pack([{files, [
|
||||
{data, "set_r0.rel", Set0, SetPtrs},
|
||||
{data, "script.bin", ScriptBin3, []},
|
||||
{data, "text.bin", TextBin, []}
|
||||
]}]),
|
||||
{reply, ZoneNbl, State#state{zones_bin=[{{QuestID, ZoneID}, ZoneNbl}|BinCache]}};
|
||||
ZoneNbl ->
|
||||
{reply, ZoneNbl, State}
|
||||
end;
|
||||
|
||||
handle_call({area_type, QuestID, ZoneID}, _From, State=#state{quests=QuestsCache}) ->
|
||||
{_, Quest} = lists:keyfind(QuestID, 1, QuestsCache),
|
||||
{_, Zones} = lists:keyfind(zones, 1, Quest),
|
||||
{_, Zone} = lists:keyfind(ZoneID, 1, Zones),
|
||||
{_, AreaID} = lists:keyfind(areaid, 1, Zone),
|
||||
AreaType = case AreaID of
|
||||
0 -> lobby;
|
||||
2 -> lobby;
|
||||
3 -> lobby;
|
||||
4 -> lobby;
|
||||
5 -> lobby;
|
||||
22 -> myroom;
|
||||
_Any -> mission
|
||||
end,
|
||||
{reply, AreaType, State};
|
||||
|
||||
handle_call({quest_zones, QuestID}, _From, State=#state{quests=QuestsCache}) ->
|
||||
{_, Quest} = lists:keyfind(QuestID, 1, QuestsCache),
|
||||
{_, Zones} = lists:keyfind(zones, 1, Quest),
|
||||
{reply, Zones, State};
|
||||
|
||||
%% @todo The set file is loaded both here and in zone_nbl. Thinking about it zone_nbl should call this function.
|
||||
%% @todo Same for quest_nbl loading quest files and binaries, there should be a function for the file itself called only when needed.
|
||||
handle_call({set, QuestID, ZoneID, SetID}, _From, State=#state{sets=SetsCache}) ->
|
||||
case proplists:get_value({QuestID, ZoneID, SetID}, SetsCache) of
|
||||
undefined ->
|
||||
SetFilename = io_lib:format("priv/quests/~b/zone-~b/set_r~b.conf", [QuestID, ZoneID, SetID]),
|
||||
{ok, Set} = file:consult(SetFilename),
|
||||
{reply, Set, State#state{sets=[{{QuestID, ZoneID, SetID}, Set}|SetsCache]}};
|
||||
CachedSet ->
|
||||
{reply, CachedSet, State}
|
||||
end;
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
@ -85,65 +176,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
|
||||
%% Internal.
|
||||
|
||||
%% @doc Return a quest information either from the cache or from the configuration file,
|
||||
%% in which case it gets added to the cache for subsequent attempts.
|
||||
get_quest(QuestID, Cache) ->
|
||||
case proplists:get_value(QuestID, Cache) of
|
||||
undefined ->
|
||||
Dir = io_lib:format("priv/quests/~b/", [QuestID]),
|
||||
ConfFilename = Dir ++ "quest.conf",
|
||||
{QuestXnrData, QuestXnrPtrs} = egs_files:load_quest_xnr(ConfFilename),
|
||||
UnitTitleBinFiles = load_unit_title_bin_files(Dir, ConfFilename),
|
||||
Files = [{data, "quest.xnr", QuestXnrData, QuestXnrPtrs}],
|
||||
Files2 = Files ++ case UnitTitleBinFiles of
|
||||
ignore -> [];
|
||||
_Any ->
|
||||
TablePos = egs_files:nbl_padded_size(byte_size(QuestXnrData)),
|
||||
TextSize = lists:sum([egs_files:nbl_padded_size(byte_size(D)) || {data, _F, D, _P} <- UnitTitleBinFiles]),
|
||||
TablePos2 = TablePos + TextSize,
|
||||
{UnitTitleTableRelData, UnitTitleTableRelPtrs} = egs_files:load_unit_title_table_rel(ConfFilename, TablePos2),
|
||||
UnitTitleBinFiles ++ [{data, "unit_title_table.rel", UnitTitleTableRelData, UnitTitleTableRelPtrs}]
|
||||
end,
|
||||
QuestNbl = egs_files:nbl_pack([{files, Files2}]),
|
||||
Cache2 = [{QuestID, QuestNbl}|Cache],
|
||||
{QuestNbl, Cache2};
|
||||
QuestNbl ->
|
||||
{QuestNbl, Cache}
|
||||
end.
|
||||
|
||||
%% @doc Return a zone information either from the cache or from the configuration files.
|
||||
%% @todo FilePos, text.bin, other sets, enemies.
|
||||
get_zone(QuestID, ZoneID, Cache) ->
|
||||
case proplists:get_value({QuestID, ZoneID}, Cache) of
|
||||
undefined ->
|
||||
Dir = io_lib:format("priv/quests/~b/", [QuestID]),
|
||||
ZoneDir = Dir ++ io_lib:format("zone-~b/", [ZoneID]),
|
||||
{ok, QuestSettings} = file:consult(Dir ++ "quest.conf"),
|
||||
Zones = proplists:get_value(zones, QuestSettings),
|
||||
Zone = proplists:get_value(ZoneID, Zones),
|
||||
AreaID = proplists:get_value(areaid, Zone),
|
||||
Maps = proplists:get_value(maps, Zone),
|
||||
FilePos = 0, %% @todo
|
||||
{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),
|
||||
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"),
|
||||
ZoneNbl = egs_files:nbl_pack([{files, [
|
||||
{data, "set_r0.rel", Set0, SetPtrs},
|
||||
{data, "script.bin", ScriptBin3, []},
|
||||
{data, "text.bin", TextBin, []}
|
||||
]}]),
|
||||
Cache2 = [{{QuestID, ZoneID}, ZoneNbl}|Cache],
|
||||
{ZoneNbl, Cache2};
|
||||
ZoneNbl ->
|
||||
{ZoneNbl, Cache}
|
||||
end.
|
||||
|
||||
load_unit_title_bin_files(Dir, ConfFilename) ->
|
||||
{ok, Settings} = file:consult(ConfFilename),
|
||||
load_unit_title_bin_files(Dir, Settings) ->
|
||||
case proplists:get_value(notitles, Settings) of
|
||||
true -> ignore;
|
||||
_Any ->
|
46
apps/egs/src/egs_quests_sup.erl
Normal file
46
apps/egs/src/egs_quests_sup.erl
Normal file
@ -0,0 +1,46 @@
|
||||
%% @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.
|
||||
%%
|
||||
%% 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_quests_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0, start_quest/2]). %% API.
|
||||
-export([init/1]). %% supervisor.
|
||||
|
||||
-define(SUPERVISOR, ?MODULE).
|
||||
|
||||
%% API.
|
||||
|
||||
-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([]) ->
|
||||
{ok, {{simple_one_for_one, 0, 1}, [{egs_quests,
|
||||
{egs_quests, start_link, []}, temporary, brutal_kill,
|
||||
worker, [egs_quests]}]}}.
|
@ -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.
|
1076
apps/egs/src/egs_script_lexer.erl
Normal file
1076
apps/egs/src/egs_script_lexer.erl
Normal file
File diff suppressed because it is too large
Load Diff
@ -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.
|
1331
apps/egs/src/egs_script_parser.erl
Normal file
1331
apps/egs/src/egs_script_parser.erl
Normal file
File diff suppressed because it is too large
Load Diff
@ -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.
|
@ -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.
|
||||
|
@ -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
54
apps/egs/src/egs_sup.erl
Normal 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]}.
|
@ -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,16 +19,22 @@
|
||||
|
||||
-module(egs_universes).
|
||||
-behavior(gen_server).
|
||||
-export([start_link/0, stop/0, all/0, defaultid/0, enter/1, leave/1, myroomid/0, read/1, reload/0]). %% API.
|
||||
|
||||
-export([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.
|
||||
|
||||
%% 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).
|
||||
|
||||
%% Lobbies: permanent quests to start with the universe.
|
||||
-define(LOBBIES, [1100000]).
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link() -> {ok,Pid::pid()}
|
||||
@ -65,20 +71,31 @@ myroomid() ->
|
||||
read(UniID) ->
|
||||
gen_server:call(?SERVER, {read, UniID}).
|
||||
|
||||
lobby_pid(UniID, QuestID) ->
|
||||
gen_server:call(?SERVER, {lobby_pid, UniID, QuestID}).
|
||||
|
||||
%% @spec reload() -> ok
|
||||
reload() ->
|
||||
gen_server:cast(?SERVER, reload).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
%% @doc Create the unis, then load all the permanent quests nbl files, then create processes for all the needed quests.
|
||||
init([]) ->
|
||||
{ok, [create_myroom()|create_unis()]}.
|
||||
State = #state{unis=[create_myroom()|create_unis()]},
|
||||
[egs_quests_db:quest_nbl(QuestID) || QuestID <- ?LOBBIES],
|
||||
Lobbies = lists:flatten([init_lobbies(UniID) || {UniID, {Type, _Name, _NbPlayers, _MaxPlayers}} <- State#state.unis, Type =:= universe]),
|
||||
{ok, State#state{lobbies=Lobbies}}.
|
||||
|
||||
handle_call(all, _From, State) ->
|
||||
{reply, State, State};
|
||||
{reply, State#state.unis, State};
|
||||
|
||||
handle_call({read, UniID}, _From, State) ->
|
||||
{reply, proplists:get_value(UniID, State), State};
|
||||
{reply, proplists:get_value(UniID, State#state.unis), State};
|
||||
|
||||
handle_call({lobby_pid, UniID, QuestID}, _From, State) ->
|
||||
{_, Pid} = lists:keyfind({UniID, QuestID}, 1, State#state.lobbies),
|
||||
{reply, Pid, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
@ -87,21 +104,21 @@ handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast({enter, UniID}, State) ->
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State),
|
||||
State2 = proplists:delete(UniID, State),
|
||||
State3 = [{UniID, {Type, Name, NbPlayers + 1, MaxPlayers}}|State2],
|
||||
State4 = lists:keysort(1, State3),
|
||||
{noreply, State4};
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State#state.unis),
|
||||
Unis = proplists:delete(UniID, State#state.unis),
|
||||
Unis2 = [{UniID, {Type, Name, NbPlayers + 1, MaxPlayers}}|Unis],
|
||||
Unis3 = lists:keysort(1, Unis2),
|
||||
{noreply, State#state{unis=Unis3}};
|
||||
|
||||
handle_cast({leave, UniID}, State) ->
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State),
|
||||
State2 = proplists:delete(UniID, State),
|
||||
State3 = [{UniID, {Type, Name, NbPlayers - 1, MaxPlayers}}|State2],
|
||||
State4 = lists:keysort(1, State3),
|
||||
{noreply, State4};
|
||||
{Type, Name, NbPlayers, MaxPlayers} = proplists:get_value(UniID, State#state.unis),
|
||||
Unis = proplists:delete(UniID, State#state.unis),
|
||||
Unis2 = [{UniID, {Type, Name, NbPlayers - 1, MaxPlayers}}|Unis],
|
||||
Unis3 = lists:keysort(1, Unis2),
|
||||
{noreply, State#state{unis=Unis3}};
|
||||
|
||||
handle_cast(reload, _State) ->
|
||||
{noreply, [create_myroom()|create_unis()]};
|
||||
{noreply, #state{unis=[create_myroom()|create_unis()]}};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
@ -131,3 +148,10 @@ create_unis([], _UniID, Acc) ->
|
||||
lists:reverse(Acc);
|
||||
create_unis([Name|Tail], UniID, Acc) ->
|
||||
create_unis(Tail, UniID + 2, [{UniID, {universe, Name, 0, 1000}}|Acc]).
|
||||
|
||||
%% @doc Start lobbies for the given universe.
|
||||
init_lobbies(UniID) ->
|
||||
lists:map(fun(QuestID) ->
|
||||
{ok, Pid} = egs_quests_sup:start_quest(UniID, QuestID),
|
||||
{{UniID, QuestID}, Pid}
|
||||
end, ?LOBBIES).
|
239
apps/egs/src/egs_users.erl
Normal file
239
apps/egs/src/egs_users.erl
Normal file
@ -0,0 +1,239 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010-2011 Loïc Hoguin.
|
||||
%% @doc Users 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_users).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0, stop/0, broadcast/2, broadcast_all/1, find_by_pid/1, set_zone/3]). %% API.
|
||||
-export([read/1, select/1, write/1, delete/1, item_nth/2, item_add/3, item_qty_add/3,
|
||||
shop_enter/2, shop_leave/1, shop_get/1, money_add/2]). %% Deprecated API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-include("include/records.hrl").
|
||||
|
||||
-record(state, {
|
||||
users = [] :: list({egs:gid(), #users{}})
|
||||
}).
|
||||
|
||||
%% API.
|
||||
|
||||
-spec start_link() -> {ok, Pid::pid()}.
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
-spec stop() -> stopped.
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
broadcast(Message, PlayersGID) ->
|
||||
gen_server:cast(?SERVER, {broadcast, Message, PlayersGID}).
|
||||
|
||||
broadcast_all(Message) ->
|
||||
gen_server:cast(?SERVER, {broadcast_all, Message}).
|
||||
|
||||
find_by_pid(Pid) ->
|
||||
gen_server:call(?SERVER, {find_by_pid, Pid}).
|
||||
|
||||
set_zone(GID, ZonePid, LID) ->
|
||||
gen_server:call(?SERVER, {set_zone, GID, ZonePid, LID}).
|
||||
|
||||
%% Deprecated API.
|
||||
|
||||
%% @spec read(ID) -> {ok, User} | {error, badarg}
|
||||
read(ID) ->
|
||||
gen_server:call(?SERVER, {read, ID}).
|
||||
|
||||
select(GIDsList) ->
|
||||
gen_server:call(?SERVER, {select, GIDsList}).
|
||||
|
||||
%% @spec write(User) -> ok
|
||||
write(User) ->
|
||||
gen_server:call(?SERVER, {write, User}).
|
||||
|
||||
%% @spec delete(GID) -> ok
|
||||
delete(GID) ->
|
||||
gen_server:call(?SERVER, {delete, GID}).
|
||||
|
||||
item_nth(GID, ItemIndex) ->
|
||||
gen_server:call(?SERVER, {item_nth, GID, ItemIndex}).
|
||||
|
||||
item_add(GID, ItemID, Variables) ->
|
||||
gen_server:call(?SERVER, {item_add, GID, ItemID, Variables}).
|
||||
|
||||
%% @todo Consumable items.
|
||||
item_qty_add(GID, ItemIndex, QuantityDiff) ->
|
||||
gen_server:call(?SERVER, {item_qty_add, GID, ItemIndex, QuantityDiff}).
|
||||
|
||||
shop_enter(GID, ShopID) ->
|
||||
gen_server:call(?SERVER, {shop_enter, GID, ShopID}).
|
||||
|
||||
shop_leave(GID) ->
|
||||
gen_server:call(?SERVER, {shop_leave, GID}).
|
||||
|
||||
shop_get(GID) ->
|
||||
gen_server:call(?SERVER, {shop_get, GID}).
|
||||
|
||||
money_add(GID, MoneyDiff) ->
|
||||
gen_server:call(?SERVER, {money_add, GID, MoneyDiff}).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({find_by_pid, Pid}, _From, 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#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#state.users),
|
||||
{reply, {ok, User}, State};
|
||||
|
||||
handle_call({select, UsersGID}, _From, State) ->
|
||||
Users = [begin
|
||||
{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#state.users),
|
||||
{reply, ok, State#state{users=[{User#users.gid, User}|Users2]}};
|
||||
|
||||
handle_call({delete, GID}, _From, State) ->
|
||||
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#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#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, 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, 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, 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, User#users.inventory, {ItemID, #psu_trap_item_variables{quantity=Quantity3}})
|
||||
end;
|
||||
_ ->
|
||||
New = true,
|
||||
if length(User#users.inventory) < 60 ->
|
||||
User#users.inventory ++ [{ItemID, Variables}]
|
||||
end
|
||||
end,
|
||||
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(Inventory), State2}
|
||||
end;
|
||||
|
||||
handle_call({item_qty_add, GID, ItemIndex, QuantityDiff}, _From, State) ->
|
||||
{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 ->
|
||||
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},
|
||||
string:substr(User#users.inventory, 1, ItemIndex) ++ [{ItemID, Variables2}] ++ string:substr(User#users.inventory, ItemIndex + 2)
|
||||
end
|
||||
end,
|
||||
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#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#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#state.users),
|
||||
{reply, User#users.shopid, State};
|
||||
|
||||
handle_call({money_add, GID, MoneyDiff}, _From, State) ->
|
||||
{GID, User} = lists:keyfind(GID, 1, State#state.users),
|
||||
Money = User#users.money + MoneyDiff,
|
||||
if Money >= 0 ->
|
||||
Users2 = lists:delete({GID, User}, State#state.users),
|
||||
{reply, ok, [{GID, User#users{money=Money}}|Users2]}
|
||||
end;
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast({broadcast, Message, PlayersGID}, State) ->
|
||||
[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#state.users],
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
182
apps/egs/src/egs_zones.erl
Normal file
182
apps/egs/src/egs_zones.erl
Normal file
@ -0,0 +1,182 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Zone handler.
|
||||
%%
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_zones).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/4, stop/1, setid/1, enter/2, leave/2, get_all_players/2, broadcast/3]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
-record(state, {
|
||||
setid = 0 :: integer(),
|
||||
objects = [] :: list(),
|
||||
indexes = [] :: list(),
|
||||
targets = [] :: list(),
|
||||
players = [] :: list(),
|
||||
freelids = [] :: list()
|
||||
}).
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link(UniID, QuestID, ZoneID, ZoneData) -> {ok,Pid::pid()}
|
||||
start_link(UniID, QuestID, ZoneID, ZoneData) ->
|
||||
gen_server:start_link(?MODULE, [UniID, QuestID, ZoneID, ZoneData], []).
|
||||
|
||||
%% @spec stop(Pid) -> stopped
|
||||
stop(Pid) ->
|
||||
gen_server:call(Pid, stop).
|
||||
|
||||
setid(Pid) ->
|
||||
gen_server:call(Pid, setid).
|
||||
|
||||
enter(Pid, GID) ->
|
||||
gen_server:call(Pid, {enter, GID}).
|
||||
|
||||
leave(undefined, _GID) ->
|
||||
ok;
|
||||
leave(Pid, GID) ->
|
||||
gen_server:call(Pid, {leave, GID}).
|
||||
|
||||
get_all_players(Pid, ExcludeGID) ->
|
||||
gen_server:call(Pid, {get_all_players, ExcludeGID}).
|
||||
|
||||
broadcast(Pid, FromGID, Packet) ->
|
||||
gen_server:cast(Pid, {broadcast, FromGID, Packet}).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([UniID, QuestID, ZoneID, ZoneData]) ->
|
||||
SetID = rand_setid(proplists:get_value(sets, ZoneData, [100])),
|
||||
Set = egs_quests_db:set(QuestID, ZoneID, SetID),
|
||||
Objects = create_units(Set),
|
||||
{Indexes, Targets} = index_objects(Objects),
|
||||
FreeLIDs = lists:seq(0, 1023),
|
||||
{ok, #state{setid=SetID, objects=Objects, indexes=Indexes, targets=Targets, freelids=FreeLIDs}}.
|
||||
|
||||
handle_call(setid, _From, State) ->
|
||||
{reply, State#state.setid, State};
|
||||
|
||||
handle_call({enter, GID}, _From, State) ->
|
||||
[LID|FreeLIDs] = State#state.freelids,
|
||||
egs_users:set_zone(GID, self(), LID),
|
||||
{ok, Spawn} = egs_users:read(GID),
|
||||
egs_users:broadcast({egs, player_spawn, Spawn}, players_gid(State#state.players)),
|
||||
{reply, LID, State#state{players=[{GID, LID}|State#state.players], freelids=FreeLIDs}};
|
||||
|
||||
handle_call({leave, GID}, _From, State) ->
|
||||
{_, LID} = lists:keyfind(GID, 1, State#state.players),
|
||||
Players = lists:delete({GID, LID}, State#state.players),
|
||||
{ok, Spawn} = egs_users:read(GID),
|
||||
egs_users:broadcast({egs, player_unspawn, Spawn}, players_gid(Players)),
|
||||
{reply, ok, State#state{players=Players, freelids=[LID|State#state.freelids]}};
|
||||
|
||||
handle_call({get_all_players, ExcludeGID}, _From, State) ->
|
||||
{reply, lists:delete(ExcludeGID, players_gid(State#state.players)), State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast({broadcast, FromGID, Packet}, State) ->
|
||||
PlayersGID = lists:delete(FromGID, players_gid(State#state.players)),
|
||||
egs_users:broadcast({egs, cast, Packet}, PlayersGID),
|
||||
{noreply, State};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% Internal.
|
||||
|
||||
%% @doc Return a random setid from a list of chances per set.
|
||||
rand_setid(Sets) ->
|
||||
N = crypto:rand_uniform(1, lists:sum(Sets)),
|
||||
rand_setid(N, Sets, 0).
|
||||
rand_setid(N, [Set|_Tail], I) when N < Set ->
|
||||
I;
|
||||
rand_setid(N, [Set|Tail], I) ->
|
||||
rand_setid(N - Set, Tail, I + 1).
|
||||
|
||||
%% @doc Create the objects for all units in a set.
|
||||
create_units(Set) ->
|
||||
create_units(Set, 0, []).
|
||||
create_units([], _MapNb, Acc) ->
|
||||
lists:flatten(lists:reverse(Acc));
|
||||
create_units([{{map, _MapID}, Groups}|Tail], MapNb, Acc) ->
|
||||
MapObjects = create_groups(Groups, MapNb),
|
||||
create_units(Tail, MapNb + 1, [MapObjects|Acc]).
|
||||
|
||||
%% @doc Create the objects for all groups in a unit.
|
||||
create_groups(Groups, MapNb) ->
|
||||
create_groups(Groups, MapNb, 0, []).
|
||||
create_groups([], _MapNb, _GroupNb, Acc) ->
|
||||
lists:flatten(lists:reverse(Acc));
|
||||
create_groups([Objects|Tail], MapNb, GroupNb, Acc) ->
|
||||
GroupObjects = create_objects(Objects, MapNb, GroupNb),
|
||||
create_groups(Tail, MapNb, GroupNb + 1, [GroupObjects|Acc]).
|
||||
|
||||
%% @doc Create the given objects.
|
||||
create_objects(Objects, MapNb, GroupNb) ->
|
||||
create_objects(Objects, MapNb, GroupNb, 0, []).
|
||||
create_objects([], _MapNb, _GroupNb, _ObjectNb, Acc) ->
|
||||
lists:reverse(Acc);
|
||||
create_objects([{ObjType, ObjPos, ObjRot, ObjParams}|Tail], MapNb, GroupNb, ObjectNb, Acc) ->
|
||||
Object = create_object(ObjType, ObjPos, ObjRot, ObjParams),
|
||||
create_objects(Tail, MapNb, GroupNb, ObjectNb + 1, [{{MapNb, GroupNb, ObjectNb}, Object}|Acc]).
|
||||
|
||||
%% @doc Create the given object.
|
||||
create_object(Type, Pos, Rot, Params) ->
|
||||
M = list_to_existing_atom(lists:flatten(["egs_obj_", atom_to_list(Type)])),
|
||||
M:init(Pos, Rot, Params).
|
||||
|
||||
%% @doc Build a list of object indexes and targets based on the list of objects.
|
||||
index_objects(Objects) ->
|
||||
index_objects(Objects, 0, [], 1024, []).
|
||||
index_objects([], _Index, IndexesAcc, _Target, TargetsAcc) ->
|
||||
{lists:reverse(IndexesAcc), lists:reverse(TargetsAcc)};
|
||||
index_objects([{Key, Object}|Tail], Index, IndexesAcc, Target, TargetsAcc) ->
|
||||
M = element(1, Object),
|
||||
Attrs = M:module_info(attributes),
|
||||
{Index2, IndexesAcc2} = case lists:keyfind(is_indexed, 1, Attrs) of
|
||||
{_, [true]} -> {Index + 1, [{Index, Key}|IndexesAcc]};
|
||||
{_, [false]} -> {Index, IndexesAcc}
|
||||
end,
|
||||
{Target2, TargetsAcc2} = case lists:keyfind(is_target, 1, Attrs) of
|
||||
{_, [true]} -> {Target + 1, [{Target, Key}|TargetsAcc]};
|
||||
{_, [false]} -> {Target, TargetsAcc}
|
||||
end,
|
||||
index_objects(Tail, Index2, IndexesAcc2, Target2, TargetsAcc2).
|
||||
|
||||
%% @doc Return a list of GID from a list of {GID, LID}.
|
||||
players_gid(Players) ->
|
||||
players_gid(Players, []).
|
||||
players_gid([], Acc) ->
|
||||
Acc;
|
||||
players_gid([{GID, _LID}|Tail], Acc) ->
|
||||
players_gid(Tail, [GID|Acc]).
|
47
apps/egs/src/egs_zones_sup.erl
Normal file
47
apps/egs/src/egs_zones_sup.erl
Normal 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]}]}}.
|
33
apps/egs/src/objects/egs_obj_chair.erl
Normal file
33
apps/egs/src/objects/egs_obj_chair.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Chair object.
|
||||
%%
|
||||
%% 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_obj_chair).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_chair, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_chair{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_door.erl
Normal file
33
apps/egs/src/objects/egs_obj_door.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Door object.
|
||||
%%
|
||||
%% 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_obj_door).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_door, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_door{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_entrance.erl
Normal file
33
apps/egs/src/objects/egs_obj_entrance.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Entrance object.
|
||||
%%
|
||||
%% 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_obj_entrance).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_entrance, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_entrance{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_exit.erl
Normal file
33
apps/egs/src/objects/egs_obj_exit.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Exit object.
|
||||
%%
|
||||
%% 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_obj_exit).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_exit, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_exit{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_invisible_block.erl
Normal file
33
apps/egs/src/objects/egs_obj_invisible_block.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Invisible block object.
|
||||
%%
|
||||
%% 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_obj_invisible_block).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_invisible_block, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_invisible_block{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_label.erl
Normal file
33
apps/egs/src/objects/egs_obj_label.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Label object.
|
||||
%%
|
||||
%% 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_obj_label).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_label, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_label{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_npc.erl
Normal file
33
apps/egs/src/objects/egs_obj_npc.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc NPC object.
|
||||
%%
|
||||
%% 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_obj_npc).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_npc, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_npc{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_pp_cube.erl
Normal file
33
apps/egs/src/objects/egs_obj_pp_cube.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc PP cube object.
|
||||
%%
|
||||
%% 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_obj_pp_cube).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_pp_cube, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_pp_cube{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_sensor.erl
Normal file
33
apps/egs/src/objects/egs_obj_sensor.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Sensor object.
|
||||
%%
|
||||
%% 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_obj_sensor).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_sensor, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_sensor{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_static_model.erl
Normal file
33
apps/egs/src/objects/egs_obj_static_model.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Static model object.
|
||||
%%
|
||||
%% 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_obj_static_model).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(false).
|
||||
-is_target(false).
|
||||
|
||||
-record(egs_obj_static_model, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_static_model{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_type_counter.erl
Normal file
33
apps/egs/src/objects/egs_obj_type_counter.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Type counter object.
|
||||
%%
|
||||
%% 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_obj_type_counter).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_type_counter, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_type_counter{pos=Pos, rot=Rot}.
|
33
apps/egs/src/objects/egs_obj_uni_cube.erl
Normal file
33
apps/egs/src/objects/egs_obj_uni_cube.erl
Normal file
@ -0,0 +1,33 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2011 Loïc Hoguin.
|
||||
%% @doc Uni cube object.
|
||||
%%
|
||||
%% 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_obj_uni_cube).
|
||||
-export([init/3]).
|
||||
|
||||
%% @todo Real values.
|
||||
-is_indexed(true).
|
||||
-is_target(true).
|
||||
|
||||
-record(egs_obj_uni_cube, {
|
||||
pos :: {X :: float(), Y :: float(), Z :: float()},
|
||||
rot :: {Rx :: float(), Ry :: float(), Zy :: float()}
|
||||
}).
|
||||
|
||||
init(Pos, Rot, Params) ->
|
||||
#egs_obj_uni_cube{pos=Pos, rot=Rot}.
|
96
apps/egs/src/psu/psu_characters.erl
Normal file
96
apps/egs/src/psu/psu_characters.erl
Normal 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 >>.
|
@ -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.
|
4
apps/egs_net/rebar.config
Normal file
4
apps/egs_net/rebar.config
Normal file
@ -0,0 +1,4 @@
|
||||
{deps, [
|
||||
{erlson, ".*", {git, "https://github.com/alavrik/erlson.git", "HEAD"}}
|
||||
]}.
|
||||
{plugins, [erlson_rebar_plugin]}.
|
12
apps/egs_net/src/egs_net.app.src
Normal file
12
apps/egs_net/src/egs_net.app.src
Normal file
@ -0,0 +1,12 @@
|
||||
%%-*- mode: erlang -*-
|
||||
{application, egs_net, [
|
||||
{description, "EGS network layer."},
|
||||
{vsn, "0.1.0"},
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib,
|
||||
cowboy
|
||||
]}
|
||||
]}.
|
1576
apps/egs_net/src/egs_net.erl
Normal file
1576
apps/egs_net/src/egs_net.erl
Normal file
File diff suppressed because it is too large
Load Diff
BIN
apps/egs_patch/priv/files/DATA/1ffd0db3e0b54048caff394e9c09eda8
Normal file
BIN
apps/egs_patch/priv/files/DATA/1ffd0db3e0b54048caff394e9c09eda8
Normal file
Binary file not shown.
BIN
apps/egs_patch/priv/files/DATA/bb04cc8e1727288bd2a336d60040eff1
Normal file
BIN
apps/egs_patch/priv/files/DATA/bb04cc8e1727288bd2a336d60040eff1
Normal file
Binary file not shown.
27
apps/egs_patch/priv/patch.conf
Normal file
27
apps/egs_patch/priv/patch.conf
Normal file
@ -0,0 +1,27 @@
|
||||
%% 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 List of folders where patched files are located.
|
||||
{folders, [root, "DATA"]}.
|
||||
|
||||
%% @doc Files in the root folder.
|
||||
{{folder, root}, [
|
||||
]}.
|
||||
|
||||
%% @doc Files in the DATA folder.
|
||||
{{folder, "DATA"}, [
|
||||
"1ffd0db3e0b54048caff394e9c09eda8", %% crc.bin
|
||||
"bb04cc8e1727288bd2a336d60040eff1" %% SmutFilter_J.bin
|
||||
]}.
|
3
apps/egs_patch/rebar.config
Normal file
3
apps/egs_patch/rebar.config
Normal file
@ -0,0 +1,3 @@
|
||||
{deps, [
|
||||
{cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "HEAD"}}
|
||||
]}.
|
14
apps/egs_patch/src/egs_patch.app.src
Normal file
14
apps/egs_patch/src/egs_patch.app.src
Normal 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, []}
|
||||
]}.
|
47
apps/egs_patch/src/egs_patch_app.erl
Normal file
47
apps/egs_patch/src/egs_patch_app.erl
Normal 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).
|
175
apps/egs_patch/src/egs_patch_files_db.erl
Normal file
175
apps/egs_patch/src/egs_patch_files_db.erl
Normal file
@ -0,0 +1,175 @@
|
||||
%% @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.
|
||||
%%
|
||||
%% 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_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}).
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link() -> {ok,Pid::pid()}
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%% @spec stop() -> stopped
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @spec list() -> binary()
|
||||
list() ->
|
||||
gen_server:call(?SERVER, list).
|
||||
|
||||
%% @spec check(FileNumber, CRC, Size) -> ok | invalid
|
||||
check(FileNumber, CRC, Size) ->
|
||||
gen_server:call(?SERVER, {check, FileNumber, CRC, Size}).
|
||||
|
||||
%% @spec get_size(FileNumber) -> Size
|
||||
get_size(FileNumber) ->
|
||||
gen_server:call(?SERVER, {get_size, FileNumber}).
|
||||
|
||||
%% @spec get_info(FileNumber) -> {CRC, Size, FilenameBin, FullFilename}
|
||||
get_info(FileNumber) ->
|
||||
gen_server:call(?SERVER, {get_info, FileNumber}).
|
||||
|
||||
%% @spec reload() -> ok
|
||||
reload() ->
|
||||
gen_server:cast(?SERVER, reload).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
{ok, build_state()}.
|
||||
|
||||
handle_call(list, _From, State=#state{list_bin=Bin}) ->
|
||||
{reply, Bin, State};
|
||||
|
||||
handle_call({check, FileNumber, CRC, Size}, _From, State=#state{files=Files}) ->
|
||||
File = proplists:get_value(FileNumber, Files),
|
||||
case File of
|
||||
#file{crc=CRC, size=Size} -> {reply, ok, State};
|
||||
_Any -> {reply, invalid, State}
|
||||
end;
|
||||
|
||||
handle_call({get_size, FileNumber}, _From, State=#state{files=Files}) ->
|
||||
File = proplists:get_value(FileNumber, Files),
|
||||
{reply, File#file.size, State};
|
||||
|
||||
handle_call({get_info, FileNumber}, _From, State=#state{files=Files}) ->
|
||||
{reply, proplists:get_value(FileNumber, Files), State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast(reload, _State) ->
|
||||
{noreply, build_state()};
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% Internal.
|
||||
|
||||
build_state() ->
|
||||
{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, [PrivDir, "/files/"]),
|
||||
#state{list_bin=ListBin, files=Files}.
|
||||
|
||||
%% The file number must start at 0.
|
||||
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, PatchDir, N, Acc, FilesAcc) ->
|
||||
Filenames = proplists:get_value({folder, Folder}, Terms),
|
||||
{BinFiles, Files, N2} = build_files_bin(Folder, Filenames, PatchDir, N),
|
||||
BinFiles2 = case Folder of
|
||||
root -> BinFiles;
|
||||
_Any ->
|
||||
FolderBin = list_to_binary(Folder),
|
||||
Padding = 8 * (64 - length(Folder)),
|
||||
<< 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, PatchDir, N2, [BinFiles2|Acc], [Files|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], PatchDir, N, Acc, FilesAcc) ->
|
||||
FullFilename = case Folder of
|
||||
root -> [PatchDir|Filename];
|
||||
_Any -> [PatchDir,Folder,"/"|Filename]
|
||||
end,
|
||||
Size = file_get_size(FullFilename),
|
||||
CRC = file_get_crc(FullFilename),
|
||||
FilenameBin = list_to_binary(Filename),
|
||||
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, 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),
|
||||
FileInfo#file_info.size.
|
||||
|
||||
file_get_crc(Filename) ->
|
||||
{ok, IoDevice} = file:open(Filename, [read, raw, binary]),
|
||||
case file:read(IoDevice, 524288) of
|
||||
eof -> 0;
|
||||
{ok, Data} ->
|
||||
CRC = erlang:crc32(Data),
|
||||
file_get_crc(IoDevice, CRC)
|
||||
end.
|
||||
file_get_crc(IoDevice, CRC) ->
|
||||
case file:read(IoDevice, 524288) of
|
||||
{ok, Data} ->
|
||||
CRC2 = erlang:crc32(CRC, Data),
|
||||
file_get_crc(IoDevice, CRC2);
|
||||
eof ->
|
||||
file:close(IoDevice),
|
||||
CRC
|
||||
end.
|
218
apps/egs_patch/src/egs_patch_protocol.erl
Normal file
218
apps/egs_patch/src/egs_patch_protocol.erl
Normal 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).
|
32
apps/egs_patch/src/egs_patch_sup.erl
Normal file
32
apps/egs_patch/src/egs_patch_sup.erl
Normal file
@ -0,0 +1,32 @@
|
||||
%% 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_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 = [{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
0
apps/egs_store/priv/.gitignore
vendored
Normal file
13
apps/egs_store/src/egs_store.app.src
Normal file
13
apps/egs_store/src/egs_store.app.src
Normal file
@ -0,0 +1,13 @@
|
||||
%%-*- mode: erlang -*-
|
||||
{application, egs_store, [
|
||||
{description, "EGS storage layer."},
|
||||
{vsn, "0.1.0"},
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib
|
||||
]},
|
||||
{mod, {egs_store_app, []}},
|
||||
{env, []}
|
||||
]}.
|
92
apps/egs_store/src/egs_store.erl
Normal file
92
apps/egs_store/src/egs_store.erl
Normal file
@ -0,0 +1,92 @@
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_store).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0,
|
||||
load_character/2, load_characters/2, save_character/3]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(ACCOUNTS_TBL, accounts_tbl).
|
||||
-define(ACCOUNTS_VSN, 1).
|
||||
-define(CHARACTERS_TBL, characters_tbl).
|
||||
-define(CHARACTERS_VSN, 1).
|
||||
|
||||
%% API.
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
load_character(GID, Slot) ->
|
||||
gen_server:call(?SERVER, {load_character, GID, Slot}).
|
||||
|
||||
load_characters(GID, Slots) ->
|
||||
gen_server:call(?SERVER, {load_characters, GID, Slots}).
|
||||
|
||||
save_character(GID, Slot, Data) ->
|
||||
gen_server:call(?SERVER, {save_character, GID, Slot, Data}).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
{ok, App} = application:get_application(),
|
||||
PrivDir = code:priv_dir(App),
|
||||
AccountsFile = PrivDir ++ "/accounts.tbl",
|
||||
CharactersFile = PrivDir ++ "/characters.tbl",
|
||||
{ok, ?ACCOUNTS_TBL} = dets:open_file(?ACCOUNTS_TBL,
|
||||
[{file, AccountsFile}]),
|
||||
io:format("accounts tbl:~n~p~n~n", [dets:info(?ACCOUNTS_TBL)]),
|
||||
{ok, ?CHARACTERS_TBL} = dets:open_file(?CHARACTERS_TBL,
|
||||
[{file, CharactersFile}]),
|
||||
io:format("characters tbl:~n~p~n~n", [dets:info(?CHARACTERS_TBL)]),
|
||||
{ok, undefined}.
|
||||
|
||||
handle_call({load_character, GID, Slot}, _From, State) ->
|
||||
case dets:lookup(?CHARACTERS_TBL, {GID, Slot}) of
|
||||
[{{GID, Slot}, Version, Data}] ->
|
||||
{reply, {ok, Version, Data}, State};
|
||||
[] ->
|
||||
{reply, {error, notfound}, State}
|
||||
end;
|
||||
handle_call({load_characters, GID, Slots}, _From, State) ->
|
||||
Characters = lists:map(fun(Slot) ->
|
||||
case dets:lookup(?CHARACTERS_TBL, {GID, Slot}) of
|
||||
[{{GID, Slot}, Version, Data}] ->
|
||||
{Version, Data};
|
||||
[] ->
|
||||
notfound
|
||||
end
|
||||
end, Slots),
|
||||
{reply, {ok, Characters}, State};
|
||||
handle_call({save_character, GID, Slot, Data}, _From, State) ->
|
||||
ok = dets:insert(?CHARACTERS_TBL, {{GID, Slot}, ?CHARACTERS_VSN, Data}),
|
||||
{reply, ok, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
31
apps/egs_store/src/egs_store_app.erl
Normal file
31
apps/egs_store/src/egs_store_app.erl
Normal file
@ -0,0 +1,31 @@
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_store_app).
|
||||
-behaviour(application).
|
||||
-export([start/2, stop/1]). %% API.
|
||||
|
||||
-type application_start_type()
|
||||
:: normal | {takeover, node()} | {failover, node()}.
|
||||
|
||||
%% API.
|
||||
|
||||
-spec start(application_start_type(), any()) -> {ok, pid()}.
|
||||
start(_Type, _StartArgs) ->
|
||||
egs_store_sup:start_link().
|
||||
|
||||
-spec stop(any()) -> ok.
|
||||
stop(_State) ->
|
||||
ok.
|
31
apps/egs_store/src/egs_store_sup.erl
Normal file
31
apps/egs_store/src/egs_store_sup.erl
Normal file
@ -0,0 +1,31 @@
|
||||
%% This file is part of EGS.
|
||||
%%
|
||||
%% EGS is free software: you can redistribute it and/or modify
|
||||
%% it under the terms of the GNU Affero General Public License as
|
||||
%% published by the Free Software Foundation, either version 3 of the
|
||||
%% License, or (at your option) any later version.
|
||||
%%
|
||||
%% EGS is distributed in the hope that it will be useful,
|
||||
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
%% GNU Affero General Public License for more details.
|
||||
%%
|
||||
%% You should have received a copy of the GNU Affero General Public License
|
||||
%% along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
-module(egs_store_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0]). %% API.
|
||||
-export([init/1]). %% Supervisor.
|
||||
|
||||
-spec start_link() -> {ok, pid()}.
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
-spec init([]) -> {ok, {{one_for_one, 10, 10}, [supervisor:child_spec(), ...]}}.
|
||||
init([]) ->
|
||||
{ok, {{one_for_one, 10, 10}, [
|
||||
{egs_store, {egs_store, start_link, []},
|
||||
permanent, 5000, worker, [egs_store]}
|
||||
]}}.
|
@ -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.
|
||||
@ -19,7 +19,7 @@
|
||||
along with EGS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "/opt/erlang/lib/erlang/usr/include/erl_nif.h"
|
||||
#include <erl_nif.h>
|
||||
|
||||
extern unsigned long prs_compress(unsigned char* source, unsigned char* dest, unsigned long size);
|
||||
|
||||
@ -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
11
apps/prs/src/prs.app.src
Normal file
@ -0,0 +1,11 @@
|
||||
%%-*- mode: erlang -*-
|
||||
{application, prs, [
|
||||
{description, "PRS compression library."},
|
||||
{vsn, "0.1.0"},
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib
|
||||
]}
|
||||
]}.
|
@ -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) ->
|
||||
exit(nif_library_not_loaded).
|
||||
erlang:nif_error(not_loaded).
|
1
c_src/.gitignore
vendored
1
c_src/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*.o
|
1
data/lobby/.gitignore
vendored
1
data/lobby/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
1
data/missions/.gitignore
vendored
1
data/missions/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
1
data/rooms/.gitignore
vendored
1
data/rooms/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
1
data/tutorial/.gitignore
vendored
1
data/tutorial/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
@ -265,6 +265,7 @@ OK A 5 1204 --S
|
||||
!* A 6 1211 --S (object event related)
|
||||
* A 5 1212 --S
|
||||
!* A 6 1213 --S object state on/off
|
||||
C 1 1214 --S season on/off
|
||||
* A 3 1215 --S (boss gate related)
|
||||
** C 3 1216 --- (airboard rally race start; channel 0)
|
||||
|
||||
@ -412,7 +413,7 @@ Casino/Temple
|
||||
C 3 1d17 --S (casino coin request related)
|
||||
C 3 1d18 --S (casino coin request related)
|
||||
|
||||
13 WIP 85 OK 336 Total
|
||||
13 WIP 85 OK 337 Total
|
||||
|
||||
! means the command has already been analyzed, at least partially
|
||||
* means the command is already found somewhere in the server but needs to be reviewed
|
||||
|
21
docs/protocol/commands/1214.txt
Normal file
21
docs/protocol/commands/1214.txt
Normal file
@ -0,0 +1,21 @@
|
||||
1214
|
||||
|
||||
Size:32
|
||||
Command:16 Channel:8 Garbage:8
|
||||
ffff:16 0:16
|
||||
0:32
|
||||
0:32
|
||||
0:32
|
||||
0:32
|
||||
DestTypeID:32
|
||||
DestGID:32
|
||||
0:32
|
||||
0:32
|
||||
QuestID:32
|
||||
ZoneID:32
|
||||
MapID:32
|
||||
IsSeasonal:32
|
||||
|
||||
Channel: 3
|
||||
DestTypeID: 00011300
|
||||
IsSeasonal: 0 otherwise
|
2
ebin/.gitignore
vendored
2
ebin/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*.beam
|
||||
egs.app
|
726
include/maps.hrl
726
include/maps.hrl
@ -1,726 +0,0 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010 Loïc Hoguin.
|
||||
%% @doc Quests, zones, maps and counters definitions.
|
||||
%%
|
||||
%% 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/>.
|
||||
|
||||
%% EGS maps settings.
|
||||
|
||||
-define(QUESTS, [
|
||||
% Unsafe Passage
|
||||
|
||||
{1000000, [{type, mission}, {file, "data/missions/unsafe-passage.1.c.quest.nbl"}, {start, [0, 1120, 0]}, {sets, 4}]},
|
||||
{1000001, [{type, mission}, {file, "data/missions/unsafe-passage.1.b.quest.nbl"}, {start, [0, 1120, 0]}, {sets, 4}]},
|
||||
{1000002, [{type, mission}, {file, "data/missions/unsafe-passage.1.a.quest.nbl"}, {start, [0, 1120, 0]}, {sets, 4}]},
|
||||
{1000003, [{type, mission}, {file, "data/missions/unsafe-passage.1.s.quest.nbl"}, {start, [0, 1120, 0]}, {sets, 4}]},
|
||||
{1000004, [{type, mission}, {file, "data/missions/unsafe-passage.1.s2.quest.nbl"}, {start, [0, 1120, 0]}, {sets, 4}]},
|
||||
|
||||
{1000010, [{type, mission}, {file, "data/missions/unsafe-passage.2.c.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 4}]},
|
||||
{1000011, [{type, mission}, {file, "data/missions/unsafe-passage.2.b.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 4}]},
|
||||
{1000012, [{type, mission}, {file, "data/missions/unsafe-passage.2.a.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 4}]},
|
||||
{1000013, [{type, mission}, {file, "data/missions/unsafe-passage.2.s.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 4}]},
|
||||
{1000014, [{type, mission}, {file, "data/missions/unsafe-passage.2.s2.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 4}]},
|
||||
|
||||
{1000020, [{type, mission}, {file, "data/missions/unsafe-passage.3.c.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000021, [{type, mission}, {file, "data/missions/unsafe-passage.3.b.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000022, [{type, mission}, {file, "data/missions/unsafe-passage.3.a.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000023, [{type, mission}, {file, "data/missions/unsafe-passage.3.s.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000024, [{type, mission}, {file, "data/missions/unsafe-passage.3.s2.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
|
||||
% Fight for Food
|
||||
|
||||
{1000100, [{type, mission}, {file, "data/missions/fight-for-food.1.c.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000101, [{type, mission}, {file, "data/missions/fight-for-food.1.b.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
%~ {1000102, [{type, mission}, {file, "data/missions/fight-for-food.1.a.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
%~ {1000103, [{type, mission}, {file, "data/missions/fight-for-food.1.s.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
{1000104, [{type, mission}, {file, "data/missions/fight-for-food.1.s2.quest.nbl"}, {start, [0, 1200, 0]}, {sets, 4}]},
|
||||
|
||||
{1000110, [{type, mission}, {file, "data/missions/fight-for-food.2.c.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
%~ {1000111, [{type, mission}, {file, "data/missions/fight-for-food.2.b.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
%~ {1000112, [{type, mission}, {file, "data/missions/fight-for-food.2.a.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
%~ {1000113, [{type, mission}, {file, "data/missions/fight-for-food.2.s.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
{1000114, [{type, mission}, {file, "data/missions/fight-for-food.2.s2.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
|
||||
{1000120, [{type, mission}, {file, "data/missions/fight-for-food.3.c.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
%~ {1000121, [{type, mission}, {file, "data/missions/fight-for-food.3.b.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
{1000122, [{type, mission}, {file, "data/missions/fight-for-food.3.a.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
%~ {1000123, [{type, mission}, {file, "data/missions/fight-for-food.3.s.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
{1000124, [{type, mission}, {file, "data/missions/fight-for-food.3.s2.quest.nbl"}, {start, [0, 1301, 0]}, {sets, 4}]},
|
||||
|
||||
% Dark Satellite
|
||||
|
||||
{1001000, [{type, mission}, {file, "data/missions/dark-satellite.1.c.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
{1001001, [{type, mission}, {file, "data/missions/dark-satellite.1.b.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
{1001002, [{type, mission}, {file, "data/missions/dark-satellite.1.a.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
%~ {1001003, [{type, mission}, {file, "data/missions/dark-satellite.1.s.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
{1001004, [{type, mission}, {file, "data/missions/dark-satellite.1.s2.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
{1001005, [{type, mission}, {file, "data/missions/dark-satellite.1.s3.quest.nbl"}, {start, [0, 101, 0]}, {sets, 4}]},
|
||||
|
||||
{1001010, [{type, mission}, {file, "data/missions/dark-satellite.2.c.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
{1001011, [{type, mission}, {file, "data/missions/dark-satellite.2.b.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
{1001012, [{type, mission}, {file, "data/missions/dark-satellite.2.a.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
{1001013, [{type, mission}, {file, "data/missions/dark-satellite.2.s.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
%~ {1001014, [{type, mission}, {file, "data/missions/dark-satellite.2.s2.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
%~ {1001015, [{type, mission}, {file, "data/missions/dark-satellite.2.s3.quest.nbl"}, {start, [0, 102, 0]}, {sets, 4}]},
|
||||
|
||||
{1001020, [{type, mission}, {file, "data/missions/dark-satellite.3.c.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
%~ {1001021, [{type, mission}, {file, "data/missions/dark-satellite.3.b.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
{1001022, [{type, mission}, {file, "data/missions/dark-satellite.3.a.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
%~ {1001023, [{type, mission}, {file, "data/missions/dark-satellite.3.s.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
{1001024, [{type, mission}, {file, "data/missions/dark-satellite.3.s2.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
{1001025, [{type, mission}, {file, "data/missions/dark-satellite.3.s3.quest.nbl"}, {start, [0, 103, 0]}, {sets, 4}]},
|
||||
|
||||
% Seed Awakening
|
||||
|
||||
%~ {1001100, [{type, mission}, {file, "data/missions/seed-awakening.1.c.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
{1001101, [{type, mission}, {file, "data/missions/seed-awakening.1.b.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
{1001102, [{type, mission}, {file, "data/missions/seed-awakening.1.a.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
{1001103, [{type, mission}, {file, "data/missions/seed-awakening.1.s.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
{1001104, [{type, mission}, {file, "data/missions/seed-awakening.1.s2.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
{1001105, [{type, mission}, {file, "data/missions/seed-awakening.1.s3.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
|
||||
{1001110, [{type, mission}, {file, "data/missions/seed-awakening.2.c.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
{1001111, [{type, mission}, {file, "data/missions/seed-awakening.2.b.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
{1001112, [{type, mission}, {file, "data/missions/seed-awakening.2.a.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
%~ {1001113, [{type, mission}, {file, "data/missions/seed-awakening.2.s.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
{1001114, [{type, mission}, {file, "data/missions/seed-awakening.2.s2.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
{1001115, [{type, mission}, {file, "data/missions/seed-awakening.2.s3.quest.nbl"}, {start, [0, 112, 0]}, {sets, 4}]},
|
||||
|
||||
{1001120, [{type, mission}, {file, "data/missions/seed-awakening.3.c.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
%~ {1001121, [{type, mission}, {file, "data/missions/seed-awakening.3.b.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
%~ {1001122, [{type, mission}, {file, "data/missions/seed-awakening.3.a.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
%~ {1001123, [{type, mission}, {file, "data/missions/seed-awakening.3.s.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
{1001124, [{type, mission}, {file, "data/missions/seed-awakening.3.s2.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
{1001125, [{type, mission}, {file, "data/missions/seed-awakening.3.s3.quest.nbl"}, {start, [0, 113, 0]}, {sets, 4}]},
|
||||
|
||||
% True Darkness
|
||||
|
||||
%~ {1001200, [{type, mission}, {file, "data/missions/true-darkness.1.c.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
%~ {1001201, [{type, mission}, {file, "data/missions/true-darkness.1.b.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
%~ {1001202, [{type, mission}, {file, "data/missions/true-darkness.1.a.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
%~ {1001203, [{type, mission}, {file, "data/missions/true-darkness.1.s.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
%~ {1001204, [{type, mission}, {file, "data/missions/true-darkness.1.s2.quest.nbl"}, {start, [0, 130, 0]}, {sets, 4}]},
|
||||
|
||||
{1001210, [{type, mission}, {file, "data/missions/true-darkness.2.c.quest.nbl"}, {start, [0, 131, 0]}, {sets, 4}]},
|
||||
%~ {1001211, [{type, mission}, {file, "data/missions/true-darkness.2.b.quest.nbl"}, {start, [0, 131, 0]}, {sets, 4}]},
|
||||
{1001212, [{type, mission}, {file, "data/missions/true-darkness.2.a.quest.nbl"}, {start, [0, 131, 0]}, {sets, 4}]},
|
||||
{1001213, [{type, mission}, {file, "data/missions/true-darkness.2.s.quest.nbl"}, {start, [0, 131, 0]}, {sets, 4}]},
|
||||
%~ {1001214, [{type, mission}, {file, "data/missions/true-darkness.2.s2.quest.nbl"}, {start, [0, 131, 0]}, {sets, 4}]},
|
||||
|
||||
{1001220, [{type, mission}, {file, "data/missions/true-darkness.3.c.quest.nbl"}, {start, [0, 802, 0]}, {sets, 4}]},
|
||||
{1001221, [{type, mission}, {file, "data/missions/true-darkness.3.b.quest.nbl"}, {start, [0, 802, 0]}, {sets, 4}]},
|
||||
{1001222, [{type, mission}, {file, "data/missions/true-darkness.3.a.quest.nbl"}, {start, [0, 802, 0]}, {sets, 4}]},
|
||||
%~ {1001223, [{type, mission}, {file, "data/missions/true-darkness.3.s.quest.nbl"}, {start, [0, 802, 0]}, {sets, 4}]},
|
||||
{1001224, [{type, mission}, {file, "data/missions/true-darkness.3.s2.quest.nbl"}, {start, [0, 802, 0]}, {sets, 4}]},
|
||||
|
||||
% The Black Nest
|
||||
|
||||
{1003000, [{type, mission}, {file, "data/missions/black-nest.1.c.quest.nbl"}, {start, [0, 6301, 0]}, {sets, 4}]},
|
||||
{1003001, [{type, mission}, {file, "data/missions/black-nest.1.b.quest.nbl"}, {start, [0, 6301, 0]}, {sets, 4}]},
|
||||
{1003002, [{type, mission}, {file, "data/missions/black-nest.1.a.quest.nbl"}, {start, [0, 6301, 0]}, {sets, 4}]},
|
||||
{1003003, [{type, mission}, {file, "data/missions/black-nest.1.s.quest.nbl"}, {start, [0, 6301, 0]}, {sets, 4}]},
|
||||
{1003004, [{type, mission}, {file, "data/missions/black-nest.1.s2.quest.nbl"}, {start, [0, 6301, 0]}, {sets, 4}]},
|
||||
|
||||
{1003010, [{type, mission}, {file, "data/missions/black-nest.2.c.quest.nbl"}, {start, [0, 6303, 0]}, {sets, 4}]},
|
||||
%~ {1003011, [{type, mission}, {file, "data/missions/black-nest.2.b.quest.nbl"}, {start, [0, 6303, 0]}, {sets, 4}]},
|
||||
%~ {1003012, [{type, mission}, {file, "data/missions/black-nest.2.a.quest.nbl"}, {start, [0, 6303, 0]}, {sets, 4}]},
|
||||
%~ {1003013, [{type, mission}, {file, "data/missions/black-nest.2.s.quest.nbl"}, {start, [0, 6303, 0]}, {sets, 4}]},
|
||||
{1003014, [{type, mission}, {file, "data/missions/black-nest.2.s2.quest.nbl"}, {start, [0, 6303, 0]}, {sets, 4}]},
|
||||
|
||||
{1003020, [{type, mission}, {file, "data/missions/black-nest.3.c.quest.nbl"}, {start, [0, 6803, 0]}, {sets, 4}]},
|
||||
{1003021, [{type, mission}, {file, "data/missions/black-nest.3.b.quest.nbl"}, {start, [0, 6803, 0]}, {sets, 4}]},
|
||||
%~ {1003022, [{type, mission}, {file, "data/missions/black-nest.3.a.quest.nbl"}, {start, [0, 6803, 0]}, {sets, 4}]},
|
||||
{1003023, [{type, mission}, {file, "data/missions/black-nest.3.s.quest.nbl"}, {start, [0, 6803, 0]}, {sets, 4}]},
|
||||
%~ {1003024, [{type, mission}, {file, "data/missions/black-nest.3.s2.quest.nbl"}, {start, [0, 6803, 0]}, {sets, 4}]},
|
||||
|
||||
% The Dark God
|
||||
|
||||
{1003100, [{type, mission}, {file, "data/missions/dark-god.1.c.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
%~ {1003101, [{type, mission}, {file, "data/missions/dark-god.1.b.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003102, [{type, mission}, {file, "data/missions/dark-god.1.a.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003103, [{type, mission}, {file, "data/missions/dark-god.1.s.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003104, [{type, mission}, {file, "data/missions/dark-god.1.s2.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
|
||||
{1003110, [{type, mission}, {file, "data/missions/dark-god.2.c.quest.nbl"}, {start, [0, 6304, 0]}, {sets, 4}]},
|
||||
{1003111, [{type, mission}, {file, "data/missions/dark-god.2.b.quest.nbl"}, {start, [0, 6304, 0]}, {sets, 4}]},
|
||||
{1003112, [{type, mission}, {file, "data/missions/dark-god.2.a.quest.nbl"}, {start, [0, 6304, 0]}, {sets, 4}]},
|
||||
{1003113, [{type, mission}, {file, "data/missions/dark-god.2.s.quest.nbl"}, {start, [0, 6304, 0]}, {sets, 4}]},
|
||||
{1003114, [{type, mission}, {file, "data/missions/dark-god.2.s2.quest.nbl"}, {start, [0, 6304, 0]}, {sets, 4}]},
|
||||
|
||||
{1003120, [{type, mission}, {file, "data/missions/dark-god.3.c.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003121, [{type, mission}, {file, "data/missions/dark-god.3.b.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003122, [{type, mission}, {file, "data/missions/dark-god.3.a.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003123, [{type, mission}, {file, "data/missions/dark-god.3.s.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
{1003124, [{type, mission}, {file, "data/missions/dark-god.3.s2.quest.nbl"}, {start, [0, 6302, 0]}, {sets, 4}]},
|
||||
|
||||
% Phantom Ruins (Linear Line counter)
|
||||
|
||||
%~ {1060300, [{type, mission}, {file, "data/missions/phantom-ruins.c.quest.nbl"}, {start, [0, 8002, 0]}, {sets, 3}]},
|
||||
{1060301, [{type, mission}, {file, "data/missions/phantom-ruins.b.quest.nbl"}, {start, [0, 8002, 0]}, {sets, 3}]},
|
||||
%~ {1060302, [{type, mission}, {file, "data/missions/phantom-ruins.a.quest.nbl"}, {start, [0, 8002, 0]}, {sets, 3}]},
|
||||
{1060303, [{type, mission}, {file, "data/missions/phantom-ruins.s.quest.nbl"}, {start, [0, 8002, 0]}, {sets, 3}]},
|
||||
|
||||
% Photon Eraser Return
|
||||
|
||||
%~ {1070080, [{type, mission}, {file, "data/missions/photon-eraser-return.quest.nbl"}, {start, [0, 300, 0]}, {sets, 1}]},
|
||||
|
||||
% Dark Crystal Seeker
|
||||
|
||||
{1070742, [{type, mission}, {file, "data/missions/dark-crystal-seeker.quest.nbl"}, {start, [0, 1002, 0]}, {sets, 1}]},
|
||||
|
||||
% MAG'
|
||||
|
||||
{1072100, [{type, mission}, {file, "data/missions/mag-prime.c.quest.nbl"}, {start, [1, 5000, 0]}, {sets, 2}]},
|
||||
{1072101, [{type, mission}, {file, "data/missions/mag-prime.b.quest.nbl"}, {start, [1, 5000, 0]}, {sets, 2}]},
|
||||
{1072102, [{type, mission}, {file, "data/missions/mag-prime.a.quest.nbl"}, {start, [1, 5000, 0]}, {sets, 2}]},
|
||||
{1072103, [{type, mission}, {file, "data/missions/mag-prime.s.quest.nbl"}, {start, [1, 5000, 0]}, {sets, 2}]},
|
||||
{1072104, [{type, mission}, {file, "data/missions/mag-prime.s2.quest.nbl"}, {start, [1, 5000, 0]}, {sets, 2}]},
|
||||
|
||||
% Gifts from Beyond
|
||||
|
||||
{1072300, [{type, mission}, {file, "data/missions/gifts-from-beyond-plus.quest.nbl"}, {start, [0, 300, 0]}, {sets, 1}]},
|
||||
|
||||
% Airboard Rally
|
||||
|
||||
{1090700, [{type, mission}, {file, "data/missions/airboard-rally.quest.nbl"}, {start, [0, 800, 0]}, {sets, 1}]},
|
||||
|
||||
% Planetary lobbies
|
||||
|
||||
{1100000, [{type, lobby}, {file, nofile}]},
|
||||
{1101000, [{type, lobby}, {file, nofile}]},
|
||||
{1102000, [{type, lobby}, {file, nofile}]},
|
||||
{1103000, [{type, lobby}, {file, nofile}]},
|
||||
|
||||
{1104000, [{type, spaceport}, {file, nofile}]},
|
||||
|
||||
% Tutorial
|
||||
|
||||
{1106000, [{type, lobby}, {file, "data/tutorial/lobby.quest.nbl"}]},
|
||||
|
||||
% SEED-Form Purge
|
||||
|
||||
{1113000, [{type, mission}, {file, "data/tutorial/seed-form-purge.hyuga.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 1}]},
|
||||
{1113001, [{type, mission}, {file, "data/tutorial/seed-form-purge.maya.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 1}]},
|
||||
{1113002, [{type, mission}, {file, "data/tutorial/seed-form-purge.lou.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 1}]},
|
||||
{1113003, [{type, mission}, {file, "data/tutorial/seed-form-purge.leo.quest.nbl"}, {start, [0, 1121, 0]}, {sets, 1}]},
|
||||
|
||||
% My room
|
||||
|
||||
{1120000, [{type, myroom}, {file, "data/rooms/test.quest.nbl"}]},
|
||||
|
||||
% Story Episode 2
|
||||
|
||||
{1131010, [{type, mission}, {file, "data/missions/ep2ch1.c.quest.nbl"}, {start, [0, 0, 0]}, {sets, 1}]},
|
||||
|
||||
%% Boss Tests.
|
||||
{90120, [{type, mission}, {file, "data/missions/boss/lv1/alteraz/quest_ae.nbl"}, {start, [0, 100, 0]}, {sets, 1}]},
|
||||
{90130, [{type, mission}, {file, "data/missions/boss/lv1/rolei/quest_ae.nbl"}, {start, [0, 100, 0]}, {sets, 1}]},
|
||||
{90140, [{type, mission}, {file, "data/missions/boss/lv1/motherbrain/quest_ae.nbl"}, {start, [0, 100, 0]}, {sets, 1}]},
|
||||
{90150, [{type, mission}, {file, "data/missions/boss/lv1/falz1/quest_ae.nbl"}, {start, [0, 100, 0]}, {sets, 1}]},
|
||||
{90160, [{type, mission}, {file, "data/missions/boss/lv1/falz2/quest_ae.nbl"}, {start, [0, 100, 0]}, {sets, 1}]},
|
||||
|
||||
%% v1 Free Missions.
|
||||
{110000, [{type, mission}, {file, "data/missions/v1/evacuation/quest_ae.nbl"}, {start, [0, 1130, 0]}, {sets, 1}]},
|
||||
{110010, [{type, mission}, {file, "data/missions/v1/annihilation/quest_ae.nbl"}, {start, [0, 1300, 0]}, {sets, 1}]},
|
||||
{110100, [{type, mission}, {file, "data/missions/v1/dark-satellite/quest_ae.nbl"}, {start, [0, 111, 0]}, {sets, 1}]},
|
||||
{111000, [{type, mission}, {file, "data/missions/v1/creature-discomfort/quest_ae.nbl"}, {start, [0, 101, 0]}, {sets, 1}]},
|
||||
{111010, [{type, mission}, {file, "data/missions/v1/burning-plains/quest_ae.nbl"}, {start, [0, 1120, 0]}, {sets, 1}]},
|
||||
{111020, [{type, mission}, {file, "data/missions/v1/mad-beasts/quest_ae.nbl"}, {start, [0, 1130, 0]}, {sets, 1}]},
|
||||
{111030, [{type, mission}, {file, "data/missions/v1/ruler-of-the-plains/quest_ae.nbl"}, {start, [0, 110, 0]}, {sets, 1}]},
|
||||
{111100, [{type, mission}, {file, "data/missions/v1/what-is-in-the-ruins/quest_ae.nbl"}, {start, [0, 1102, 0]}, {sets, 1}]},
|
||||
{111110, [{type, mission}, {file, "data/missions/v1/two-headed-sentinel/quest_ae.nbl"}, {start, [0, 1211, 0]}, {sets, 1}]},
|
||||
{111200, [{type, mission}, {file, "data/missions/v1/cargo-train-rescue/quest_ae.nbl"}, {start, [0, 105, 3]}, {sets, 1}]},
|
||||
{111300, [{type, mission}, {file, "data/missions/v1/endrum-underground/quest_ae.nbl"}, {start, [0, 110, 0]}, {sets, 1}]},
|
||||
{112000, [{type, mission}, {file, "data/missions/v1/mizuraki-devastation/quest_ae.nbl"}, {start, [0, 101, 0]}, {sets, 1}]},
|
||||
{112010, [{type, mission}, {file, "data/missions/v1/frozen-woods/quest_ae.nbl"}, {start, [0, 1111, 0]}, {sets, 1}]},
|
||||
{112020, [{type, mission}, {file, "data/missions/v1/demons-above/quest_ae.nbl"}, {start, [0, 3115, 0]}, {sets, 1}]},
|
||||
{112100, [{type, mission}, {file, "data/missions/v1/defend-islands/quest_ae.nbl"}, {start, [0, 3120, 0]}, {sets, 1}]},
|
||||
{112120, [{type, mission}, {file, "data/missions/v1/woodland-flames/quest_ae.nbl"}, {start, [0, 1220, 0]}, {sets, 1}]},
|
||||
{112200, [{type, mission}, {file, "data/missions/v1/grove-of-fanatics/quest_ae.nbl"}, {start, [0, 104, 0]}, {sets, 1}]},
|
||||
{112210, [{type, mission}, {file, "data/missions/v1/temple-of-ice/quest_ae.nbl"}, {start, [0, 1201, 0]}, {sets, 1}]},
|
||||
{113000, [{type, mission}, {file, "data/missions/v1/pandemonium/quest_ae.nbl"}, {start, [0, 3110, 0]}, {sets, 1}]},
|
||||
{113100, [{type, mission}, {file, "data/missions/v1/tunnel-infestation/quest_ae.nbl"}, {start, [0, 210, 0]}, {sets, 1}]},
|
||||
{113200, [{type, mission}, {file, "data/missions/v1/goliath-in-the-desert/quest_ae.nbl"}, {start, [0, 3120, 0]}, {sets, 1}]},
|
||||
{113230, [{type, mission}, {file, "data/missions/v1/absolute-zero/quest_ae.nbl"}, {start, [0, 1221, 0]}, {sets, 1}]},
|
||||
{113240, [{type, mission}, {file, "data/missions/v1/terror-in-the-desert/quest_ae.nbl"}, {start, [0, 4240, 0]}, {sets, 1}]}
|
||||
]).
|
||||
|
||||
-define(ZONES, [
|
||||
% Unsafe Passage
|
||||
|
||||
{[1000000, 0], [{file, "data/missions/unsafe-passage.1.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1000001, 0], [{file, "data/missions/unsafe-passage.1.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1000002, 0], [{file, "data/missions/unsafe-passage.1.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1000003, 0], [{file, "data/missions/unsafe-passage.1.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000004, 0], [{file, "data/missions/unsafe-passage.1.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1000010, 0], [{file, "data/missions/unsafe-passage.2.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1000011, 0], [{file, "data/missions/unsafe-passage.2.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1000012, 0], [{file, "data/missions/unsafe-passage.2.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1000013, 0], [{file, "data/missions/unsafe-passage.2.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000014, 0], [{file, "data/missions/unsafe-passage.2.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1000020, 0], [{file, "data/missions/unsafe-passage.3.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1000021, 0], [{file, "data/missions/unsafe-passage.3.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1000022, 0], [{file, "data/missions/unsafe-passage.3.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1000023, 0], [{file, "data/missions/unsafe-passage.3.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000024, 0], [{file, "data/missions/unsafe-passage.3.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
% Fight for Food
|
||||
|
||||
{[1000100, 0], [{file, "data/missions/fight-for-food.1.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1000101, 0], [{file, "data/missions/fight-for-food.1.b.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000102, 0], [{file, "data/missions/fight-for-food.1.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000103, 0], [{file, "data/missions/fight-for-food.1.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000104, 0], [{file, "data/missions/fight-for-food.1.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1000110, 0], [{file, "data/missions/fight-for-food.2.c.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000111, 0], [{file, "data/missions/fight-for-food.2.b.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000112, 0], [{file, "data/missions/fight-for-food.2.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000113, 0], [{file, "data/missions/fight-for-food.2.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000114, 0], [{file, "data/missions/fight-for-food.2.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1000120, 0], [{file, "data/missions/fight-for-food.3.c.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000121, 0], [{file, "data/missions/fight-for-food.3.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1000122, 0], [{file, "data/missions/fight-for-food.3.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1000123, 0], [{file, "data/missions/fight-for-food.3.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1000124, 0], [{file, "data/missions/fight-for-food.3.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
% Dark Satellite
|
||||
|
||||
{[1001000, 0], [{file, "data/missions/dark-satellite.1.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1001001, 0], [{file, "data/missions/dark-satellite.1.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1001002, 0], [{file, "data/missions/dark-satellite.1.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1001003, 0], [{file, "data/missions/dark-satellite.1.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1001004, 0], [{file, "data/missions/dark-satellite.1.s2.zone.nbl"}, {sets, 4}]},
|
||||
{[1001005, 0], [{file, "data/missions/dark-satellite.1.s3.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1001010, 0], [{file, "data/missions/dark-satellite.2.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1001011, 0], [{file, "data/missions/dark-satellite.2.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1001012, 0], [{file, "data/missions/dark-satellite.2.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1001013, 0], [{file, "data/missions/dark-satellite.2.s.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1001014, 0], [{file, "data/missions/dark-satellite.2.s2.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1001015, 0], [{file, "data/missions/dark-satellite.2.s3.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1001020, 0], [{file, "data/missions/dark-satellite.3.c.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1001021, 0], [{file, "data/missions/dark-satellite.3.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1001022, 0], [{file, "data/missions/dark-satellite.3.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1001023, 0], [{file, "data/missions/dark-satellite.3.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1001024, 0], [{file, "data/missions/dark-satellite.3.s2.zone.nbl"}, {sets, 4}]},
|
||||
{[1001025, 0], [{file, "data/missions/dark-satellite.3.s3.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
% Seed Awakening
|
||||
|
||||
%~ {[1001100, 0], [{file, "data/missions/seed-awakening.1.c.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001100, 1], [{file, "data/missions/seed-awakening.1.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001101, 0], [{file, "data/missions/seed-awakening.1.b.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001101, 1], [{file, "data/missions/seed-awakening.1.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001102, 0], [{file, "data/missions/seed-awakening.1.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001102, 1], [{file, "data/missions/seed-awakening.1.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001103, 0], [{file, "data/missions/seed-awakening.1.s.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001103, 1], [{file, "data/missions/seed-awakening.1.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001103, 2], [{file, "data/missions/seed-awakening.1.s.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001104, 0], [{file, "data/missions/seed-awakening.1.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001104, 1], [{file, "data/missions/seed-awakening.1.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001104, 2], [{file, "data/missions/seed-awakening.1.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001105, 0], [{file, "data/missions/seed-awakening.1.s3.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001105, 1], [{file, "data/missions/seed-awakening.1.s3.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001105, 2], [{file, "data/missions/seed-awakening.1.s3.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1001110, 0], [{file, "data/missions/seed-awakening.2.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001110, 1], [{file, "data/missions/seed-awakening.2.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001111, 0], [{file, "data/missions/seed-awakening.2.b.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001111, 1], [{file, "data/missions/seed-awakening.2.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001112, 0], [{file, "data/missions/seed-awakening.2.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001112, 1], [{file, "data/missions/seed-awakening.2.a.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001113, 0], [{file, "data/missions/seed-awakening.2.s.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001113, 1], [{file, "data/missions/seed-awakening.2.s.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001113, 2], [{file, "data/missions/seed-awakening.2.s.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001114, 0], [{file, "data/missions/seed-awakening.2.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001114, 1], [{file, "data/missions/seed-awakening.2.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001114, 2], [{file, "data/missions/seed-awakening.2.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001115, 0], [{file, "data/missions/seed-awakening.2.s3.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001115, 1], [{file, "data/missions/seed-awakening.2.s3.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001115, 2], [{file, "data/missions/seed-awakening.2.s3.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1001120, 0], [{file, "data/missions/seed-awakening.3.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001120, 1], [{file, "data/missions/seed-awakening.3.c.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001121, 0], [{file, "data/missions/seed-awakening.3.b.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001121, 1], [{file, "data/missions/seed-awakening.3.b.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001122, 0], [{file, "data/missions/seed-awakening.3.a.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001122, 1], [{file, "data/missions/seed-awakening.3.a.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001123, 0], [{file, "data/missions/seed-awakening.3.s.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001123, 1], [{file, "data/missions/seed-awakening.3.s.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001123, 2], [{file, "data/missions/seed-awakening.3.s.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001124, 0], [{file, "data/missions/seed-awakening.3.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001124, 1], [{file, "data/missions/seed-awakening.3.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001124, 2], [{file, "data/missions/seed-awakening.3.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
{[1001125, 0], [{file, "data/missions/seed-awakening.3.s3.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001125, 1], [{file, "data/missions/seed-awakening.3.s3.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001125, 2], [{file, "data/missions/seed-awakening.3.s3.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
% True Darkness
|
||||
|
||||
%~ {[1001200, 0], [{file, "data/missions/true-darkness.1.c.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001200, 1], [{file, "data/missions/true-darkness.1.c.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001201, 0], [{file, "data/missions/true-darkness.1.b.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001201, 1], [{file, "data/missions/true-darkness.1.b.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001202, 0], [{file, "data/missions/true-darkness.1.a.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001202, 1], [{file, "data/missions/true-darkness.1.a.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001203, 0], [{file, "data/missions/true-darkness.1.s.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001203, 1], [{file, "data/missions/true-darkness.1.s.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001204, 0], [{file, "data/missions/true-darkness.1.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001204, 1], [{file, "data/missions/true-darkness.1.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1001210, 0], [{file, "data/missions/true-darkness.2.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001210, 1], [{file, "data/missions/true-darkness.2.c.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001211, 0], [{file, "data/missions/true-darkness.2.b.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001211, 1], [{file, "data/missions/true-darkness.2.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001212, 0], [{file, "data/missions/true-darkness.2.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001212, 1], [{file, "data/missions/true-darkness.2.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001213, 0], [{file, "data/missions/true-darkness.2.s.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001213, 1], [{file, "data/missions/true-darkness.2.s.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001214, 0], [{file, "data/missions/true-darkness.2.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001214, 1], [{file, "data/missions/true-darkness.2.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1001220, 0], [{file, "data/missions/true-darkness.3.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001220, 1], [{file, "data/missions/true-darkness.3.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001221, 0], [{file, "data/missions/true-darkness.3.b.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001221, 1], [{file, "data/missions/true-darkness.3.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001222, 0], [{file, "data/missions/true-darkness.3.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001222, 1], [{file, "data/missions/true-darkness.3.a.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1001223, 0], [{file, "data/missions/true-darkness.3.s.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1001223, 1], [{file, "data/missions/true-darkness.3.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1001224, 0], [{file, "data/missions/true-darkness.3.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1001224, 1], [{file, "data/missions/true-darkness.3.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
|
||||
% The Black Nest
|
||||
|
||||
{[1003000, 0], [{file, "data/missions/black-nest.1.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1003001, 0], [{file, "data/missions/black-nest.1.b.zone.nbl"}, {sets, 4}]},
|
||||
{[1003002, 0], [{file, "data/missions/black-nest.1.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1003003, 0], [{file, "data/missions/black-nest.1.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1003004, 0], [{file, "data/missions/black-nest.1.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1003010, 0], [{file, "data/missions/black-nest.2.c.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1003011, 0], [{file, "data/missions/black-nest.2.b.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1003012, 0], [{file, "data/missions/black-nest.2.a.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1003013, 0], [{file, "data/missions/black-nest.2.s.zone.nbl"}, {sets, 4}]},
|
||||
{[1003014, 0], [{file, "data/missions/black-nest.2.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
{[1003020, 0], [{file, "data/missions/black-nest.3.c.zone.nbl"}, {sets, 4}]},
|
||||
{[1003021, 0], [{file, "data/missions/black-nest.3.b.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1003022, 0], [{file, "data/missions/black-nest.3.a.zone.nbl"}, {sets, 4}]},
|
||||
{[1003023, 0], [{file, "data/missions/black-nest.3.s.zone.nbl"}, {sets, 4}]},
|
||||
%~ {[1003024, 0], [{file, "data/missions/black-nest.3.s2.zone.nbl"}, {sets, 4}]},
|
||||
|
||||
% The Dark God
|
||||
|
||||
{[1003100, 0], [{file, "data/missions/dark-god.1.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003100, 1], [{file, "data/missions/dark-god.1.c.zone-1.nbl"}, {sets, 1}]},
|
||||
%~ {[1003101, 0], [{file, "data/missions/dark-god.1.b.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1003101, 1], [{file, "data/missions/dark-god.1.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003102, 0], [{file, "data/missions/dark-god.1.a.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1003102, 1], [{file, "data/missions/dark-god.1.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003103, 0], [{file, "data/missions/dark-god.1.s.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003103, 1], [{file, "data/missions/dark-god.1.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003104, 0], [{file, "data/missions/dark-god.1.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003104, 1], [{file, "data/missions/dark-god.1.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003104, 2], [{file, "data/missions/dark-god.1.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1003110, 0], [{file, "data/missions/dark-god.2.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003110, 1], [{file, "data/missions/dark-god.2.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003111, 0], [{file, "data/missions/dark-god.2.b.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003111, 1], [{file, "data/missions/dark-god.2.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003112, 0], [{file, "data/missions/dark-god.2.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003112, 1], [{file, "data/missions/dark-god.2.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003113, 0], [{file, "data/missions/dark-god.2.s.zone-0.nbl"}, {sets, 4}]},
|
||||
%~ {[1003113, 1], [{file, "data/missions/dark-god.2.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003114, 0], [{file, "data/missions/dark-god.2.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003114, 1], [{file, "data/missions/dark-god.2.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003114, 2], [{file, "data/missions/dark-god.2.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
{[1003120, 0], [{file, "data/missions/dark-god.3.c.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003120, 1], [{file, "data/missions/dark-god.3.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003121, 0], [{file, "data/missions/dark-god.3.b.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003121, 1], [{file, "data/missions/dark-god.3.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003122, 0], [{file, "data/missions/dark-god.3.a.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003122, 1], [{file, "data/missions/dark-god.3.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003123, 0], [{file, "data/missions/dark-god.3.s.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003123, 1], [{file, "data/missions/dark-god.3.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003124, 0], [{file, "data/missions/dark-god.3.s2.zone-0.nbl"}, {sets, 4}]},
|
||||
{[1003124, 1], [{file, "data/missions/dark-god.3.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1003124, 2], [{file, "data/missions/dark-god.3.s2.zone-2.nbl"}, {sets, 1}]},
|
||||
|
||||
% Phantom Ruins (Linear Line counter)
|
||||
|
||||
%~ {[1060300, 0], [{file, "data/missions/phantom-ruins.c-0.zone.nbl"}, {sets, 3}]},
|
||||
%~ {[1060300, 1], [{file, "data/missions/phantom-ruins.c-1.zone.nbl"}, {sets, 1}]},
|
||||
{[1060301, 0], [{file, "data/missions/phantom-ruins.b-0.zone.nbl"}, {sets, 3}]},
|
||||
{[1060301, 1], [{file, "data/missions/phantom-ruins.b-1.zone.nbl"}, {sets, 1}]},
|
||||
%~ {[1060302, 0], [{file, "data/missions/phantom-ruins.a-0.zone.nbl"}, {sets, 3}]},
|
||||
%~ {[1060302, 1], [{file, "data/missions/phantom-ruins.a-1.zone.nbl"}, {sets, 1}]},
|
||||
{[1060303, 0], [{file, "data/missions/phantom-ruins.s-0.zone.nbl"}, {sets, 3}]},
|
||||
{[1060303, 1], [{file, "data/missions/phantom-ruins.s-1.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% Photon Eraser Return
|
||||
|
||||
%~ {[1070080, 0], [{file, "data/missions/photon-eraser-return.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% Dark Crystal Seeker
|
||||
|
||||
{[1070742, 0], [{file, "data/missions/dark-crystal-seeker.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% MAG'
|
||||
|
||||
{[1072100, 1], [{file, "data/missions/mag-prime.c.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1072100, 2], [{file, "data/missions/mag-prime.c.zone-2.nbl"}, {sets, 2}]},
|
||||
{[1072100, 3], [{file, "data/missions/mag-prime.c.zone-3.nbl"}, {sets, 1}]},
|
||||
{[1072101, 1], [{file, "data/missions/mag-prime.b.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1072101, 2], [{file, "data/missions/mag-prime.b.zone-2.nbl"}, {sets, 2}]},
|
||||
%~ {[1072101, 3], [{file, "data/missions/mag-prime.b.zone-3.nbl"}, {sets, 1}]},
|
||||
{[1072102, 1], [{file, "data/missions/mag-prime.a.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1072102, 2], [{file, "data/missions/mag-prime.a.zone-2.nbl"}, {sets, 2}]},
|
||||
{[1072102, 3], [{file, "data/missions/mag-prime.a.zone-3.nbl"}, {sets, 1}]},
|
||||
{[1072103, 1], [{file, "data/missions/mag-prime.s.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1072103, 2], [{file, "data/missions/mag-prime.s.zone-2.nbl"}, {sets, 2}]},
|
||||
{[1072103, 3], [{file, "data/missions/mag-prime.s.zone-3.nbl"}, {sets, 1}]},
|
||||
{[1072104, 1], [{file, "data/missions/mag-prime.s2.zone-1.nbl"}, {sets, 1}]},
|
||||
{[1072104, 2], [{file, "data/missions/mag-prime.s2.zone-2.nbl"}, {sets, 2}]},
|
||||
{[1072104, 3], [{file, "data/missions/mag-prime.s2.zone-3.nbl"}, {sets, 1}]},
|
||||
|
||||
% Gifts from Beyond
|
||||
|
||||
{[1072300, 0], [{file, "data/missions/gifts-from-beyond-plus.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% Airboard Rally
|
||||
|
||||
{[1090700, 0], [{file, "data/missions/airboard-rally.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% Colony
|
||||
|
||||
{[1100000, 0], [{file, nofile}]},
|
||||
{[1100000, 1], [{file, "data/lobby/colony.zone-1.nbl"}]},
|
||||
{[1100000, 2], [{file, "data/lobby/colony.zone-2.nbl"}]},
|
||||
{[1100000, 3], [{file, "data/lobby/colony.zone-3.nbl"}]},
|
||||
{[1100000, 4], [{file, "data/lobby/colony.zone-4.nbl"}]},
|
||||
{[1100000, 7], [{file, "data/lobby/colony.zone-7.nbl"}]},
|
||||
{[1100000,11], [{file, "data/lobby/colony.zone-11.nbl"}]},
|
||||
{[1100000,12], [{file, "data/lobby/colony.zone-12.nbl"}]},
|
||||
{[1100000,13], [{file, "data/lobby/colony.zone-13.nbl"}]},
|
||||
|
||||
% Parum
|
||||
|
||||
{[1101000, 0], [{file, "data/lobby/parum.zone-0.nbl"}]},
|
||||
{[1101000, 1], [{file, "data/lobby/parum.zone-1.nbl"}]},
|
||||
{[1101000, 2], [{file, "data/lobby/parum.zone-2.nbl"}]},
|
||||
{[1101000, 3], [{file, "data/lobby/parum.zone-3.nbl"}]},
|
||||
{[1101000, 4], [{file, "data/lobby/parum.zone-4.nbl"}]},
|
||||
{[1101000, 5], [{file, "data/lobby/parum.zone-5.nbl"}]},
|
||||
{[1101000, 7], [{file, "data/lobby/parum.zone-7.nbl"}]},
|
||||
{[1101000,11], [{file, "data/lobby/parum.zone-11.nbl"}]},
|
||||
{[1101000,12], [{file, "data/lobby/parum.zone-12.nbl"}]},
|
||||
{[1101000,13], [{file, "data/lobby/parum.zone-13.nbl"}]},
|
||||
|
||||
% Neudaiz
|
||||
|
||||
{[1102000, 0], [{file, "data/lobby/neudaiz.zone-0.nbl"}]},
|
||||
{[1102000, 1], [{file, "data/lobby/neudaiz.zone-1.nbl"}]},
|
||||
{[1102000, 2], [{file, "data/lobby/neudaiz.zone-2.nbl"}]},
|
||||
{[1102000, 3], [{file, "data/lobby/neudaiz.zone-3.nbl"}]},
|
||||
{[1102000, 4], [{file, "data/lobby/neudaiz.zone-4.nbl"}]},
|
||||
{[1102000, 7], [{file, "data/lobby/neudaiz.zone-7.nbl"}]},
|
||||
{[1102000,11], [{file, "data/lobby/neudaiz.zone-11.nbl"}]},
|
||||
{[1102000,12], [{file, "data/lobby/neudaiz.zone-12.nbl"}]},
|
||||
{[1102000,13], [{file, "data/lobby/neudaiz.zone-13.nbl"}]},
|
||||
|
||||
% Moatoob
|
||||
|
||||
{[1103000, 0], [{file, "data/lobby/moatoob.zone-0.nbl"}]},
|
||||
{[1103000, 1], [{file, "data/lobby/moatoob.zone-1.nbl"}]},
|
||||
{[1103000, 3], [{file, "data/lobby/moatoob.zone-3.nbl"}]},
|
||||
{[1103000, 4], [{file, "data/lobby/moatoob.zone-4.nbl"}]},
|
||||
{[1103000, 5], [{file, "data/lobby/moatoob.zone-5.nbl"}]},
|
||||
{[1103000, 6], [{file, "data/lobby/moatoob.zone-6.nbl"}]},
|
||||
{[1103000, 7], [{file, "data/lobby/moatoob.zone-7.nbl"}]},
|
||||
{[1103000,11], [{file, "data/lobby/moatoob.zone-11.nbl"}]},
|
||||
{[1103000,12], [{file, "data/lobby/moatoob.zone-12.nbl"}]},
|
||||
{[1103000,13], [{file, "data/lobby/moatoob.zone-13.nbl"}]},
|
||||
|
||||
% Spaceport
|
||||
|
||||
{[1104000, 0], [{file, "data/lobby/spaceport.zone.nbl"}]},
|
||||
|
||||
% Tutorial (colony)
|
||||
|
||||
{[1106000, 0], [{file, "data/tutorial/lobby.zone-0.nbl"}]},
|
||||
{[1106000, 1], [{file, "data/tutorial/lobby.zone-1.nbl"}]},
|
||||
|
||||
% SEED-Form Purge
|
||||
|
||||
{[1113000, 0], [{file, "data/tutorial/seed-form-purge.hyuga.zone.nbl"}, {sets, 1}]},
|
||||
{[1113001, 0], [{file, "data/tutorial/seed-form-purge.maya.zone.nbl"}, {sets, 1}]},
|
||||
{[1113002, 0], [{file, "data/tutorial/seed-form-purge.lou.zone.nbl"}, {sets, 1}]},
|
||||
{[1113003, 0], [{file, "data/tutorial/seed-form-purge.leo.zone.nbl"}, {sets, 1}]},
|
||||
|
||||
% My room
|
||||
|
||||
{[1120000, 0], [{file, "data/rooms/test.zone.nbl"}]},
|
||||
|
||||
% Tutorial (my room)
|
||||
|
||||
{[1120000,10], [{file, "data/tutorial/myroom.zone.nbl"}]},
|
||||
|
||||
% Story Episode 2
|
||||
|
||||
{[1131010,0], [{file, "data/missions/ep2ch1.c.zone-0.nbl"}]},
|
||||
{[1131010,1], [{file, "data/missions/ep2ch1.c.zone-1.nbl"}]},
|
||||
{[1131010,2], [{file, "data/missions/ep2ch1.c.zone-2.nbl"}]},
|
||||
{[1131010,3], [{file, "data/missions/ep2ch1.c.zone-3.nbl"}]},
|
||||
|
||||
%% Boss Tests.
|
||||
{[90120, 0], [{file, "data/missions/boss/lv1/alteraz/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[90120, 1], [{file, "data/missions/boss/lv1/alteraz/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[90130, 0], [{file, "data/missions/boss/lv1/rolei/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[90130, 1], [{file, "data/missions/boss/lv1/rolei/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[90140, 0], [{file, "data/missions/boss/lv1/motherbrain/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[90140, 1], [{file, "data/missions/boss/lv1/motherbrain/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[90150, 0], [{file, "data/missions/boss/lv1/falz1/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[90150, 1], [{file, "data/missions/boss/lv1/falz1/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[90160, 0], [{file, "data/missions/boss/lv1/falz2/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[90160, 1], [{file, "data/missions/boss/lv1/falz2/zone01_ae.nbl"}, {sets, 1}]},
|
||||
|
||||
%% v1 Free Missions.
|
||||
{[110000, 0], [{file, "data/missions/v1/evacuation/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[110010, 0], [{file, "data/missions/v1/annihilation/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[110100, 0], [{file, "data/missions/v1/dark-satellite/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[110100, 1], [{file, "data/missions/v1/dark-satellite/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[110100, 2], [{file, "data/missions/v1/dark-satellite/zone02_ae.nbl"}, {sets, 1}]},
|
||||
{[111000, 0], [{file, "data/missions/v1/creature-discomfort/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111010, 0], [{file, "data/missions/v1/burning-plains/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111020, 0], [{file, "data/missions/v1/mad-beasts/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111030, 0], [{file, "data/missions/v1/ruler-of-the-plains/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111030, 1], [{file, "data/missions/v1/ruler-of-the-plains/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[111100, 0], [{file, "data/missions/v1/what-is-in-the-ruins/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111100, 1], [{file, "data/missions/v1/what-is-in-the-ruins/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[111110, 0], [{file, "data/missions/v1/two-headed-sentinel/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111110, 1], [{file, "data/missions/v1/two-headed-sentinel/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[111200, 0], [{file, "data/missions/v1/cargo-train-rescue/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111300, 0], [{file, "data/missions/v1/endrum-underground/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[111300, 1], [{file, "data/missions/v1/endrum-underground/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[112000, 0], [{file, "data/missions/v1/mizuraki-devastation/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112010, 0], [{file, "data/missions/v1/frozen-woods/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112020, 0], [{file, "data/missions/v1/demons-above/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112020, 1], [{file, "data/missions/v1/demons-above/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[112100, 0], [{file, "data/missions/v1/defend-islands/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112120, 0], [{file, "data/missions/v1/woodland-flames/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112200, 0], [{file, "data/missions/v1/grove-of-fanatics/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[112200, 1], [{file, "data/missions/v1/grove-of-fanatics/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[112210, 0], [{file, "data/missions/v1/temple-of-ice/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113000, 0], [{file, "data/missions/v1/pandemonium/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113100, 0], [{file, "data/missions/v1/tunnel-infestation/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113200, 0], [{file, "data/missions/v1/goliath-in-the-desert/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113200, 1], [{file, "data/missions/v1/goliath-in-the-desert/zone01_ae.nbl"}, {sets, 1}]},
|
||||
{[113230, 0], [{file, "data/missions/v1/absolute-zero/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113240, 0], [{file, "data/missions/v1/terror-in-the-desert/zone00_ae.nbl"}, {sets, 1}]},
|
||||
{[113240, 1], [{file, "data/missions/v1/terror-in-the-desert/zone01_ae.nbl"}, {sets, 1}]}
|
||||
]).
|
||||
|
||||
-define(MAPS, [
|
||||
% Colony
|
||||
|
||||
{[1100000, 1], [{name, "Colony 1st Floor"}]},
|
||||
{[1100000, 2], [{name, "Colony 2nd Floor"}]},
|
||||
{[1100000, 3], [{name, "Colony 3rd Floor"}]},
|
||||
{[1100000, 4], [{name, "Colony 4th Floor"}]},
|
||||
{[1100000, 5], [{name, "Colony GUARDIANS"}]},
|
||||
{[1100000, 100], [{name, "Colony 2nd, Grind Shop"}]},
|
||||
{[1100000, 100], [{name, "Colony 2nd, Synth Shop"}]},
|
||||
{[1100000, 100], [{name, "Colony 2nd, Decos Shop"}]},
|
||||
{[1100000, 101], [{name, "Colony 2nd, Items Shop"}]},
|
||||
{[1100000, 101], [{name, "Colony 2nd, Weapons Shop"}]},
|
||||
{[1100000, 101], [{name, "Colony 2nd, Armors Shop"}]},
|
||||
{[1100000, 102], [{name, "Colony 3rd, Lumilass"}]},
|
||||
{[1100000, 102], [{name, "Colony 3rd, Clothes Shop"}]},
|
||||
{[1100000, 102], [{name, "Colony 3rd, Parts Shop"}]},
|
||||
{[1100000, 103], [{name, "Colony Club"}]},
|
||||
{[1100000, 110], [{name, "Colony R&D"}]},
|
||||
{[1100000,9000], [{name, "Colony Aurorey"}]},
|
||||
{[1100000,9001], [{name, "Colony Transfer Terminal"}]},
|
||||
{[1100000,9010], [{name, "Colony Dallgun"}]},
|
||||
{[1100000,9102], [{name, "Colony HIVE"}]},
|
||||
{[1100000,9200], [{name, "Colony Rykros"}]},
|
||||
{[1100000,9202], [{name, "Colony Falz Memoria"}]},
|
||||
|
||||
% Parum
|
||||
|
||||
{[1101000, 1], [{name, "Parum City Central"}]},
|
||||
{[1101000, 2], [{name, "Parum City West"}]},
|
||||
{[1101000, 3], [{name, "Parum City East"}]},
|
||||
{[1101000, 4], [{name, "Parum GUARDIANS"}]},
|
||||
{[1101000, 100], [{name, "Parum Synth Shop"}]},
|
||||
{[1101000, 100], [{name, "Parum Clothes Shop"}]},
|
||||
{[1101000, 100], [{name, "Parum Parts Shop"}]},
|
||||
{[1101000, 200], [{name, "Parum GRM"}]},
|
||||
{[1101000,9000], [{name, "Parum Raffon"}]},
|
||||
{[1101000,9010], [{name, "Parum Lakeshore"}]},
|
||||
{[1101000,9030], [{name, "Parum Waterfall"}]},
|
||||
{[1101000,9100], [{name, "Parum Denes"}]},
|
||||
{[1101000,9101], [{name, "Parum Underground"}]},
|
||||
{[1101000,9200], [{name, "Parum Beach"}]},
|
||||
{[1101000,9201], [{name, "Parum Rozenom"}]},
|
||||
{[1101000,9203], [{name, "Parum Subway"}]},
|
||||
{[1101000,9209], [{name, "Parum AMF"}]},
|
||||
|
||||
% Neudaiz
|
||||
|
||||
{[1102000, 1], [{name, "Neudaiz City"}]},
|
||||
{[1102000, 3], [{name, "Neudaiz GUARDIANS"}]},
|
||||
{[1102000, 100], [{name, "Neudaiz Synth Shop"}]},
|
||||
{[1102000, 100], [{name, "Neudaiz Clothes Shop"}]},
|
||||
{[1102000, 100], [{name, "Neudaiz Parts Shop"}]},
|
||||
{[1102000, 200], [{name, "Neudaiz Yohmei"}]},
|
||||
{[1102000,9000], [{name, "Neudaiz Islands"}]},
|
||||
{[1102000,9010], [{name, "Neudaiz Relics"}]},
|
||||
{[1102000,9100], [{name, "Neudaiz Mizuraki"}]},
|
||||
{[1102000,9120], [{name, "Neudaiz Hot Springs"}]},
|
||||
{[1102000,9300], [{name, "Neudaiz Temple"}]},
|
||||
{[1102000,9301], [{name, "Neudaiz Pavilion"}]},
|
||||
{[1102000,9302], [{name, "Neudaiz Habirao"}]},
|
||||
{[1102000,9305], [{name, "Neudaiz Saguraki"}]},
|
||||
|
||||
% Moatoob
|
||||
|
||||
{[1103000, 1], [{name, "Moatoob City"}]},
|
||||
{[1103000, 2], [{name, "Moatoob GUARDIANS"}]},
|
||||
{[1103000, 100], [{name, "Moatoob Parts Shop"}]},
|
||||
{[1103000, 100], [{name, "Moatoob Clothes Shop"}]},
|
||||
{[1103000, 100], [{name, "Moatoob Synth Shop"}]},
|
||||
{[1103000, 101], [{name, "Moatoob Pub"}]},
|
||||
{[1103000, 200], [{name, "Moatoob Tenora"}]},
|
||||
{[1103000,9010], [{name, "Moatoob Desert"}]},
|
||||
{[1103000,9030], [{name, "Moatoob Oasis"}]},
|
||||
{[1103000,9040], [{name, "Moatoob Glacier"}]},
|
||||
{[1103000,9101], [{name, "Moatoob Basin"}]},
|
||||
{[1103000,9202], [{name, "Moatoob Underground Lake"}]},
|
||||
{[1103000,9300], [{name, "Moatoob Casino"}]},
|
||||
{[1103000,9302], [{name, "Moatoob Il Cabo"}]},
|
||||
{[1103000,9304], [{name, "Moatoob Granigs"}]},
|
||||
|
||||
% Spaceports
|
||||
|
||||
{[1104000,900], [{name, "Spaceport"}]}
|
||||
]).
|
@ -1,4 +0,0 @@
|
||||
%% This file is automatically generated by EGS.
|
||||
%% Please do not edit it manually, as you would risk losing your changes.
|
||||
|
||||
-define(MISSIONS, []).
|
BIN
p/flags.bin
BIN
p/flags.bin
Binary file not shown.
BIN
p/packet1309.bin
BIN
p/packet1309.bin
Binary file not shown.
BIN
p/packet1332.bin
BIN
p/packet1332.bin
Binary file not shown.
1
priv/.gitignore
vendored
1
priv/.gitignore
vendored
@ -1 +0,0 @@
|
||||
egs_drv.so
|
@ -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"
|
||||
]}.
|
||||
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
═╚и╡|7с[
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
”§<EFBFBD><EFBFBD>±ינE<EFBFBD>%‚®³5%
|
@ -1,2 +0,0 @@
|
||||
ú;§¾x¢ýÀ´6çZ¥ë
|
||||
Ùí„Î!ÚU“ ÓC¥Ÿ¬(ä\ˆ–<>®ôÅRä$£a 6Ä+÷´Š…FVÞx9‘xü*°âÊ+².<2E>tþàBw]õÜÑ3ŒR¬ÙŒ
|
@ -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}},
|
||||
|
Binary file not shown.
Binary file not shown.
@ -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.
|
||||
|
@ -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}]},
|
||||
|
19
rebar.config
Normal file
19
rebar.config
Normal file
@ -0,0 +1,19 @@
|
||||
{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
|
||||
]}.
|
70
src/egs.erl
70
src/egs.erl
@ -1,70 +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)),
|
||||
ensure_started(mnesia),
|
||||
application:start(egs).
|
||||
|
||||
%% @spec stop() -> ok
|
||||
%% @doc Stop the EGS server.
|
||||
stop() ->
|
||||
Res = application:stop(egs),
|
||||
application:stop(mnesia),
|
||||
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 ->
|
||||
{ok, List} = egs_user_model:select(all),
|
||||
lists:foreach(fun(User) -> User#egs_user_model.pid ! {egs, notice, top, Message} end, List)
|
||||
end.
|
||||
|
||||
%% @doc Warp all players to a new map.
|
||||
warp(QuestID, ZoneID, MapID, EntryID) ->
|
||||
{ok, List} = egs_user_model:select(all),
|
||||
lists:foreach(fun(User) -> User#egs_user_model.pid ! {egs, warp, QuestID, ZoneID, MapID, EntryID} end, List).
|
||||
|
||||
%% @doc Warp one player to a new map.
|
||||
warp(GID, QuestID, ZoneID, MapID, EntryID) ->
|
||||
{ok, User} = egs_user_model:read(GID),
|
||||
User#egs_user_model.pid ! {egs, warp, QuestID, ZoneID, MapID, EntryID}.
|
@ -1,116 +0,0 @@
|
||||
%% @author Loïc Hoguin <essen@dev-extend.eu>
|
||||
%% @copyright 2010 Loïc Hoguin.
|
||||
%% @doc Accounts 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_accounts).
|
||||
-behavior(gen_server).
|
||||
-export([start_link/0, stop/0, get_folder/1, key_auth/2, key_auth_init/1, login_auth/2]). %% API.
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
||||
|
||||
%% Use the module name for the server's name.
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(TABLE, accounts).
|
||||
|
||||
-include("include/records.hrl").
|
||||
|
||||
%% API.
|
||||
|
||||
%% @spec start_link() -> {ok,Pid::pid()}
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
%% @spec stop() -> stopped
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
%% @todo Temporary code until we properly save the player data.
|
||||
get_folder(GID) ->
|
||||
gen_server:call(?SERVER, {get_folder, GID}).
|
||||
|
||||
key_auth(GID, AuthKey) ->
|
||||
gen_server:call(?SERVER, {key_auth, GID, AuthKey}).
|
||||
|
||||
key_auth_init(GID) ->
|
||||
gen_server:call(?SERVER, {key_auth_init, GID}).
|
||||
|
||||
%% @todo Properly handle login authentication when accounts are saved.
|
||||
login_auth(Username, Password) ->
|
||||
gen_server:call(?SERVER, {login_auth, Username, Password}).
|
||||
|
||||
%% gen_server.
|
||||
|
||||
init([]) ->
|
||||
error_logger:info_report("egs_accounts started"),
|
||||
{ok, undefined}.
|
||||
|
||||
handle_call({get_folder, GID}, _From, State) ->
|
||||
{atomic, [#accounts{username=Username, password=Password}]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end),
|
||||
{reply, << Username/binary, "-", Password/binary >>, State};
|
||||
|
||||
handle_call({key_auth, GID, AuthKey}, _From, State) ->
|
||||
{atomic, [#accounts{auth_state=AuthState}]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end),
|
||||
case AuthState of
|
||||
{wait_for_authentication, AuthKey, TRef} ->
|
||||
timer:cancel(TRef),
|
||||
mnesia:transaction(fun() ->
|
||||
Account = mnesia:read({?TABLE, GID}),
|
||||
mnesia:write(Account#accounts{auth_state=undefined})
|
||||
end),
|
||||
{reply, ok, State};
|
||||
_Any ->
|
||||
{reply, {error, badarg}, State}
|
||||
end;
|
||||
|
||||
handle_call({key_auth_init, GID}, _From, State) ->
|
||||
AuthKey = crypto:rand_bytes(4),
|
||||
TRef = timer:send_after(10000, {key_auth_timeout, GID}),
|
||||
mnesia:transaction(fun() ->
|
||||
[Account] = mnesia:read({?TABLE, GID}),
|
||||
mnesia:write(Account#accounts{auth_state={wait_for_authentication, AuthKey, TRef}})
|
||||
end),
|
||||
{reply, {ok, AuthKey}, State};
|
||||
|
||||
handle_call({login_auth, Username, Password}, _From, State) ->
|
||||
GID = 10000000 + mnesia:dirty_update_counter(counters, gid, 1),
|
||||
mnesia:transaction(fun() -> mnesia:write(#accounts{gid=GID, username=Username, password=Password}) end),
|
||||
{reply, {ok, GID}, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({key_auth_timeout, GID}, State) ->
|
||||
mnesia:transaction(fun() ->
|
||||
Account = mnesia:read({?TABLE, GID}),
|
||||
mnesia:write(Account#accounts{auth_state=undefined})
|
||||
end),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
@ -1,75 +0,0 @@
|
||||
%% @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
|
||||
%% 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]).
|
||||
|
||||
-include("include/records.hrl").
|
||||
|
||||
%% @spec start(_Type, _StartArgs) -> ServerRet
|
||||
%% @doc application start callback for egs.
|
||||
start(_Type, _StartArgs) ->
|
||||
case is_fresh_startup() of
|
||||
true ->
|
||||
db_init();
|
||||
{exists, Tables} ->
|
||||
ok = mnesia:wait_for_tables(Tables, 20000)
|
||||
end,
|
||||
egs_sup:start_link().
|
||||
|
||||
%% @spec stop(_State) -> ServerRet
|
||||
%% @doc application stop callback for egs.
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
%% @spec is_fresh_startup() -> true | false
|
||||
%% @doc Returns true if mnesia has not been initialized with the egs schema.
|
||||
%% Thanks to Dale Harvey for this function posted to the erlang questions mailing list.
|
||||
is_fresh_startup() ->
|
||||
Node = node(),
|
||||
case mnesia:system_info(tables) of
|
||||
[schema] -> true;
|
||||
Tables ->
|
||||
case mnesia:table_info(schema, cookie) of
|
||||
{_, Node} -> {exists, Tables};
|
||||
_ -> true
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec db_init() -> ok
|
||||
%% @doc Initialize the database.
|
||||
db_init() ->
|
||||
Nodes = [node()],
|
||||
case mnesia:system_info(is_running) of
|
||||
yes ->
|
||||
error_logger:info_report("stopping mnesia"),
|
||||
mnesia:stop();
|
||||
_ -> pass
|
||||
end,
|
||||
mnesia:create_schema(Nodes),
|
||||
error_logger:info_report("mnesia schema created"),
|
||||
error_logger:info_report("starting mnesia"),
|
||||
mnesia:start(),
|
||||
mnesia:create_table(accounts, [{attributes, record_info(fields, accounts)}]),
|
||||
mnesia:create_table(counters, [{attributes, record_info(fields, counters)}]),
|
||||
mnesia:create_table(psu_object, [{attributes, record_info(fields, psu_object)}]),
|
||||
mnesia:create_table(egs_user_model, [{attributes, record_info(fields, egs_user_model)}]),
|
||||
error_logger:info_report("mnesia tables created"),
|
||||
ok.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user