Skip to content

Commit c1b0c6f

Browse files
committed
Lobby api
1 parent 025678f commit c1b0c6f

6 files changed

Lines changed: 511 additions & 3 deletions

File tree

api/connection.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ end
122122

123123
MPAPI.is_connected = function()
124124
if _connection then
125-
return _connection:get_state()
125+
return _connection:get_state() == 'connected'
126126
end
127-
return 'disconnected'
127+
return false
128128
end
129129

130130
MPAPI.get_mqtt = function()
@@ -297,6 +297,11 @@ connection_on_state_change = function(new_state, context)
297297
log_state_update(new_state, context)
298298
end
299299

300+
-- Auto-create lobby object on reconnection
301+
if new_state == 'connected' and context.reconnected_lobby and MPAPI._internal.create_reconnected_lobby then
302+
MPAPI._internal.create_reconnected_lobby(context.reconnected_lobby)
303+
end
304+
300305
run_new_state_user_callbacks(new_state, context)
301306

302307
for _, fn in ipairs(_state_change_callbacks) do

api/lobby.lua

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
-- Forward declarations for helper functions
2+
local create_lobby_callback
3+
local create_lobby_object
4+
local subscribe_all
5+
local handle_event
6+
local handle_metadata
7+
local handle_own_state
8+
local cleanup_lobby
9+
10+
-----------------------------
11+
-- STATE VARIABLES
12+
-----------------------------
13+
14+
local _current_lobby = nil
15+
16+
-----------------------------
17+
-- API FUNCTIONS
18+
-----------------------------
19+
20+
MPAPI.create_lobby = function(mod_id, opts)
21+
opts = opts or {}
22+
local conn = MPAPI.get_connection()
23+
local mqtt = MPAPI.get_mqtt()
24+
25+
if not conn or conn:get_state() ~= 'connected' then
26+
MPAPI.sendWarnMessage('create_lobby: not connected')
27+
return nil
28+
end
29+
30+
if not mqtt then
31+
MPAPI.sendWarnMessage('create_lobby: MQTT not available')
32+
return nil
33+
end
34+
35+
local lobby = create_lobby_object({
36+
mod_id = mod_id,
37+
player_id = conn.player_id,
38+
mqtt = mqtt,
39+
api = conn.api,
40+
connection = conn,
41+
})
42+
43+
_current_lobby = lobby
44+
45+
conn.api:create_lobby(conn.jwt_token, mod_id, opts.max_players, create_lobby_callback)
46+
47+
return lobby
48+
end
49+
50+
MPAPI.join_lobby = function(mod_id, code, opts)
51+
opts = opts or {}
52+
local conn = MPAPI.get_connection()
53+
local mqtt = MPAPI.get_mqtt()
54+
55+
if not conn or conn:get_state() ~= 'connected' then
56+
MPAPI.sendWarnMessage('join_lobby: not connected')
57+
return nil
58+
end
59+
60+
if not mqtt then
61+
MPAPI.sendWarnMessage('join_lobby: MQTT not available')
62+
return nil
63+
end
64+
65+
local lobby = create_lobby_object({
66+
mod_id = mod_id,
67+
player_id = conn.player_id,
68+
mqtt = mqtt,
69+
api = conn.api,
70+
connection = conn,
71+
})
72+
73+
_current_lobby = lobby
74+
75+
conn.api:join_lobby(conn.jwt_token, code, )
76+
77+
return lobby
78+
end
79+
80+
MPAPI.get_current_lobby = function()
81+
return _current_lobby
82+
end
83+
84+
-----------------------------
85+
-- INTERNAL FUNCTIONS
86+
-----------------------------
87+
88+
MPAPI._internal.create_reconnected_lobby = function(lobby_data)
89+
local conn = MPAPI.get_connection()
90+
local mqtt = MPAPI.get_mqtt()
91+
92+
if not conn or not mqtt then
93+
return
94+
end
95+
96+
local lobby = create_lobby_object({
97+
code = lobby_data.code,
98+
mod_id = lobby_data.modId,
99+
is_host = lobby_data.isHost or false,
100+
player_id = conn.player_id,
101+
mqtt = mqtt,
102+
api = conn.api,
103+
connection = conn,
104+
metadata = lobby_data.metadata or {},
105+
})
106+
107+
subscribe_all(lobby)
108+
_current_lobby = lobby
109+
lobby:_fire('connected')
110+
end
111+
112+
-----------------------------
113+
-- HELPER FUNCTIONS
114+
-----------------------------
115+
116+
join_lobby_callback = function(err, data)
117+
if err then
118+
_current_lobby:_fire('error', err)
119+
return
120+
end
121+
122+
conn.jwt_token = data.token
123+
124+
_current_lobby.code = data.lobby.code
125+
_current_lobby.is_host = data.lobby.isHost or false
126+
lo_current_lobbybby._metadata = data.lobby.metadata or {}
127+
128+
subscribe_all(_current_lobby)
129+
_current_lobby:_fire('connected')
130+
end
131+
132+
create_lobby_callback = function(err, data)
133+
if err then
134+
_current_lobby:_fire('error', err)
135+
return
136+
end
137+
138+
conn.jwt_token = data.token
139+
140+
_current_lobby.code = data.lobby.code
141+
_current_lobby.is_host = true
142+
_current_lobby._metadata = data.lobby.metadata or {}
143+
144+
subscribe_all(_current_lobby)
145+
_current_lobby:_fire('connected')
146+
end
147+
148+
create_lobby_object = function(opts)
149+
local lobby = {
150+
code = opts.code,
151+
mod_id = opts.mod_id,
152+
is_host = opts.is_host or false,
153+
player_id = opts.player_id,
154+
_mqtt = opts.mqtt,
155+
_api = opts.api,
156+
_connection = opts.connection,
157+
_metadata = opts.metadata or {},
158+
_player_state = nil,
159+
_players = {},
160+
_event_handlers = {},
161+
_destroyed = false,
162+
}
163+
164+
function lobby:on(event_name, handler)
165+
if not self._event_handlers[event_name] then
166+
self._event_handlers[event_name] = {}
167+
end
168+
local handlers = self._event_handlers[event_name]
169+
handlers[#handlers + 1] = handler
170+
end
171+
172+
function lobby:_fire(event_name, ...)
173+
local handlers = self._event_handlers[event_name]
174+
if not handlers then
175+
return
176+
end
177+
for _, handler in ipairs(handlers) do
178+
local ok, err = pcall(handler, ...)
179+
if not ok then
180+
MPAPI.sendWarnMessage('Lobby event "' .. event_name .. '" handler error: ' .. tostring(err))
181+
end
182+
end
183+
end
184+
185+
function lobby:set_metadata(tbl)
186+
if self._destroyed then
187+
return
188+
end
189+
if not self.is_host then
190+
self:_fire('error', 'Only the host can set metadata')
191+
return
192+
end
193+
self._api:set_lobby_metadata(self._connection.jwt_token, self.code, tbl, function(err, data)
194+
if err then
195+
self:_fire('error', err)
196+
return
197+
end
198+
if data and data.metadata then
199+
self._metadata = data.metadata
200+
end
201+
end)
202+
end
203+
204+
function lobby:get_metadata()
205+
return self._metadata
206+
end
207+
208+
function lobby:set_player_state(tbl)
209+
if self._destroyed then
210+
return
211+
end
212+
local topic = self._mqtt:lobby_topic(self.code, 'players/' .. self.player_id .. '/state')
213+
self._mqtt:publish(topic, MPAPI.json_encode(tbl), 1, true)
214+
self._player_state = tbl
215+
end
216+
217+
function lobby:get_player_state()
218+
return self._player_state
219+
end
220+
221+
function lobby:get_players()
222+
local result = {}
223+
for _, player in pairs(self._players) do
224+
result[#result + 1] = player
225+
end
226+
return result
227+
end
228+
229+
function lobby:leave()
230+
if self._destroyed then
231+
return
232+
end
233+
self._api:leave_lobby(self._connection.jwt_token, self.code, function(err, data)
234+
if err then
235+
self:_fire('error', err)
236+
return
237+
end
238+
if data and data.token then
239+
self._connection.jwt_token = data.token
240+
end
241+
cleanup_lobby(self)
242+
self:_fire('disconnected')
243+
end)
244+
end
245+
246+
return lobby
247+
end
248+
249+
subscribe_all = function(lobby)
250+
if not lobby._mqtt or not lobby.code then
251+
return
252+
end
253+
254+
local events_topic = lobby._mqtt:lobby_topic(lobby.code, 'events')
255+
lobby._mqtt:subscribe(events_topic, 1, function(topic, payload)
256+
handle_event(lobby, payload)
257+
end)
258+
259+
local metadata_topic = lobby._mqtt:lobby_topic(lobby.code, 'metadata')
260+
lobby._mqtt:subscribe(metadata_topic, 1, function(topic, payload)
261+
handle_metadata(lobby, payload)
262+
end)
263+
264+
local state_topic = lobby._mqtt:lobby_topic(lobby.code, 'players/' .. lobby.player_id .. '/state')
265+
lobby._mqtt:subscribe(state_topic, 1, function(topic, payload)
266+
handle_own_state(lobby, payload)
267+
end)
268+
end
269+
270+
handle_event = function(lobby, payload)
271+
if lobby._destroyed then
272+
return
273+
end
274+
275+
local ok, data = pcall(MPAPI.json_decode, payload)
276+
if not ok or not data or not data.type then
277+
return
278+
end
279+
280+
local event_type = data.type
281+
282+
if event_type == 'player_joined' then
283+
if data.playerId then
284+
lobby._players[data.playerId] = {
285+
id = data.playerId,
286+
displayName = data.displayName,
287+
is_away = false,
288+
}
289+
end
290+
lobby:_fire('player_joined', data.playerId)
291+
elseif event_type == 'player_left' then
292+
if data.playerId then
293+
lobby._players[data.playerId] = nil
294+
end
295+
lobby:_fire('player_left', data.playerId)
296+
elseif event_type == 'player_disconnected' then
297+
if data.playerId and lobby._players[data.playerId] then
298+
lobby._players[data.playerId].is_away = true
299+
end
300+
lobby:_fire('player_disconnected', data.playerId)
301+
elseif event_type == 'player_reconnected' then
302+
if data.playerId and lobby._players[data.playerId] then
303+
lobby._players[data.playerId].is_away = false
304+
end
305+
lobby:_fire('player_reconnected', data.playerId)
306+
elseif event_type == 'metadata_changed' then
307+
if data.data then
308+
lobby._metadata = data.data
309+
end
310+
lobby:_fire('metadata_changed', lobby._metadata)
311+
elseif event_type == 'host_changed' then
312+
if data.playerId then
313+
lobby.is_host = (data.playerId == lobby.player_id)
314+
end
315+
lobby:_fire('host_changed', data.playerId)
316+
elseif event_type == 'lobby_closed' then
317+
cleanup_lobby(lobby)
318+
lobby:_fire('disconnected')
319+
end
320+
end
321+
322+
handle_metadata = function(lobby, payload)
323+
if lobby._destroyed then
324+
return
325+
end
326+
327+
local ok, data = pcall(MPAPI.json_decode, payload)
328+
if not ok or not data then
329+
return
330+
end
331+
332+
lobby._metadata = data
333+
lobby:_fire('metadata_changed', lobby._metadata)
334+
end
335+
336+
handle_own_state = function(lobby, payload)
337+
if lobby._destroyed then
338+
return
339+
end
340+
341+
local ok, data = pcall(MPAPI.json_decode, payload)
342+
if not ok or not data then
343+
return
344+
end
345+
346+
lobby._player_state = data
347+
end
348+
349+
cleanup_lobby = function(lobby)
350+
if lobby._destroyed then
351+
return
352+
end
353+
lobby._destroyed = true
354+
355+
if lobby._mqtt and lobby.code then
356+
lobby._mqtt:unsubscribe(lobby._mqtt:lobby_topic(lobby.code, 'events'))
357+
lobby._mqtt:unsubscribe(lobby._mqtt:lobby_topic(lobby.code, 'metadata'))
358+
lobby._mqtt:unsubscribe(lobby._mqtt:lobby_topic(lobby.code, 'players/' .. lobby.player_id .. '/state'))
359+
end
360+
361+
if _current_lobby == lobby then
362+
_current_lobby = nil
363+
end
364+
end

0 commit comments

Comments
 (0)