diff --git a/src/psu/psu_game.erl b/src/psu/psu_game.erl index c5189dc..cbfa736 100644 --- a/src/psu/psu_game.erl +++ b/src/psu/psu_game.erl @@ -220,9 +220,9 @@ char_load(User) -> send_0d01(User), % 0246 send_0a0a(), - send_1006(5), + send_1006(5, 0), send_1005((User#egs_user_model.character)#characters.name), - send_1006(12), + send_1006(12, 0), send_0210(), send_0222(), send_1500(User), @@ -450,7 +450,8 @@ loop(SoFar) -> send_0233(SpawnList), ?MODULE:loop(SoFar); {psu_player_unspawn, Spawn} -> - send_0204(Spawn#egs_user_model.id, Spawn#egs_user_model.lid, 5), + {ok, User} = egs_user_model:read(get(gid)), + send_0204(User, Spawn, 5), ?MODULE:loop(SoFar); {psu_warp, QuestID, ZoneID, MapID, EntryID} -> event({area_change, QuestID, ZoneID, MapID, EntryID}), @@ -754,7 +755,7 @@ event(lumilass_options_request) -> %% @todo Probably replenish the player HP when entering a non-mission area rather than when aborting the mission? event(mission_abort) -> - send_1006(11), + send_1006(11, 0), {ok, User} = egs_user_model:read(get(gid)), %% delete the mission if User#egs_user_model.instancepid =:= undefined -> ignore; @@ -956,6 +957,20 @@ event({object_warp_take, BlockID, ListNb, ObjectNb}) -> send_0503(User#egs_user_model.pos), send_1211(16#ffffffff, 0, 14, 0); +event({party_remove_member, PartyPos}) -> + log("party remove member ~b", [PartyPos]), + {ok, DestUser} = egs_user_model:read(get(gid)), + {ok, RemovedGID} = psu_party:get_member(DestUser#egs_user_model.partypid, PartyPos), + psu_party:remove_member(DestUser#egs_user_model.partypid, PartyPos), + {ok, RemovedUser} = egs_user_model:read(RemovedGID), + case (RemovedUser#egs_user_model.character)#characters.type of + npc -> egs_user_model:delete(RemovedGID); + _ -> ignore + end, + send_1006(8, PartyPos), + send_0204(DestUser, RemovedUser, 1), + psu_proto:send_0215(DestUser, 0); + event({player_options_change, Options}) -> {ok, User} = egs_user_model:read(get(gid)), file:write_file(io_lib:format("save/~s/~b-character.options", [User#egs_user_model.folder, (User#egs_user_model.character)#characters.slot]), Options); @@ -1174,11 +1189,16 @@ send_0202() -> send(<< 16#02020300:32, 0:352 >>). %% @todo Not sure. Used for unspawning, and more. -send_0204(PlayerGID, PlayerLID, Action) -> - GID = get(gid), - send(<< 16#02040300:32, 0:32, 16#00001200:32, PlayerGID:32/little-unsigned-integer, 0:64, - 16#00011300:32, GID:32/little-unsigned-integer, 0:64, PlayerGID:32/little-unsigned-integer, - PlayerLID:32/little-unsigned-integer, Action:32/little-unsigned-integer >>). +send_0204(DestUser, TargetUser, Action) -> + DestGID = DestUser#egs_user_model.id, + TargetTypeID = case (TargetUser#egs_user_model.character)#characters.type of + npc -> 16#00001d00; + _ -> 16#00001200 + end, + #egs_user_model{id=TargetGID, lid=TargetLID} = TargetUser, + send(<< 16#02040300:32, 0:32, TargetTypeID:32, TargetGID:32/little-unsigned-integer, 0:64, + 16#00011300:32, DestGID:32/little-unsigned-integer, 0:64, TargetGID:32/little-unsigned-integer, + TargetLID:32/little-unsigned-integer, Action:32/little-unsigned-integer >>). %% @doc Indicate to the client that loading should finish. %% @todo Last value seems to be 2 most of the time. Never 0 though. Apparently counters have it at 4. @@ -1414,10 +1434,9 @@ send_1005(Name) -> send(<< (header(16#1005))/binary, Before/binary, GID:32/little-unsigned-integer, 0:64, Name/binary, After/binary >>). %% @doc Party-related command probably controlling the party state. -%% Value 11 aborts the mission. -%% @todo Figure out what the packet is. -send_1006(N) -> - send(<< (header(16#1006))/binary, N:32/little-unsigned-integer >>). +%% EventID 11 aborts the mission. +send_1006(EventID, PartyPos) -> + send(<< (header(16#1006))/binary, EventID:8, PartyPos:8, 0:16 >>). %% @doc Send the player's current location. send_100e(QuestID, ZoneID, MapID, Location, CounterID) -> diff --git a/src/psu/psu_party.erl b/src/psu/psu_party.erl index 70221d8..32ab004 100644 --- a/src/psu/psu_party.erl +++ b/src/psu/psu_party.erl @@ -19,7 +19,7 @@ -module(psu_party). -behavior(gen_server). --export([start_link/1, stop/1, join/3, leave/2, get_instance/1, set_instance/2, remove_instance/1, get_npc/1]). %% API. +-export([start_link/1, stop/1, join/3, leave/2, get_instance/1, set_instance/2, remove_instance/1, get_member/2, remove_member/2, get_npc/1]). %% API. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server. -record(state, {free_spots, users, instancepid}). @@ -50,6 +50,14 @@ set_instance(PartyPid, InstancePid) -> remove_instance(PartyPid) -> gen_server:cast(PartyPid, remove_instance). +%% @doc Return the user at the given position. +get_member(PartyPid, Spot) -> + gen_server:call(PartyPid, {get_member, Spot}). + +%% @doc Remove a member from the party. +remove_member(PartyPid, Spot) -> + gen_server:cast(PartyPid, {remove_member, Spot}). + %% @doc Returns a list of NPC UserID. get_npc(PartyPid) -> gen_server:call(PartyPid, get_npc). @@ -82,6 +90,10 @@ handle_call({join, PlayerType, UserID}, _From, State) -> handle_call(get_instance, _From, State) -> {reply, {ok, State#state.instancepid}, State}; +handle_call({get_member, Spot}, _From, State) -> + [UserID] = [FoundUserID || {PlayerSpot, _PlayerType, FoundUserID} <- State#state.users, PlayerSpot =:= Spot], + {reply, {ok, UserID}, State}; + handle_call(get_npc, _From, State) -> List = [{Spot, UserID} || {Spot, PlayerType, UserID} <- State#state.users, PlayerType =:= npc], {reply, {ok, List}, State}; @@ -109,6 +121,11 @@ handle_cast({set_instance, InstancePid}, State) -> handle_cast(remove_instance, State) -> {noreply, State#state{instancepid=undefined}}; +handle_cast({remove_member, Spot}, State) -> + Users = [{PlayerSpot, PlayerType, UserID} || {PlayerSpot, PlayerType, UserID} <- State#state.users, PlayerSpot =/= Spot], + FreeSpots = State#state.free_spots, + {noreply, State#state{free_spots=[Spot|FreeSpots], users=Users}}; + handle_cast(_Msg, State) -> {noreply, State}. diff --git a/src/psu/psu_proto.erl b/src/psu/psu_proto.erl index 733e98f..386618c 100644 --- a/src/psu/psu_proto.erl +++ b/src/psu/psu_proto.erl @@ -366,6 +366,23 @@ parse(Size, 16#080d, Channel, Data) -> ?ASSERT_EQ(VarI, 0), ignore; +%% @todo Find out what it's really doing! +parse(Size, 16#080f, Channel, Data) -> + << _LID:16/little, VarA:16/little, VarB:32/little, VarC:32/little, VarD:32/little, VarE:32/little, + VarF:32/little, VarG:32/little, VarH:32/little, VarI:32/little, _PartyPos:32/little >> = Data, + ?ASSERT_EQ(Size, 48), + ?ASSERT_EQ(Channel, 2), + ?ASSERT_EQ(VarA, 0), + ?ASSERT_EQ(VarB, 0), + ?ASSERT_EQ(VarC, 0), + ?ASSERT_EQ(VarD, 0), + ?ASSERT_EQ(VarE, 0), + ?ASSERT_EQ(VarF, 0), + ?ASSERT_EQ(VarG, 0), + ?ASSERT_EQ(VarH, 0), + ?ASSERT_EQ(VarI, 0), + ignore; + parse(Size, 16#0811, Channel, Data) -> << _LID:16/little, VarA:16/little, VarB:32/little, VarC:32/little, VarD:32/little, VarE:32/little, VarF:32/little, VarG:32/little, VarH:32/little, VarI:32/little, _CounterType:8, VarJ:8, FromZoneID:16/little, FromMapID:16/little, FromEntryID:16/little, CounterID:32/little, VarK:32/little >> = Data, @@ -798,6 +815,22 @@ parse(Size, 16#0f0a, Channel, Data) -> ignore end; +parse(Size, 16#1007, Channel, Data) -> + << VarA:32/little, VarB:32/little, VarC:32/little, VarD:32/little, VarE:32/little, VarF:32/little, + VarG:32/little, VarH:32/little, VarI:32/little, PartyPos:32/little, _Name:512/bits >> = Data, + ?ASSERT_EQ(Size, 112), + ?ASSERT_EQ(Channel, 2), + ?ASSERT_EQ(VarA, 0), + ?ASSERT_EQ(VarB, 0), + ?ASSERT_EQ(VarC, 0), + ?ASSERT_EQ(VarD, 0), + ?ASSERT_EQ(VarE, 0), + ?ASSERT_EQ(VarF, 0), + ?ASSERT_EQ(VarG, 0), + ?ASSERT_EQ(VarH, 0), + ?ASSERT_EQ(VarI, 0), + {party_remove_member, PartyPos}; + parse(Size, 16#1705, Channel, Data) -> << _LID:16/little, VarA:16/little, VarB:32/little, VarC:32/little, VarD:32/little, VarE:32/little, VarF:32/little, VarG:32/little, VarH:32/little, VarI:32/little >> = Data, ?ASSERT_EQ(Size, 44),