about summary refs log tree commit diff stats
path: root/apworld/client/multiplayerManager.gd
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2026-02-10 15:39:32 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2026-02-10 15:39:32 -0500
commit510ca12f40a8db44e6ca962089bc0c6363ba1e19 (patch)
tree6dfa36051a936f2bafc4947acc4c8349c8f11572 /apworld/client/multiplayerManager.gd
parent8f5184fdb2545b46d930af17e99be76f59516906 (diff)
downloadlingo2-archipelago-multiplayer.tar.gz
lingo2-archipelago-multiplayer.tar.bz2
lingo2-archipelago-multiplayer.zip
Initial multiplayer stuff multiplayer
Diffstat (limited to 'apworld/client/multiplayerManager.gd')
-rw-r--r--apworld/client/multiplayerManager.gd264
1 files changed, 264 insertions, 0 deletions
diff --git a/apworld/client/multiplayerManager.gd b/apworld/client/multiplayerManager.gd new file mode 100644 index 0000000..dd4499f --- /dev/null +++ b/apworld/client/multiplayerManager.gd
@@ -0,0 +1,264 @@
1extends Node
2
3var player_steam_id
4var player_node
5var active_lobby_id = 0
6var active_lobby_members = {}
7var is_remote_signal = false
8var next_message_id = 0
9var messages_needing_ack = {}
10
11const MAX_PLAYERS = 250
12const PROTOCOL_VERSION = 4000
13const RECIPIENT_BROADCAST_ALL = -1
14
15
16func _ready():
17 # P2P solve messages should still be received while paused.
18 set_process_mode(Node.PROCESS_MODE_ALWAYS)
19
20 player_steam_id = Steam.getSteamID()
21 player_node = get_node("/root/scene/player")
22
23 Steam.lobby_match_list.connect(_on_lobby_match_list)
24 Steam.lobby_created.connect(_on_lobby_created)
25 Steam.lobby_joined.connect(_on_lobby_joined)
26 Steam.lobby_chat_update.connect(_on_lobby_chat_update)
27 Steam.lobby_data_update.connect(_on_lobby_data_update)
28 Steam.persona_state_change.connect(_on_persona_state_change)
29 Steam.p2p_session_request.connect(_on_p2p_session_request)
30
31 _setup_recurring_task(0.1, _broadcast_player_location)
32 _setup_recurring_task(5.0, _resend_messages_needing_ack)
33 _setup_recurring_task(10.0, _request_lobby_list)
34
35 _request_lobby_list()
36
37
38func _process(_delta: float) -> void:
39 _read_p2p_packet()
40
41
42func _exit_tree():
43 if active_lobby_id != 0:
44 Steam.leaveLobby(active_lobby_id)
45
46
47func _setup_recurring_task(wait_time, function):
48 var timer = Timer.new()
49 timer.set_wait_time(wait_time)
50 timer.set_one_shot(false)
51 timer.timeout.connect(function)
52 add_child(timer)
53 timer.start()
54
55
56func _broadcast_player_location():
57 if active_lobby_id == 0:
58 return
59 _send_p2p_packet(
60 {
61 "global_transform": player_node.global_transform,
62 },
63 RECIPIENT_BROADCAST_ALL,
64 Steam.P2P_SEND_UNRELIABLE_NO_DELAY,
65 false
66 )
67
68
69func _resend_messages_needing_ack():
70 for message_id in messages_needing_ack:
71 var message = messages_needing_ack[message_id]
72 if message["recipient_id"] in active_lobby_members:
73 _send_p2p_packet(message["data"], message["recipient_id"], message["mode"], true)
74 else:
75 messages_needing_ack.erase(message_id)
76
77
78func _request_lobby_list():
79 var ap = global.get_node("Archipelago")
80 if !ap._already_connected:
81 return
82
83 Steam.addRequestLobbyListDistanceFilter(Steam.LOBBY_DISTANCE_FILTER_WORLDWIDE)
84 Steam.addRequestLobbyListNumericalFilter(
85 "protocol_version", PROTOCOL_VERSION, Steam.LOBBY_COMPARISON_EQUAL
86 )
87 Steam.addRequestLobbyListStringFilter("map", global.map, Steam.LOBBY_COMPARISON_EQUAL)
88 Steam.addRequestLobbyListStringFilter("seed", ap.client._seed, Steam.LOBBY_COMPARISON_EQUAL)
89 Steam.requestLobbyList()
90
91
92func _on_lobby_match_list(lobbies: Array) -> void:
93 if active_lobby_id != 0 && not active_lobby_id in lobbies:
94 # Not sure why this happens, but it seems to sometimes.
95 lobbies.append(active_lobby_id)
96 var best_lobby_id = 0
97 var best_lobby_size = -1
98 for lobby_id in lobbies:
99 var lobby_size = Steam.getNumLobbyMembers(lobby_id)
100 if (
101 lobby_size > best_lobby_size
102 || (lobby_size == best_lobby_size && lobby_id < best_lobby_id)
103 ):
104 best_lobby_id = lobby_id
105 best_lobby_size = lobby_size
106 if best_lobby_id == 0:
107 Steam.createLobby(Steam.LOBBY_TYPE_INVISIBLE, MAX_PLAYERS)
108 elif best_lobby_id != active_lobby_id:
109 Steam.joinLobby(best_lobby_id)
110 elif best_lobby_size <= 1:
111 _request_lobby_list()
112
113
114func _on_lobby_created(result: int, lobby_id: int) -> void:
115 if result != Steam.RESULT_OK:
116 return
117 var ap = global.get_node("Archipelago")
118 if !ap._already_connected:
119 return
120 var _ignore = Steam.setLobbyData(lobby_id, "map", global.map)
121 _ignore = Steam.setLobbyData(lobby_id, "protocol_version", str(PROTOCOL_VERSION))
122 _ignore = Steam.setLobbyData(lobby_id, "seed", ap.client._seed)
123 _request_lobby_list()
124
125
126func _on_lobby_joined(lobby_id: int, _permissions: int, _locked: bool, result: int) -> void:
127 if result != Steam.RESULT_OK:
128 return
129 if active_lobby_id != 0 && active_lobby_id != lobby_id:
130 Steam.leaveLobby(active_lobby_id)
131 active_lobby_id = lobby_id
132 _update_lobby_members()
133 # Broadcast out all our solves in case we have existing save data.
134 _send_hi(RECIPIENT_BROADCAST_ALL)
135
136
137func _on_lobby_chat_update(
138 _lobby_id: int, member_id: int, _making_change_id: int, chat_state: int
139) -> void:
140 _update_lobby_members()
141 # New user joined, so send them all our already solved panels.
142 if chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED:
143 _send_hi(member_id)
144
145
146func _send_hi(recipient_id):
147 pass
148 #_send_p2p_packet(
149 # {
150 # "hi": true,
151 # "solves": solves,
152 # },
153 # recipient_id,
154 # Steam.P2P_SEND_RELIABLE,
155 # true
156 #)
157
158
159func _on_lobby_data_update(_lobby_id: int, _member_id: int, _key: int) -> void:
160 _update_lobby_members()
161
162
163func _on_persona_state_change(_persona_id: int, _flag: int) -> void:
164 _update_lobby_members()
165
166
167func _update_lobby_members():
168 if active_lobby_id == 0:
169 return
170 var lobby_size: int = Steam.getNumLobbyMembers(active_lobby_id)
171 var prior_lobby_members = active_lobby_members
172 active_lobby_members = {}
173 for i in range(0, lobby_size):
174 var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i)
175 if member_id != player_steam_id:
176 var steam_name = Steam.getFriendPersonaName(member_id)
177 print(steam_name)
178 if member_id in prior_lobby_members:
179 active_lobby_members[member_id] = prior_lobby_members[member_id]
180 else:
181 active_lobby_members[member_id] = (
182 load("res://objects/nodes/multiplayer/avatar.tscn").instantiate()
183 )
184 active_lobby_members[member_id].set_steam_name(steam_name)
185 get_node("/root/scene").add_child.call_deferred(active_lobby_members[member_id])
186 active_lobby_members[member_id].steam_id = member_id
187 active_lobby_members[member_id].steam_name = steam_name
188 active_lobby_members[member_id].material = "transparentWhite"
189 for member in prior_lobby_members.values():
190 if not member.steam_id in active_lobby_members:
191 member.queue_free()
192
193
194func _on_p2p_session_request(remote_id: int) -> void:
195 if remote_id in active_lobby_members:
196 var _ignore = Steam.acceptP2PSessionWithUser(remote_id)
197
198
199func _read_p2p_packet() -> void:
200 var packet_size: int = Steam.getAvailableP2PPacketSize(0)
201 if packet_size > 0:
202 var packet: Dictionary = Steam.readP2PPacket(packet_size, 0)
203 var remote_id = packet["remote_steam_id"]
204 if remote_id in active_lobby_members:
205 var serialized: PackedByteArray = packet["data"]
206 var data: Dictionary = bytes_to_var(serialized)
207 if "hi" in data:
208 _receive_hi(remote_id)
209 if "global_transform" in data:
210 _receive_member_location(remote_id, data["global_transform"])
211 #if "solves" in data:
212 # for solve in data["solves"]:
213 # _receive_solve(solve)
214 #if "unsolves" in data:
215 # for unsolve in data["unsolves"]:
216 # _receive_unsolve(unsolve)
217 if "message_id" in data:
218 _send_p2p_packet(
219 {
220 "ack": data["message_id"],
221 },
222 remote_id,
223 Steam.P2P_SEND_RELIABLE_WITH_BUFFERING,
224 false
225 )
226 if "ack" in data:
227 messages_needing_ack.erase(data["ack"])
228
229
230func _receive_hi(member_id: int) -> void:
231 var member = active_lobby_members[member_id]
232 member.said_hi = true
233
234
235func _receive_member_location(member_id: int, global_transform) -> void:
236 var member = active_lobby_members[member_id]
237 member.global_move_to(global_transform)
238 if !member.visible:
239 member.show()
240
241
242func _send_p2p_packet(data: Dictionary, recipient_id: int, mode: int, needs_ack: bool) -> void:
243 if recipient_id == RECIPIENT_BROADCAST_ALL:
244 for member_id in active_lobby_members.keys():
245 _send_p2p_packet(data.duplicate(), member_id, mode, needs_ack)
246 return
247
248 if needs_ack:
249 var message_id
250 if "message_id" in data:
251 message_id = data["message_id"]
252 else:
253 message_id = next_message_id
254 next_message_id += 1
255 data["message_id"] = message_id
256 if not message_id in messages_needing_ack:
257 messages_needing_ack[message_id] = {
258 "data": data,
259 "recipient_id": recipient_id,
260 "mode": mode,
261 }
262 var serialized: PackedByteArray = []
263 serialized.append_array(var_to_bytes(data))
264 var _ignore = Steam.sendP2PPacket(recipient_id, serialized, mode)