diff --git a/rebar.config b/rebar.config index 4a35678..5baea58 100644 --- a/rebar.config +++ b/rebar.config @@ -11,4 +11,3 @@ {enet, ".*", {git, "https://github.com/flambard/enet", {branch, "master"}}}, {lager, ".*", {git, "https://github.com/erlang-lager/lager", {branch, "adt/lager_use_logger-option"}}} ]}. - diff --git a/rebar.lock b/rebar.lock index ea9966e..1e7aa66 100644 --- a/rebar.lock +++ b/rebar.lock @@ -14,7 +14,7 @@ {<<"iso8601">>,{pkg,<<"iso8601">>,<<"1.3.4">>},0}, {<<"lager">>, {git,"https://github.com/erlang-lager/lager", - {ref,"20ee7596867705d8ec9f6194bc937cc8250c6e33"}}, + {ref,"c97cf1512f976583f632158a96afa9e92713bac0"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.3.0">>},1}, diff --git a/src/openomf_lobby_client.erl b/src/openomf_lobby_client.erl index 339437b..75cdb09 100644 --- a/src/openomf_lobby_client.erl +++ b/src/openomf_lobby_client.erl @@ -27,6 +27,7 @@ -define(PACKET_REFRESH, 8). -define(PACKET_ANNOUNCEMENT, 9). -define(PACKET_RELAY, 10). +-define(PACKET_SPECTATE, 11). -define(CHALLENGE_OFFER, 1). -define(CHALLENGE_ACCEPT, 1). @@ -35,6 +36,9 @@ -define(CHALLENGE_DONE, 4). -define(CHALLENGE_ERROR, 5). +-define(SPECTATE_ACCEPT, 1). +-define(SPECTATE_ERROR, 2). + -define(JOIN_ERROR_NAME_USED, 1). -define(JOIN_ERROR_NAME_INVALID, 2). -define(JOIN_ERROR_UNSUPPORTED_PROTOCOL, 3). @@ -71,7 +75,7 @@ init(PeerInfo) -> handle_call(_, _, State) -> {reply, error, State}. -handle_cast({get_presence, From}, State = #state{name=Name, version=Version}) when is_binary(Name) -> +handle_cast({get_presence, From}, State = #state{name=Name}) when is_binary(Name) -> gen_server:cast(From, {presence, encode_peer_to_presence(State#state.peer_info, 0)}), {noreply, State}; @@ -83,7 +87,7 @@ handle_cast({challenge, From, ChallengerID, Version}, State = #state{name=Name, enet:send_reliable(Channel, Packet), PeerInfo2 = maps:put(status, ?PRESENCE_PONDERING, PeerInfo), enet:broadcast_reliable(2098, 0, encode_peer_to_presence(PeerInfo2, 0)), - %% TODO + %% TODO {noreply, State#state{match_pid=From, peer_info=PeerInfo2}}; handle_cast({challenge, From, _ChallengerID, _Version}, State = #state{name=Name, match_pid=MatchPid}) when is_binary(Name), is_pid(MatchPid) -> @@ -93,7 +97,7 @@ handle_cast({challenge, From, _ChallengerID, _Version}, State = #state{name=Name handle_cast({challenge, From, _ChallengerID, TheirVersion}, State = #state{name=Name, version=OurVersion, match_pid=undefined}) when is_binary(Name) andalso TheirVersion /= OurVersion -> gen_server:cast(From, {incompatible, self()}), - %% TODO + %% TODO {noreply, State}; handle_cast(accepted, State = #state{peer_info=PeerInfo, match_pid = MatchPid}) when is_pid(MatchPid) -> @@ -168,6 +172,8 @@ handle_info({'EXIT', PeerPid, _Reason}, State = #state{name=Name, peer_pid=PeerP RFU = 0, ConnectID = maps:get(connect_id, State#state.peer_info), user_leave_event(Name), + %% Record last player activity + openomf_lobby_client_sup:record_last_activity(Name), enet:broadcast_reliable(2098, 0, <>), {stop, normal, State}; @@ -262,6 +268,8 @@ handle_info({enet, ChannelID, #reliable{ data = <>) end, + %% Send last match and activity info + send_activity_info(Channel), {noreply ,State#state{name = Name, version = Version, protocol_version = LobbyVersion, peer_info = PeerInfo2}} catch _:_ -> %% user already registered @@ -306,6 +314,14 @@ handle_info({enet, _ChannelID, #reliable{ data = <>), + %% Bridge chat message to Discord + case application:get_env(discord_callback) of + undefined -> ok; + {ok, URL} -> + ChatMessage = iolist_to_binary(io_lib:format("**~s**: ~s", [Name, Yell])), + DiscordPayload = <<"{\"content\": \"", ChatMessage/binary, "\" }">>, + hackney:request(post, URL, [{<<"Content-Type">>, <<"application/json">>}], DiscordPayload, []) + end, {noreply ,State}; handle_info({enet, _ChannelID, #reliable{ data = <> }}, State = #state{match_pid=MatchPid}) when MatchPid /= undefined -> @@ -335,14 +351,22 @@ handle_info({enet, ChannelID, #reliable{ data = < - case openomf_lobby_match_sup:start_match(self(), PeerInfo, Pid) of - {ok, MatchPid} -> - PeerInfo2 = maps:put(status, ?PRESENCE_CHALLENGING, PeerInfo), - enet:broadcast_reliable(2098, 0, encode_peer_to_presence(PeerInfo2, 0)), - {noreply, State#state{match_pid =MatchPid, peer_info=PeerInfo2}}; - {error, Reason} -> - lager:info("failed to challenge ~p", [Reason]), - ErrorString = list_to_binary(io_lib:format("Failed to challenge user: ~p", [Reason])), + case find_match_processes(ID) of + [] -> + case openomf_lobby_match_sup:start_match(self(), PeerInfo, Pid, ID) of + {ok, MatchPid} -> + PeerInfo2 = maps:put(status, ?PRESENCE_CHALLENGING, PeerInfo), + enet:broadcast_reliable(2098, 0, encode_peer_to_presence(PeerInfo2, 0)), + {noreply, State#state{match_pid =MatchPid, peer_info=PeerInfo2}}; + {error, Reason} -> + lager:info("failed to challenge ~p", [Reason]), + ErrorString = list_to_binary(io_lib:format("Failed to challenge user: ~p", [Reason])), + enet:send_reliable(Channel, <>), + {noreply, State} + end; + _ -> + lager:info("failed to challenge: user busy"), + ErrorString = <<"Failed to challenge user: user busy">>, enet:send_reliable(Channel, <>), {noreply, State} end; @@ -352,6 +376,39 @@ handle_info({enet, ChannelID, #reliable{ data = <> }}, State = #state{peer_info=PeerInfo}) -> + Channels = maps:get(channels, PeerInfo), + LobbyChannel = maps:get(ChannelID, Channels), + case find_match_processes(ID) of + [Pid] -> + lager:info("sending spectate request to ~p", [Pid]), + Channel = maps:get(2, Channels), + OkFun = fun() -> + enet:send_reliable(LobbyChannel, <>) + end, + try gen_statem:call(Pid, {subscribe, Channel, self(), OkFun}) of + ok -> + PeerInfo2 = maps:put(status, ?PRESENCE_WATCHING, PeerInfo), + enet:broadcast_reliable(2098, 0, encode_peer_to_presence(PeerInfo2, 0)), + {noreply, State#state{peer_info=PeerInfo2}}; + Error -> + lager:warning("unexpected spectate result ~p", [Error]), + ErrorString = list_to_binary(io_lib:format("Failed to spectate match: ~p", [Error])), + enet:send_reliable(LobbyChannel, <>), + {noreply, State} + catch What:Why -> + lager:info("failed to spectate ~p", [{What, Why}]), + ErrorString = list_to_binary(io_lib:format("Failed to spectate match: ~p", [{What, Why}])), + enet:send_reliable(LobbyChannel, <>), + {noreply, State} + end; + _ -> + lager:info("failed to spectate: no such match"), + ErrorString = <<"Failed to spectate match: no such match">>, + enet:send_reliable(LobbyChannel, <>), + {noreply, State} + end; + handle_info({enet, _ChannelID, #reliable{ data = <> }}, State = #state{match_pid = MatchPid}) when is_pid(MatchPid) -> gen_statem:cast(MatchPid, {connected, self()}), lager:info("~p connected to peer", [State#state.name]), @@ -368,7 +425,7 @@ handle_info({enet, _ChannelID, #reliable{ data = <> }}, State = #state{peer_info=PeerInfo}) -> +handle_info({enet, ChannelID, #reliable{ data = <> }}, State = #state{peer_info=PeerInfo}) -> Channels = maps:get(channels, PeerInfo), Channel = maps:get(ChannelID, Channels), @@ -385,8 +442,18 @@ handle_info({enet, ChannelID, #reliable{ data = < + PI2 = maps:put(status, ?PRESENCE_AVAILABLE, State#state.peer_info), + enet:broadcast_reliable(2098, 0, encode_peer_to_presence(PI2, 0)), + PI2; + _ -> + %% normal refresh + PeerInfo + end, + + enet:send_reliable(Channel, encode_peer_to_presence(PeerInfo2, 0)), + {noreply, State#state{peer_info=PeerInfo2}}; handle_info({enet, _ChannelID, #reliable{ data = Packet }}, State) -> lager:info("got reliable packet ~p", [Packet]), @@ -430,4 +497,86 @@ user_leave_event(Name) -> hackney:request(post, URL, [{<<"Content-Type">>, <<"application/json">>}], <<"{\"content\": \"'", Name/binary, "' has left the arena\" }">>, []) end. +find_match_processes(KnownID) -> + %% Construct a match specification to check both ID positions + MatchSpec = [{ + %% Match the key pattern {n, l, {match, '$1', '$2'}} + { {n, l, {match, '$1', '$2'}}, '_', '_' }, + %% Guard: Check if either '$1' or '$2' equals KnownID + [{'orelse', {'=:=', '$1', KnownID}, {'=:=', '$2', KnownID}}], + %% Return all matched entries + ['$_'] + }], + %% Execute the select query on the local registry + case gproc:select(n, MatchSpec) of + [] -> + []; %% No processes found + Entries -> + %% Extract PIDs from the matched entries + [Pid || {_, Pid, _} <- Entries] + end. + +%% Format relative time from timestamp +format_relative_time(Timestamp) -> + Now = erlang:system_time(second), + Diff = Now - Timestamp, + + if + Diff < 60 -> + "just now"; + Diff < 3600 -> + Minutes = Diff div 60, + case Minutes of + 1 -> "1 minute ago"; + _ -> io_lib:format("~p minutes ago", [Minutes]) + end; + Diff < 86400 -> + Hours = Diff div 3600, + case Hours of + 1 -> "1 hour ago"; + _ -> io_lib:format("~p hours ago", [Hours]) + end; + Diff < 604800 -> + Days = Diff div 86400, + case Days of + 1 -> "1 day ago"; + _ -> io_lib:format("~p days ago", [Days]) + end; + true -> + Weeks = Diff div 604800, + case Weeks of + 1 -> "1 week ago"; + _ -> io_lib:format("~p weeks ago", [Weeks]) + end + end. + +%% Send activity information to a newly joined client +send_activity_info(Channel) -> + %% Check if there are other players online + Children = supervisor:which_children(openomf_lobby_client_sup), + ActivePlayerCount = length([Pid || {_Id, Pid, _Type, _Modules} <- Children, Pid /= self()]), + + %% Send last match info + case openomf_lobby_client_sup:get_last_match() of + {ok, {Timestamp, Winner, Loser}} -> + RelativeTime = format_relative_time(Timestamp), + Message = iolist_to_binary(io_lib:format("Last match: ~s defeated ~s ~s", [Winner, Loser, RelativeTime])), + enet:send_reliable(Channel, <>); + undefined -> + ok + end, + %% Send last activity info only if no other players are online + case ActivePlayerCount of + 0 -> + case openomf_lobby_client_sup:get_last_activity() of + {ok, {ActivityTimestamp, PlayerName}} -> + ActivityRelativeTime = format_relative_time(ActivityTimestamp), + ActivityMessage = iolist_to_binary(io_lib:format("Last player online: ~s ~s", [PlayerName, ActivityRelativeTime])), + enet:send_reliable(Channel, <>); + undefined -> + ok + end; + _ -> + ok + end. diff --git a/src/openomf_lobby_client_sup.erl b/src/openomf_lobby_client_sup.erl index dba3b0e..7b710c1 100644 --- a/src/openomf_lobby_client_sup.erl +++ b/src/openomf_lobby_client_sup.erl @@ -3,11 +3,15 @@ -behaviour(supervisor). -export([start_link/0, - start_client/1, client_presence/0, announce/1]). + start_client/1, client_presence/0, announce/1, + record_last_match/2, record_last_activity/1, + get_last_match/0, get_last_activity/0]). -export([init/1]). start_link() -> + % Create ETS table for tracking activity + ets:new(lobby_activity, [named_table, public, set]), supervisor:start_link({local, ?MODULE}, ?MODULE, []). init(_Args) -> @@ -32,3 +36,23 @@ announce(Message) -> Children = supervisor:which_children(?MODULE), [ openomf_lobby_client:announce(Pid, Message) || {_Id, Pid, _Type, _Modules} <- Children]. +%% Activity tracking functions +record_last_match(Winner, Loser) -> + Timestamp = erlang:system_time(second), + ets:insert(lobby_activity, {last_match, {Timestamp, Winner, Loser}}). + +record_last_activity(PlayerName) -> + Timestamp = erlang:system_time(second), + ets:insert(lobby_activity, {last_activity, {Timestamp, PlayerName}}). + +get_last_match() -> + case ets:lookup(lobby_activity, last_match) of + [{last_match, Data}] -> {ok, Data}; + [] -> undefined + end. + +get_last_activity() -> + case ets:lookup(lobby_activity, last_activity) of + [{last_activity, Data}] -> {ok, Data}; + [] -> undefined + end. diff --git a/src/openomf_lobby_match.erl b/src/openomf_lobby_match.erl index 9f107ef..1254184 100644 --- a/src/openomf_lobby_match.erl +++ b/src/openomf_lobby_match.erl @@ -4,7 +4,7 @@ -behaviour(gen_statem). --export([start_link/3]). +-export([start_link/4]). -export([init/1,callback_mode/0, terminate/3]). @@ -12,6 +12,7 @@ -record(pilot, { har_id :: non_neg_integer(), + pilot_id :: non_neg_integer(), power :: non_neg_integer(), agility :: non_neg_integer(), endurance :: non_neg_integer(), @@ -35,7 +36,10 @@ challenger_won = undefined :: undefined | boolean(), challengee_won = undefined :: undefined | boolean(), events = maps:new() :: map(), - arena_id :: non_neg_integer() + arena_id :: non_neg_integer(), + subscribers = [] :: [pid()], + proposed_rands = [] :: [{integer(), integer()}], + confirmed_rand = 0 :: integer() }). -define(QUIPS, ["~s sent ~s straight to the scrap heap... with a warranty void receipt!", @@ -51,17 +55,24 @@ -define(PACKET_ANNOUNCEMENT, 9). -define(EVENT_TYPE_ACTION, 0). +-define(EVENT_TYPE_PROPOSE_START, 2). +-define(EVENT_TYPE_CONFIRM_START, 3). -define(EVENT_TYPE_GAME_INFO, 4). -start_link(ChallengerPid, ChallengerInfo, ChallengeePid) -> - gen_statem:start_link(?MODULE, [ChallengerPid, ChallengerInfo, ChallengeePid], []). +start_link(ChallengerPid, ChallengerInfo, ChallengeePid, ChallengeeID) -> + gen_statem:start_link(?MODULE, [ChallengerPid, ChallengerInfo, ChallengeePid, ChallengeeID], []). -init([ChallengerPid, ChallengerInfo, ChallengeePid]) -> - %% die if either party's process exits - %% clients trap_exit so they'll get a message - link(ChallengerPid), - link(ChallengeePid), - {ok, starting, #state{challenger_pid=ChallengerPid, challenger_info=ChallengerInfo, challengee_pid=ChallengeePid}}. +init([ChallengerPid, ChallengerInfo, ChallengeePid, ChallengeeID]) -> + case gproc:reg({n, l, {match, maps:get(connect_id, ChallengerInfo), ChallengeeID}}, self()) of + true -> + %% die if either party's process exits + %% clients trap_exit so they'll get a message + link(ChallengerPid), + link(ChallengeePid), + {ok, starting, #state{challenger_pid=ChallengerPid, challenger_info=ChallengerInfo, challengee_pid=ChallengeePid}}; + _ -> + {stop, normal} + end. callback_mode() -> [state_functions,state_enter]. @@ -145,24 +156,105 @@ connected(cast, {done, ChallengeePid, WonOrLost}, Data = #state{challengee_pid = lager:info("challengee won: ~p", [WonOrLost == 1]), NewData = Data#state{challengee_won = WonOrLost == 1}, check_winner(NewData); +connected({call, From}, {subscribe, Channel, Pid, SuccessFun}, Data = #state{subscribers=Subs, events=Events}) -> + %% monitor pid so if it dies we can remove it + Ref = erlang:monitor(process, Pid), + + SuccessFun(), + + %% if both players have picked their pilots, send the info any any confirmed inputs + case Data#state.challenger_pilot /= undefined andalso Data#state.challengee_pilot /= undefined of + true -> + Packet0 = encode_match_data(Data#state.challenger_pilot, Data#state.challengee_pilot, Data#state.arena_id, Data#state.confirmed_rand), + enet:send_reliable(Channel, Packet0), + %% send the whole transcript, up to the last confirm frame + L = lists:keysort(1, maps:to_list(Events)), + Packet = encode_inputs(L, []), + %% this might be big, so break it into 500 byte chunks + Packets = packetize(Packet, 500), + [ enet:send_reliable(Channel, iolist_to_binary([<<1:8/integer-unsigned>>, P])) || P <- Packets ]; + false -> + ok + end, + %% add the pid to the subscription list + {keep_state, Data#state{subscribers = [{Channel, Ref} | Subs]}, [{reply, From, ok}]}; connected(Type, Event, Data) -> handle_event(?FUNCTION_NAME, Type, Event, Data). +handle_event(connected, cast, {enet, Pid, 2, #reliable{ data = <>}}, Data) -> + Pilot = #pilot{har_id = HARId, pilot_id = PilotId, power = Power, endurance = Endurance, agility = Agility, primary_color = PrimaryColor, secondary_color = SecondaryColor, tertiary_color = TertiaryColor, name = Name}, + NewData = case Pid of + _ when Pid == Data#state.challenger_pid andalso Data#state.challenger_pilot == undefined -> + Data#state{challenger_pilot = Pilot, arena_id =ArenaID}; + _ when Pid == Data#state.challengee_pid andalso Data#state.challengee_pilot == undefined -> + Data#state{challengee_pilot = Pilot, arena_id = ArenaID}; + _ -> + Data + end, + %% check if we have just now gotten information from both sides + case Data /= NewData andalso NewData#state.challenger_pilot /= undefined andalso NewData#state.challengee_pilot /= undefined of + true -> + %% send the starting information to all waiting subscribers + lists:foreach( + fun({Channel, _Ref}) -> + Packet0 = encode_match_data(NewData#state.challenger_pilot, NewData#state.challengee_pilot, NewData#state.arena_id, Data#state.confirmed_rand), + enet:send_reliable(Channel, Packet0) + end, NewData#state.subscribers); + false -> + ok + end, + + {keep_state, NewData}; + + +handle_event(connected, cast, {enet, _Pid, 2, #reliable{ data = <>}}, Data) -> + lager:info("got proposed seed ~p at ~p", [Seed, PeerProposal]), + {keep_state, Data#state{proposed_rands = [{PeerProposal, Seed}|Data#state.proposed_rands]}}; + +handle_event(connected, cast, {enet, _Pid, 2, #reliable{ data = <>}}, Data) -> + lager:info("saw confirmation of seed ~p at ~p", [lists:keyfind(PeerProposal, 1, Data#state.proposed_rands), PeerProposal]), + Seed = case lists:keyfind(PeerProposal, 1, Data#state.proposed_rands) of + {PeerProposal, S} -> + S; + _ -> 0 + end, + {keep_state, Data#state{confirmed_rand=Seed}}; + + handle_event(connected, cast, {enet, Pid, 2, #reliable{ data = <>}}, Data) -> - Pilot = #pilot{har_id = HARId, power = Power, endurance = Endurance, agility = Agility, primary_color = PrimaryColor, secondary_color = SecondaryColor, tertiary_color = TertiaryColor, name = Name}, + %% XXX legacy version, remove after 0.8.2+ + Pilot = #pilot{har_id = HARId, pilot_id = 0, power = Power, endurance = Endurance, agility = Agility, primary_color = PrimaryColor, secondary_color = SecondaryColor, tertiary_color = TertiaryColor, name = Name}, NewData = case Pid of - _ when Pid == Data#state.challenger_pid -> + _ when Pid == Data#state.challenger_pid andalso Data#state.challenger_pilot == undefined -> Data#state{challenger_pilot = Pilot, arena_id =ArenaID}; - _ when Pid == Data#state.challengee_pid -> + _ when Pid == Data#state.challengee_pid andalso Data#state.challengee_pilot == undefined -> Data#state{challengee_pilot = Pilot, arena_id = ArenaID}; _ -> Data end, + %% check if we have just now gotten information from both sides + case Data /= NewData andalso NewData#state.challenger_pilot /= undefined andalso NewData#state.challengee_pilot /= undefined of + true -> + %% send the starting information to all waiting subscribers + lists:foreach( + fun({Channel, _Ref}) -> + Packet0 = encode_match_data(NewData#state.challenger_pilot, NewData#state.challengee_pilot, NewData#state.arena_id, Data#state.confirmed_rand), + enet:send_reliable(Channel, Packet0) + end, NewData#state.subscribers); + false -> + ok + end, + {keep_state, NewData}; -handle_event(connected, cast, {enet, Pid, 2, #unsequenced{ data = <>}}, Data) -> + + +handle_event(connected, cast, {enet, Pid, 2, #unsequenced{ data = <>}}, Data) -> %% 0.8.1 lacked the backup ticks field {BakTicks, FrameAdvantage, Rest} = @@ -200,7 +292,10 @@ handle_event(connected, cast, {enet, Pid, 2, #unsequenced{ data = < lager:info("unhandled enet event ~p", [_Event]), keep_state_and_data; @@ -224,13 +319,14 @@ handle_event(_State, cast, {done, _Pid}, _Data) -> handle_event(_State, state_timeout, finish_match, _Data) -> lager:warning("ending match pid after one side failed to ever finish"), {stop, normal}; +handle_event(_State, info, {'DOWN', Ref, _, _}, Data = #state{subscribers = Subs}) -> + {keep_state, Data#state{subscribers=lists:filter(fun({_Channel, Ref0}) -> Ref == Ref0 end, Subs)}}; handle_event(State, Type, Event, _Data) -> lager:info("got unhandled event ~p ~p in state ~p", [Type, Event, State]), keep_state_and_data. terminate(_Reason, _State, Data) when Data#state.challengee_info /= undefined -> L = lists:keysort(1, maps:to_list(Data#state.events)), - Text = unicode:characters_to_binary(io_lib:format("~tp.~n", [{{arena_id, Data#state.arena_id}, Data#state.challenger_pilot, Data#state.challengee_pilot, L}])), Filename = lists:flatten(io_lib:format("/tmp/matches/~s-~s-~s.match", [maps:get(name, Data#state.challenger_info, undefined), maps:get(name, Data#state.challengee_info, undefined), iso8601:format(calendar:universal_time())])), filelib:ensure_dir(Filename), @@ -285,6 +381,8 @@ quip(Winner, Loser) -> Quip = lists:nth(E, ?QUIPS), Bin = iolist_to_binary(io_lib:format(Quip, [Winner, Loser])), enet:broadcast_reliable(2098, 0, <>), + %% Record the match completion for activity tracking + openomf_lobby_client_sup:record_last_match(Winner, Loser), case application:get_env(discord_callback) of undefined -> ok; {ok, URL} -> @@ -312,3 +410,47 @@ get_inputs(<<0:8/integer-unsigned, Rest/binary>>, Acc) -> {lists:reverse(Acc), Rest}; get_inputs(<>, Acc) -> get_inputs(Rest, [A|Acc]). + +%% find the last confirm frame +confirm_frame(Events, LastConfirmed) -> + L = lists:keysort(1, maps:to_list(Events)), + {ChallengerLastReceiveds, ChallengeeLastReceiveds} = lists:unzip([{maps:get(challenger_last_received_tick, E, 0), maps:get(challengee_last_received_tick, E, 0)} || {T, E} <- L, T >= LastConfirmed]), + ChallengerLastReceived = max(LastConfirmed, lists:max([0 | [ E || E <- ChallengerLastReceiveds]])), + ChallengeeLastReceived = max(LastConfirmed, lists:max([0 | [ E || E <- ChallengeeLastReceiveds]])), + min(ChallengerLastReceived, ChallengeeLastReceived). + +notify_subscribers(Data = #state{subscribers=Subs, events=Events}, PreviousConfirmed) -> + Confirmed = confirm_frame(Data#state.events, PreviousConfirmed), + %% find all the new events between the previous confirmed frame and the new one that have events + L = [E || {T, M} = E <- lists:keysort(1, maps:to_list(Events)), T > PreviousConfirmed, T =< Confirmed, maps:is_key(challenger_events, M) orelse maps:is_key(challengee_events, M) ], + Packet = encode_inputs(L, []), + [ enet:send_reliable(Channel, iolist_to_binary([<<1:8/integer-unsigned>>, Packet])) || {Channel, _Pid} <- Subs ], + ok. + +encode_inputs([], Acc) -> + lists:reverse(Acc); +encode_inputs([{Tick, Map}|T], Acc) -> + P = [<>, << <> || I <- maps:get(challenger_events, Map, []) ++ [0] >>, << <> || I <- maps:get(challengee_events, Map, []) ++ [0] >>], + lager:info("encoding event packet ~p", [P]), + encode_inputs(T, [P | Acc]). +packetize(IoList, Size) -> + packetize(IoList, Size, [], []). + +packetize([], _Size, LastPacket, Packets) -> + lists:reverse([lists:reverse(LastPacket)|Packets]); +packetize([Head | Tail]=Input, Size, ThisPacket, Packets) -> + Proposed = [Head|ThisPacket], + case iolist_size(Proposed) > Size of + false -> + packetize(Tail, Size, Proposed, Packets); + true -> + packetize(Input, Size, [], [lists:reverse(ThisPacket)|Packets]) + end. + +encode_match_data(Pilot1, Pilot2, ArenaID, Seed) -> + iolist_to_binary([<<0:8/integer>>, encode_pilot(Pilot1), encode_pilot(Pilot2), <>]). + +encode_pilot(#pilot{har_id = HARId, pilot_id = PilotId, power = Power, agility = Agility, endurance = Endurance, primary_color = C1, secondary_color = C2, tertiary_color = C3, name = Name}) -> + lager:info("encoding pilot ~p", [Name]), + NameLen = byte_size(Name), + <>. diff --git a/src/openomf_lobby_match_sup.erl b/src/openomf_lobby_match_sup.erl index 55ea60d..5265479 100644 --- a/src/openomf_lobby_match_sup.erl +++ b/src/openomf_lobby_match_sup.erl @@ -3,7 +3,7 @@ -behaviour(supervisor). -export([start_link/0, - start_match/3]). + start_match/4]). -export([init/1]). @@ -21,5 +21,5 @@ init(_Args) -> shutdown => brutal_kill}], {ok, {SupFlags, ChildSpecs}}. -start_match(ChallengerPid, ChallengerInfo, ChallengeePid) -> - supervisor:start_child(?MODULE, [ChallengerPid, ChallengerInfo, ChallengeePid]). +start_match(ChallengerPid, ChallengerInfo, ChallengeePid, ChallengeeID) -> + supervisor:start_child(?MODULE, [ChallengerPid, ChallengerInfo, ChallengeePid, ChallengeeID]).