diff --git a/include/maps.hrl b/include/maps.hrl index dcd0f83..c7fa01a 100644 --- a/include/maps.hrl +++ b/include/maps.hrl @@ -192,6 +192,10 @@ {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, "data/lobby/colony.quest.nbl"}]}, @@ -465,6 +469,10 @@ {[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, "data/lobby/colony.zone-0.nbl"}]}, diff --git a/src/psu/psu_game.erl b/src/psu/psu_game.erl index 320fab4..637f9e3 100644 --- a/src/psu/psu_game.erl +++ b/src/psu/psu_game.erl @@ -837,6 +837,32 @@ handle(16#0e00, Data) -> << _:96, Hits/bits >> = Data, handle_hits(Hits); +%% @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. +handle(16#0f00, Data) -> + << A:32/little-unsigned-integer, 0:16, B:16/little-unsigned-integer, 0:16, C:16/little-unsigned-integer, 0, Whut:8, D:16/little-unsigned-integer, 0:16, + E:16/little-unsigned-integer, 0:16, F:16/little-unsigned-integer, G:16/little-unsigned-integer, H:16/little-unsigned-integer, I:32/little-unsigned-integer >> = Data, + log("init vehicle: ~b ~b ~b ~b ~b ~b ~b ~b ~b ~b", [A, B, C, Whut, D, E, F, G, H, I]), + send(<< (header(16#1208))/binary, A:32/little-unsigned-integer, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, + 0:16, B:16/little-unsigned-integer, 0:16, C:16/little-unsigned-integer, 0:16, D:16/little-unsigned-integer, 0:112, + E:16/little-unsigned-integer, 0:16, F:16/little-unsigned-integer, H:16/little-unsigned-integer, 1, 0, 100, 0, 10, 0, G:16/little-unsigned-integer, 0:16 >>); + +%% @doc Enter vehicle. +%% @todo Separate the reply. +handle(16#0f02, Data) -> + << A:32/little-unsigned-integer, B:32/little-unsigned-integer, C:32/little-unsigned-integer >> = Data, + log("enter vehicle: ~b ~b ~b", [A, B, C]), + HP = 100, + send(<< (header(16#120a))/binary, A:32/little-unsigned-integer, B:32/little-unsigned-integer, C:32/little-unsigned-integer, HP:32/little-unsigned-integer >>); + +%% @doc Sent right after entering the vehicle. Can't move without it. +%% @todo Separate the reply. +handle(16#0f07, Data) -> + << A:32/little-unsigned-integer, B:32/little-unsigned-integer >> = Data, + log("after enter vehicle: ~b ~b", [A, B]), + send(<< (header(16#120f))/binary, A:32/little-unsigned-integer, B:32/little-unsigned-integer >>); + %% @doc Object event handler. %% @todo Handle all events appropriately. %% @todo B should be the ObjType. @@ -869,20 +895,25 @@ handle(16#0f0a, Data) -> 9 -> % healing pad % 0117, 0111, 0117? ignore; - 12 -> % pick/use key + 12 -> % pick/use key, pick vehicle_boost {ok, User} = egs_user_model:read(get(gid)), - {BlockID, [EventID|_]} = psu_instance:key_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), - send_1205(EventID, BlockID, 0), + Args = psu_instance:std_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), + case Args of + undefined -> %% vehicle boost doesn't send an event + ignore; + {BlockID, [EventID|_]} -> + send_1205(EventID, BlockID, 0) + end, send_1213(ObjectID, 1); 13 -> % floor_button on (also sent when clearing a few of the rooms in black nest) {ok, User} = egs_user_model:read(get(gid)), - {BlockID, EventID} = psu_instance:floor_button_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), + {BlockID, EventID} = psu_instance:std_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), send_1205(EventID, BlockID, 0), send_1213(ObjectID, 1); 14 -> % floor_button off %% @todo Apparently when it's not a floor_button but a light switch, this here should be handled differently. {ok, User} = egs_user_model:read(get(gid)), - {BlockID, EventID} = psu_instance:floor_button_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), + {BlockID, EventID} = psu_instance:std_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), send_1205(EventID, BlockID, 1), send_1213(ObjectID, 0); %~ 19 -> % activate trap @@ -891,23 +922,31 @@ handle(16#0f0a, Data) -> ignore; 23 -> % initialize key slots (called when picking a key or checking the gate directly with no key) {ok, User} = egs_user_model:read(get(gid)), - {BlockID, [_, EventID, _]} = psu_instance:key_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), + {BlockID, [_, EventID, _]} = psu_instance:std_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), send_1205(EventID, BlockID, 0); % in block 1, 202 = key [1] x1, 203 = key [-] x1 24 -> % open gate (only when client has key) {ok, User} = egs_user_model:read(get(gid)), - {BlockID, [_, _, EventID]} = psu_instance:key_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), + {BlockID, [_, _, EventID]} = psu_instance:std_event(User#egs_user_model.instancepid, (User#egs_user_model.area)#psu_area.zoneid, ObjectID), send_1205(EventID, BlockID, 0), send_1213(ObjectID, 1); 25 -> % sit on chair send_1211(A, C, 8, 0); 26 -> % sit out of chair send_1211(A, C, 8, 2); - %~ 30 -> % @todo (phantom ruins block 4) + 28 -> % respawn object picked (like vehicle_boost) + send_1213(ObjectID, 0); + %~ 30 -> % @todo (phantom ruins block 4, dark god 2 block 1 (fake key block)) %~ ignore; _ -> log("object event ~b", [Action]) end; +%% @todo Not sure yet. +handle(16#1019, Data) -> + << Value:32/little-unsigned-integer >> = Data, + log("command 1019 with value ~b", [Value]); + %~ send(<< (header(16#1019))/binary, 0:192, 16#00200000:32, 0:32 >>); + %% @todo Not sure about that one though. Probably related to 1112 still. handle(16#1106, Data) -> @@ -918,6 +957,12 @@ handle(16#1106, Data) -> handle(16#1112, Data) -> send_1113(Data); +%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race. +handle(16#1216, Data) -> + << Value:32/little-unsigned-integer >> = Data, + log("command 1216 with value ~b", [Value]), + send_1216(Value); + %% @doc Party information recap request. %% @todo Handle when the party already exists! And stop doing it wrong. @@ -1491,6 +1536,11 @@ send_1213(A, B) -> send_1215(A, B) -> send(<< (header(16#1215))/binary, A:32/little-unsigned-integer, 0:16, B:16/little-unsigned-integer >>). +%% @todo Not sure yet. Value is probably a TargetID. Used in Airboard Rally. Replying with the same value starts the race. +send_1216(Value) -> + GID = get(gid), + send(<< 16#12160300:32, 0:32, 16#00011300:32, GID:32/little-unsigned-integer, 0:64, 16#00011300:32, GID:32/little-unsigned-integer, 0:64, Value:32/little-unsigned-integer >>). + %% @doc Send the player's partner card. %% @todo Find out the remaining values. diff --git a/src/psu/psu_instance.erl b/src/psu/psu_instance.erl index fa2c505..83afcb1 100644 --- a/src/psu/psu_instance.erl +++ b/src/psu/psu_instance.erl @@ -20,7 +20,7 @@ -module(psu_instance). -behavior(gen_server). --export([start_link/1, stop/1, floor_button_event/3, key_event/3, spawn_cleared_event/3, warp_event/5, hit/3]). %% API. +-export([start_link/1, stop/1, std_event/3, spawn_cleared_event/3, warp_event/5, hit/3]). %% API. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server. -include_lib("stdlib/include/qlc.hrl"). @@ -44,12 +44,8 @@ stop(InstancePid) -> gen_server:call(InstancePid, stop). %% @todo @spec -floor_button_event(InstancePid, ZoneID, ObjectID) -> - gen_server:call(InstancePid, {floor_button_event, ZoneID, ObjectID}). - -%% @todo @spec event(ServerPid, ObjectID, Args) -> Response -key_event(InstancePid, ZoneID, ObjectID) -> - gen_server:call(InstancePid, {key_event, ZoneID, ObjectID}). +std_event(InstancePid, ZoneID, ObjectID) -> + gen_server:call(InstancePid, {std_event, ZoneID, ObjectID}). spawn_cleared_event(InstancePid, ZoneID, SpawnID) -> gen_server:call(InstancePid, {spawn_cleared_event, ZoneID, SpawnID}). @@ -92,19 +88,24 @@ object_init([{box, _Model, Breakable, TrigEventID}|Tail], ZoneID, BlockID, Objec end, object_init(Tail, ZoneID, BlockID, ObjectID + 1, TargetID + 1, ListIndex, ObjectIndex + 1); -%% @doc floor_button: {InstancePid, ZoneID, floor_button, ObjectID +%% @doc floor_button: {InstancePid, ZoneID, ObjectID} object_init([{floor_button, TrigEventID}|Tail], ZoneID, BlockID, ObjectID, TargetID, ListIndex, ObjectIndex) -> - object_insert(#psu_object{id={self(), ZoneID, floor_button, ObjectID}, instancepid=self(), type=floor_button, args={BlockID, TrigEventID}}), + object_insert(#psu_object{id={self(), ZoneID, ObjectID}, instancepid=self(), type=floor_button, args={BlockID, TrigEventID}}), object_init(Tail, ZoneID, BlockID, ObjectID + 1, TargetID + 1, ListIndex, ObjectIndex + 1); -%% @doc key: {InstancePid, ZoneID, key, ObjectID} +%% @doc key: {InstancePid, ZoneID, ObjectID} object_init([{key, _KeySet, TrigEventID, _ReqEventID}|Tail], ZoneID, BlockID, ObjectID, TargetID, ListIndex, ObjectIndex) -> - object_insert(#psu_object{id={self(), ZoneID, key, ObjectID}, instancepid=self(), type=key, args={BlockID, [TrigEventID]}}), + object_insert(#psu_object{id={self(), ZoneID, ObjectID}, instancepid=self(), type=key, args={BlockID, [TrigEventID]}}), object_init(Tail, ZoneID, BlockID, ObjectID + 1, TargetID, ListIndex, ObjectIndex + 1); %% @doc key_console: @see key; @todo handled the same for now object_init([{key_console, KeySet, TrigEventID, _ReqEventID}|Tail], ZoneID, BlockID, ObjectID, TargetID, ListIndex, ObjectIndex) -> - object_insert(#psu_object{id={self(), ZoneID, key, ObjectID}, instancepid=self(), type=key_console, args={BlockID, [243 + KeySet, 201 + KeySet, TrigEventID]}}), + object_insert(#psu_object{id={self(), ZoneID, ObjectID}, instancepid=self(), type=key_console, args={BlockID, [243 + KeySet, 201 + KeySet, TrigEventID]}}), + object_init(Tail, ZoneID, BlockID, ObjectID + 1, TargetID, ListIndex, ObjectIndex + 1); + +%% @doc vehicle_boost: {InstancePid, ZoneID, ObjectID} +object_init([vehicle_boost|Tail], ZoneID, BlockID, ObjectID, TargetID, ListIndex, ObjectIndex) -> + object_insert(#psu_object{id={self(), ZoneID, ObjectID}, instancepid=self(), type=vehicle_boost, args=undefined}), object_init(Tail, ZoneID, BlockID, ObjectID + 1, TargetID, ListIndex, ObjectIndex + 1); %% @doc spawn: {InstancePid, ZoneID, 'spawn', SpawnID} @@ -149,12 +150,8 @@ object_init([_Object|Tail], ZoneID, BlockID, ObjectID, TargetID, ListIndex, Obje %% Event handlers -handle_call({floor_button_event, ZoneID, ObjectID}, _From, State) -> - #psu_object{args=Args} = object_select({self(), ZoneID, floor_button, ObjectID}), - {reply, Args, State}; - -handle_call({key_event, ZoneID, ObjectID}, _From, State) -> - #psu_object{args=Args} = object_select({self(), ZoneID, key, ObjectID}), +handle_call({std_event, ZoneID, ObjectID}, _From, State) -> + #psu_object{args=Args} = object_select({self(), ZoneID, ObjectID}), {reply, Args, State}; handle_call({spawn_cleared_event, ZoneID, SpawnID}, _From, State) -> diff --git a/src/psu_parser.erl b/src/psu_parser.erl index 01b0c72..774c977 100644 --- a/src/psu_parser.erl +++ b/src/psu_parser.erl @@ -122,7 +122,7 @@ parse_object_list(BasePtr, Data, NbObjects, Ptr) -> Bits = Ptr * 8, << _:Bits/bits, Rest/bits >> = Data, List = parse_object_list_rec(Rest, NbObjects, []), - [parse_object_args(ObjType, Params, Data, ArgPtr - BasePtr - 16) || {ObjType, Params, _ArgSize, ArgPtr} <- List]. + [parse_object_args(ObjType, Params, Data, ArgSize, ArgPtr - BasePtr - 16) || {ObjType, Params, ArgSize, ArgPtr} <- List]. parse_object_list_rec(_Data, 0, Acc) -> lists:reverse(Acc); @@ -133,17 +133,18 @@ parse_object_list_rec(Data, NbObjects, Acc) -> log("object entry: a(~b) nb(~b) pos[x(~p) y(~p) z(~p)] rot[x(~p) y(~p) z(~p)] argsize(~b) argptr(~b)", [UnknownA, ObjType, PosX, PosY, PosZ, RotX, RotY, RotZ, ArgSize, ArgPtr]), parse_object_list_rec(Rest, NbObjects - 1, [{ObjType, {params, {pos, PosX, PosY, PosZ}, {rot, RotX, RotY, RotZ}}, ArgSize, ArgPtr}|Acc]). -parse_object_args(ObjType, Params, Data, Ptr) -> - Bits = Ptr * 8, - << _:Bits/bits, Rest/bits >> = Data, - parse_object_args(ObjType, Params, Rest). +parse_object_args(ObjType, Params, Data, Size, Ptr) -> + BeforeBits = Ptr * 8, + SizeBits = Size * 8, + << _:BeforeBits/bits, Args:SizeBits/bits, _/bits >> = Data, + parse_object_args(ObjType, Params, Args). parse_object_args(4, _Params, _Data) -> static_model; %% @todo Many unknowns. parse_object_args(5, _Params, Data) -> - << _:352, TrigEvent:16/little-unsigned-integer, _/bits >> = Data, + << _:352, TrigEvent:16/little-unsigned-integer, _Unknown:112 >> = Data, log("floor_button: trigevent(~p)", [TrigEvent]), {floor_button, TrigEvent}; @@ -161,7 +162,7 @@ parse_object_args(12, _Params, Data) -> << Model:16/little-unsigned-integer, UnknownA:16/little-unsigned-integer, UnknownB:32/little-unsigned-integer, UnknownC:16/little-unsigned-integer, Scale:16/little-unsigned-integer, UnknownD:16/little-unsigned-integer, 16#ff00:16, UnknownE:16/little-unsigned-integer, UnknownF:16/little-unsigned-integer, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, RawTrigEvent:16/little-unsigned-integer, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, - 16#ffff:16, UnknownG:16/little-unsigned-integer, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffff:16, UnknownH:16/little-unsigned-integer, 0:16, _/bits >> = Data, + 16#ffff:16, UnknownG:16/little-unsigned-integer, 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, 16#ffff:16, UnknownH:16/little-unsigned-integer, 0:16 >> = Data, Breakable = case UnknownB of 0 -> false; 1 -> true; @@ -173,7 +174,7 @@ parse_object_args(12, _Params, Data) -> {box, Model, Breakable, TrigEvent}; parse_object_args(14, {params, {pos, PosX, PosY, PosZ}, _Rot}, Data) -> - << _:96, DiffX:32/little-float, DiffY:32/little-float, DiffZ:32/little-float, DestDir:32/little-float, _/bits >> = Data, + << _:96, DiffX:32/little-float, DiffY:32/little-float, DiffZ:32/little-float, DestDir:32/little-float, _Unknown:512 >> = Data, log("warp: diffpos[x(~p) y(~p) z(~p)] destdir(~p)", [DiffX, DiffY, DiffZ, DestDir]), {warp, PosX + DiffX, PosY + DiffY, PosZ + DiffZ, DestDir}; @@ -187,35 +188,39 @@ parse_object_args(20, _Params, _Data) -> door; parse_object_args(22, _Params, Data) -> - << UnknownA:8, 0, KeySet:8, 1, 0:16, UnknownB:8, 0, 0:16, UnknownC:16/little-unsigned-integer, RawReqKey1Event:16/little-unsigned-integer, + << UnknownA:8, 0, KeySet:8, UnknownB:8, 0:16, UnknownC:8, 0, 0:16, UnknownD:16/little-unsigned-integer, RawReqKey1Event:16/little-unsigned-integer, RawReqKey2Event:16/little-unsigned-integer, RawReqKey3Event:16/little-unsigned-integer, RawReqKey4Event:16/little-unsigned-integer, - 16#ffffffff:32, 16#ffffffff:32, RawTrigEvent:16/little-unsigned-integer, UnknownD:16/little-unsigned-integer, - 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32, _/bits >> = Data, + 16#ffffffff:32, 16#ffffffff:32, RawTrigEvent:16/little-unsigned-integer, UnknownE:16/little-unsigned-integer, + 16#ffffffff:32, 16#ffffffff:32, 16#ffffffff:32 >> = Data, ReqKeyEvents = [convert_eventid(RawReqKey1Event), convert_eventid(RawReqKey2Event), convert_eventid(RawReqKey3Event), convert_eventid(RawReqKey4Event)], TrigEvent = convert_eventid(RawTrigEvent), - log("key_console: a(~b) keyset(~b) b(~b) c(~b) reqkeyevents(~p) trigevent(~p) d(~b)", [UnknownA, KeySet, UnknownB, UnknownC, ReqKeyEvents, TrigEvent, UnknownD]), + log("key_console: a(~b) keyset(~b) b(~b) c(~b) d(~b) reqkeyevents(~p) trigevent(~p) e(~b)", [UnknownA, KeySet, UnknownB, UnknownC, UnknownD, ReqKeyEvents, TrigEvent, UnknownE]), {key_console, KeySet, TrigEvent, ReqKeyEvents}; %% @doc Small spawn. parse_object_args(23, _Params, Data) -> %% @todo return meaningful information - << _:704, UnknownA:32/little-unsigned-integer, RawTrigEvent:16/little-unsigned-integer, RawReqEvent:16/little-unsigned-integer, 16#ffff:16, UnknownB:8, SpawnNb:8, _/bits >> = Data, + << _:704, UnknownA:32/little-unsigned-integer, RawTrigEvent:16/little-unsigned-integer, RawReqEvent:16/little-unsigned-integer, UnknownB:16/little-unsigned-integer, UnknownC:8, SpawnNb:8 >> = Data, TrigEvent = convert_eventid(RawTrigEvent), ReqEvent = convert_eventid(RawReqEvent), - log("spawn (x10): a(~b) trigevent(~p) reqevent(~p) b(~b) spawnnb(~b)", [UnknownA, TrigEvent, ReqEvent, UnknownB, SpawnNb]), + log("spawn (x10): a(~b) trigevent(~p) reqevent(~p) b(~b) c(~b) spawnnb(~b)", [UnknownA, TrigEvent, ReqEvent, UnknownB, UnknownC, SpawnNb]), {'spawn', 10, TrigEvent, ReqEvent}; %% @doc Big spawn. parse_object_args(24, _Params, Data) -> %% @todo return meaningful information - << _:704, UnknownA:32/little-unsigned-integer, RawTrigEvent:16/little-unsigned-integer, RawReqEvent:16/little-unsigned-integer, 16#ffff:16, UnknownB:8, SpawnNb:8, _/bits >> = Data, + << _:704, UnknownA:32/little-unsigned-integer, RawTrigEvent:16/little-unsigned-integer, RawReqEvent:16/little-unsigned-integer, 16#ffff:16, UnknownB:8, SpawnNb:8 >> = Data, TrigEvent = convert_eventid(RawTrigEvent), ReqEvent = convert_eventid(RawReqEvent), log("spawn (x30): a(~b) trigevent(~p) reqevent(~p) b(~b) spawnnb(~b)", [UnknownA, TrigEvent, ReqEvent, UnknownB, SpawnNb]), {'spawn', 30, TrigEvent, ReqEvent}; +%% @todo Find out! Big push 3rd zone file. +parse_object_args(25, _Params, _Data) -> + unknown_object_25; + parse_object_args(26, _Params, _Data) -> entrance; @@ -228,7 +233,7 @@ parse_object_args(28, _Params, _Data) -> parse_object_args(31, _Params, Data) -> << KeySet:8, UnknownA:8, UnknownB:8, 1:8, 16#ffff:16, RawTrigEvent:16/little-unsigned-integer, RawReqEvent1:16/little-unsigned-integer, RawReqEvent2:16/little-unsigned-integer, - RawReqEvent3:16/little-unsigned-integer, 16#ffff:16, 16#ffffffff:32, 16#ffffffff:32, _/bits >> = Data, + RawReqEvent3:16/little-unsigned-integer, 16#ffff:16, 16#ffffffff:32, 16#ffffffff:32 >> = Data, TrigEvent = convert_eventid(RawTrigEvent), ReqEvents = [convert_eventid(RawReqEvent1), convert_eventid(RawReqEvent2), convert_eventid(RawReqEvent3)], log("key: keyset(~b) a(~b) b(~b) trigevent(~p) reqevents(~p)", [KeySet, UnknownA, UnknownB, TrigEvent, ReqEvents]), @@ -241,6 +246,10 @@ parse_object_args(33, _Params, _Data) -> parse_object_args(35, _Params, _Data) -> boss; +%% @todo Find out! Big push 2nd zone file. +parse_object_args(39, _Params, _Data) -> + unknown_object_39; + parse_object_args(40, _Params, _Data) -> save_sphere; @@ -281,6 +290,14 @@ parse_object_args(53, _Params, _Data) -> parse_object_args(56, _Params, _Data) -> chair; +%% @todo Airboard Rally, floaders. Speed boost and healing. +parse_object_args(57, _Params, _Data) -> + vehicle_boost; + +%% @todo Apparently used both for floaders and airboard. +parse_object_args(58, _Params, _Data) -> + vehicle; + %% @todo Apparently used for the custom posters! parse_object_args(59, _Params, _Data) -> poster; @@ -320,12 +337,10 @@ parse_object_args(69, _Params, _Data) -> unknown_object_69; %% @todo Seems to be a megid turret "trap". - parse_object_args(70, _Params, _Data) -> trap; -%% @todo Seems to be a ceiling fall-on-you-and-explode "trap". Possibly also poison room. - +%% @todo Seems to be a ceiling fall-on-you-and-explode "trap". Possibly also poison room. Apparently also fake key. parse_object_args(71, _Params, _Data) -> trap.