diff --git a/src/psu/psu_game.erl b/src/psu/psu_game.erl index c2aad13..144b43f 100644 --- a/src/psu/psu_game.erl +++ b/src/psu/psu_game.erl @@ -497,11 +497,16 @@ loop(SoFar) -> %% @doc Dispatch the command to the right handler. dispatch(Orig) -> - {command, Command, Channel, Data} = psu_proto:packet_parse(Orig), - case Channel of - ignore -> ignore; - 1 -> broadcast(Command, Orig); - _ -> handle(Command, Data) + case psu_proto:parse(Orig) of + {command, Command, Channel, Data} -> + case Channel of + 1 -> broadcast(Command, Orig); + _ -> handle(Command, Data) + end; + ignore -> + ignore; + Event -> + event(Event) end. %% @doc Position change broadcast handler. Save the position and then dispatch it. @@ -550,21 +555,15 @@ broadcast(Command, Orig) lists:foreach(fun(User) -> User#egs_user_model.pid ! {psu_broadcast, Packet} end, SpawnList) end. -%% @doc Movement (non-broadcast) handler. Do nothing. -handle(16#0102, _) -> - ignore; - -%% @doc Weapon equip, unequip, item drop, and more... handler. Do what we can. +%% @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 We must also handle here the NPC characters. PartyPos can be used for that, and more info in unknown values maybe too? -handle(16#0105, Data) -> - << _:32, PartyPos:32/little-unsigned-integer, ItemID:8, Action:8, _:8, A:8, B:32/little-unsigned-integer, _/bits >> = Data, - log("0105 action ~b item ~b partypos ~b (~b ~b)", [Action, ItemID, PartyPos, A, B]), +%% @todo TargetGID and TargetLID must be validated, they're either the player's or his NPC characters. +event({item_equip, ItemID, TargetGID, TargetLID, A, B}) -> GID = get(gid), Category = case ItemID of % units would be 8, traps would be 12 @@ -572,41 +571,48 @@ handle(16#0105, Data) -> Y when Y =:= 5; Y =:= 6; Y =:= 7 -> 0; % clothes _ -> 1 % weapons end, - case Action of - 1 -> % equip item - Filename = case ItemID of - % weapons - 16 -> "p/packet0105_sword.bin"; - 13 -> "p/packet0105_twindaggers.bin"; - 15 -> "p/packet0105_dagger.bin"; - 9 -> "p/packet0105_rcsm.bin"; - 14 -> "p/packet0105_saber.bin"; - 8 -> "p/packet0105_mgun.bin"; - X when X =:= 17; X =:= 18 -> - "p/packet0105_twinguns.bin"; - % armor - 19 -> "p/packet0105_armor.bin"; - % clothes - X when X =:= 5; X =:= 6; X =:= 7 -> - none; - _ -> % default: do nothing - none - end, - case Filename of - none -> File = << >>; - _ -> {ok, File} = file:read_file(Filename) - end, - send(<< 16#01050300:32, 0:64, GID:32/little-unsigned-integer, 0:64, 16#00011300:32, GID:32/little-unsigned-integer, - 0:64, GID:32/little-unsigned-integer, PartyPos:32/little-unsigned-integer, ItemID, Action, Category, A, B:32/little-unsigned-integer, - File/binary >>); - 2 -> % unequip item - send(<< 16#01050300:32, 0:64, GID:32/little-unsigned-integer, 0:64, 16#00011300:32, GID:32/little-unsigned-integer, - 0:64, GID:32/little-unsigned-integer, PartyPos:32/little-unsigned-integer, ItemID, Action, Category, A, B:32/little-unsigned-integer >>); - 5 -> % drop item - ignore; - _ -> - ignore - end; + Filename = case ItemID of + % weapons + 16 -> "p/packet0105_sword.bin"; + 13 -> "p/packet0105_twindaggers.bin"; + 15 -> "p/packet0105_dagger.bin"; + 9 -> "p/packet0105_rcsm.bin"; + 14 -> "p/packet0105_saber.bin"; + 8 -> "p/packet0105_mgun.bin"; + X when X =:= 17; X =:= 18 -> + "p/packet0105_twinguns.bin"; + % armor + 19 -> "p/packet0105_armor.bin"; + % clothes + X when X =:= 5; X =:= 6; X =:= 7 -> + none; + _ -> % default, for lou + "p/packet0105_twindaggers.bin" + end, + case Filename of + none -> File = << >>; + _ -> {ok, File} = file:read_file(Filename) + end, + send(<< 16#01050300:32, 0:64, GID:32/little-unsigned-integer, 0:64, 16#00011300:32, GID:32/little-unsigned-integer, + 0:64, TargetGID:32/little-unsigned-integer, TargetLID:32/little-unsigned-integer, ItemID, 1, Category, A, B:32/little-unsigned-integer, + File/binary >>); + +%% @todo A and B are unknown. +%% @see item_equip +event({item_unequip, ItemID, TargetGID, TargetLID, A, B}) -> + GID = get(gid), + Category = case ItemID 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, + send(<< 16#01050300:32, 0:64, GID:32/little-unsigned-integer, 0:64, 16#00011300:32, GID:32/little-unsigned-integer, + 0:64, TargetGID:32/little-unsigned-integer, TargetLID:32/little-unsigned-integer, ItemID, 2, Category, A, B:32/little-unsigned-integer >>). + +%% @doc Movement (non-broadcast) handler. Do nothing. +handle(16#0102, _) -> + ignore; %% @doc Shop listing request. Currently return the normal item shop for everything. %% @todo Return the other shops appropriately. diff --git a/src/psu/psu_proto.erl b/src/psu/psu_proto.erl index 07a5dd3..c8b83b0 100644 --- a/src/psu/psu_proto.erl +++ b/src/psu/psu_proto.erl @@ -1,24 +1,106 @@ -% EGS: Erlang Game Server -% Copyright (C) 2010 Loic Hoguin -% -% This file is part of EGS. -% -% EGS is free software: you can redistribute it and/or modify -% it under the terms of the GNU 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 General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with EGS. If not, see . +%% @author Loïc Hoguin +%% @copyright 2010 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 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 General Public License for more details. +%% +%% You should have received a copy of the GNU General Public License +%% along with EGS. If not, see . -module(psu_proto). -compile(export_all). +%~ %% @todo We probably want to use active connections everywhere instead of doing this. +%~ recv %% remove later? + +%~ %% @todo We probably want to remove this after all send functions are moved back in psu_proto. +%~ send %% fragments automatically if needed + +%~ split + +%% @doc Log the message. +log(Msg) -> + io:format("~p ~s~n", [get(gid), Msg]). + +%% @spec log(Msg, FmtVars) -> ok +%% @doc Format and log the message. +log(Msg, FmtVars) -> + FmtMsg = io_lib:format(Msg, FmtVars), + log(FmtMsg). + +%% @spec assert() -> ok +%% @doc Log a detailed message when the function is called. +-define(ASSERT(), log("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 -> log("assert error in module ~p on line ~p~n", [?MODULE, ?LINE]) end). + +%% @spec parse(Packet) -> Result +%% @doc Parse the packet and return a result accordingly. +parse(<< Size:32/little, Command:16, Channel:8, _Unknown:8, Data/bits >>) -> + parse(Size, Command, Channel, Data). + +%% @todo One of the missing events is probably learning a new PA. +parse(Size, 16#0105, Channel, Data) -> + << _VarA:16/little, _VarB:16/little, VarC:32/little, _FromGID:32/little, VarD:32/little, VarE:32/little, TypeID:32/little, GID:32/little, + VarF:32/little, VarG:32/little, TargetGID:32/little, TargetLID:32/little, ItemID:8, EventID:8, _PAID:8, VarH:8, VarI:32/little, Rest/bits >> = Data, + ?ASSERT_EQ(Channel, 2), + ?ASSERT_EQ(VarC, 0), + ?ASSERT_EQ(VarD, 0), + ?ASSERT_EQ(VarE, 0), + ?ASSERT_EQ(TypeID, 0), + ?ASSERT_EQ(GID, 0), + ?ASSERT_EQ(VarF, 0), + ?ASSERT_EQ(VarG, 0), + Event = case EventID of + 1 -> item_equip; + 2 -> item_unequip; + 3 -> ignore; %% @todo item_link_pa; + 4 -> ignore; %% @todo item_unlink_pa; + 5 -> item_drop; + 7 -> ?ASSERT(), ignore; + 8 -> ignore; %% @todo item_use; + 9 -> ?ASSERT(), ignore; + 18 -> ignore; %% @todo item_unlearn_pa; + _ -> log("unknown 0105 EventID ~p", [EventID]) + end, + case Event of + item_drop -> + ?ASSERT_EQ(Size, 76), + << _Quantity:32/little, _PosX:32/little-float, _PosY:32/little-float, _PosZ:32/little-float >> = Rest, + %~ {Event, ItemID, Quantity, ...}; + ignore; + ignore -> + ?ASSERT_EQ(Size, 60), + ignore; + _ -> + ?ASSERT_EQ(Size, 60), + {Event, ItemID, TargetGID, TargetLID, VarH, VarI} + end; + +parse(Size, 16#0b05, _Channel, _Data) -> + ?ASSERT_EQ(Size, 8), + ignore; + +parse(_Size, Command, Channel, Data) -> + %% @todo log unknown command? + %~ ignore. + << _:288, Rest/bits >> = Data, + {command, Command, Channel, Rest}. + + + %% @doc Prepare a packet. Return the real size and padding at the end. packet_prepare(Packet) ->