summary refs log tree commit diff stats
path: root/racing/multiplayer.gd
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-02-08 16:29:48 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2024-02-08 16:29:48 -0500
commit60a0a573f0d0329e8d5c73878165ba80fa2d2628 (patch)
treebe2c9519c47ca3aa28f539587aae4ef9274b9e2d /racing/multiplayer.gd
downloadlingo-race-60a0a573f0d0329e8d5c73878165ba80fa2d2628.tar.gz
lingo-race-60a0a573f0d0329e8d5c73878165ba80fa2d2628.tar.bz2
lingo-race-60a0a573f0d0329e8d5c73878165ba80fa2d2628.zip
Initial commit
Diffstat (limited to 'racing/multiplayer.gd')
-rw-r--r--racing/multiplayer.gd293
1 files changed, 293 insertions, 0 deletions
diff --git a/racing/multiplayer.gd b/racing/multiplayer.gd new file mode 100644 index 0000000..1cd3b26 --- /dev/null +++ b/racing/multiplayer.gd
@@ -0,0 +1,293 @@
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 = {}
10var panel_nodes
11var is_ready = false
12var is_loaded = false
13var is_activating = false
14var letsgo = false
15var is_victory = false
16var victorious_players = []
17
18const MAX_PLAYERS = 250
19const PROTOCOL_VERSION = 2
20const RECIPIENT_BROADCAST_ALL = -1
21
22
23func _ready():
24 # P2P solve messages should still be received while paused.
25 set_pause_mode(Node.PAUSE_MODE_PROCESS)
26
27 player_steam_id = Steam.getSteamID()
28 player_node = get_node("/root/Spatial/player")
29
30 var _ignore = Steam.connect("lobby_match_list", self, "_on_lobby_match_list")
31 _ignore = Steam.connect("lobby_created", self, "_on_lobby_created")
32 _ignore = Steam.connect("lobby_joined", self, "_on_lobby_joined")
33 _ignore = Steam.connect("lobby_chat_update", self, "_on_lobby_chat_update")
34 _ignore = Steam.connect("lobby_data_update", self, "_on_lobby_data_update")
35 _ignore = Steam.connect("persona_state_change", self, "_on_persona_state_change")
36 _ignore = Steam.connect("p2p_session_request", self, "_on_p2p_session_request")
37
38 _setup_recurring_task(0.1, "_broadcast_player_location")
39 _setup_recurring_task(5.0, "_resend_messages_needing_ack")
40 _setup_recurring_task(10.0, "_request_lobby_list")
41
42 _request_lobby_list()
43
44
45func _process(_delta: float) -> void:
46 _read_p2p_packet()
47
48
49func _exit_tree():
50 if active_lobby_id != 0:
51 Steam.leaveLobby(active_lobby_id)
52
53
54func _setup_recurring_task(wait_time, function):
55 var timer = Timer.new()
56 timer.set_wait_time(wait_time)
57 timer.set_one_shot(false)
58 timer.connect("timeout", self, function)
59 add_child(timer)
60 timer.start()
61
62
63func _broadcast_player_location():
64 if active_lobby_id == 0:
65 return
66 _send_p2p_packet(
67 {
68 "global_transform": player_node.global_transform,
69 },
70 RECIPIENT_BROADCAST_ALL,
71 Steam.P2P_SEND_UNRELIABLE_NO_DELAY,
72 false
73 )
74
75
76func _resend_messages_needing_ack():
77 for message_id in messages_needing_ack:
78 var message = messages_needing_ack[message_id]
79 if message["recipient_id"] in active_lobby_members:
80 _send_p2p_packet(message["data"], message["recipient_id"], message["mode"], true)
81 else:
82 messages_needing_ack.erase(message_id)
83
84
85func _request_lobby_list():
86 Steam.addRequestLobbyListDistanceFilter(Steam.LOBBY_DISTANCE_FILTER_WORLDWIDE)
87 Steam.addRequestLobbyListNumericalFilter(
88 "protocol_version", PROTOCOL_VERSION, Steam.LOBBY_COMPARISON_EQUAL
89 )
90 Steam.addRequestLobbyListStringFilter("map", global.map, Steam.LOBBY_COMPARISON_EQUAL)
91 Steam.addRequestLobbyListStringFilter(
92 "save_file", global.save_file.to_lower(), Steam.LOBBY_COMPARISON_EQUAL
93 )
94 Steam.requestLobbyList()
95
96
97func _on_lobby_match_list(lobbies: Array) -> void:
98 if active_lobby_id != 0 && not active_lobby_id in lobbies:
99 # Not sure why this happens, but it seems to sometimes.
100 lobbies.append(active_lobby_id)
101 var best_lobby_id = 0
102 var best_lobby_size = -1
103 for lobby_id in lobbies:
104 var lobby_size = Steam.getNumLobbyMembers(lobby_id)
105 if (
106 lobby_size > best_lobby_size
107 || (lobby_size == best_lobby_size && lobby_id < best_lobby_id)
108 ):
109 best_lobby_id = lobby_id
110 best_lobby_size = lobby_size
111 if best_lobby_id == 0:
112 Steam.createLobby(Steam.LOBBY_TYPE_INVISIBLE, MAX_PLAYERS)
113 elif best_lobby_id != active_lobby_id:
114 Steam.joinLobby(best_lobby_id)
115 elif best_lobby_size <= 1:
116 _request_lobby_list()
117
118
119func _on_lobby_created(result: int, lobby_id: int) -> void:
120 if result != Steam.RESULT_OK:
121 return
122 var _ignore = Steam.setLobbyData(lobby_id, "map", global.map)
123 _ignore = Steam.setLobbyData(lobby_id, "protocol_version", str(PROTOCOL_VERSION))
124 _ignore = Steam.setLobbyData(lobby_id, "save_file", global.save_file.to_lower())
125 _request_lobby_list()
126
127
128func _on_lobby_joined(lobby_id: int, _permissions: int, _locked: bool, result: int) -> void:
129 if result != Steam.RESULT_OK:
130 return
131 if active_lobby_id != 0 && active_lobby_id != lobby_id:
132 Steam.leaveLobby(active_lobby_id)
133 active_lobby_id = lobby_id
134 if is_loaded:
135 Steam.setLobbyMemberData(active_lobby_id, "loaded", "true")
136 _update_lobby_members()
137
138
139func _on_lobby_chat_update(
140 _lobby_id: int, _member_id: int, _making_change_id: int, _chat_state: int
141) -> void:
142 _update_lobby_members()
143
144
145func _on_lobby_data_update(_lobby_id: int, _member_id: int, _key: int) -> void:
146 _update_lobby_members()
147
148
149func _on_persona_state_change(_persona_id: int, _flag: int) -> void:
150 _update_lobby_members()
151
152
153func _update_lobby_members():
154 if active_lobby_id == 0:
155 return
156 var lobby_size: int = Steam.getNumLobbyMembers(active_lobby_id)
157 var prior_lobby_members = active_lobby_members
158 active_lobby_members = {}
159 var everyone_ready = is_loaded
160 var everyone_activated = is_activating
161 for i in range(0, lobby_size):
162 var member_id: int = Steam.getLobbyMemberByIndex(active_lobby_id, i)
163 if member_id != player_steam_id:
164 var steam_name: String = Steam.getFriendPersonaName(member_id)
165 if member_id in prior_lobby_members:
166 active_lobby_members[member_id] = prior_lobby_members[member_id]
167 else:
168 active_lobby_members[member_id] = (
169 load("res://nodes/multiplayer_avatar.tscn").instance()
170 )
171 add_child(active_lobby_members[member_id])
172 active_lobby_members[member_id].steam_id = member_id
173 active_lobby_members[member_id].steam_name = steam_name
174 if Steam.getLobbyMemberData(active_lobby_id, member_id, "loaded") != "true":
175 everyone_ready = false
176 if Steam.getLobbyMemberData(active_lobby_id, member_id, "timeout") != "true":
177 everyone_activated = false
178 for member in prior_lobby_members.values():
179 if not member.steam_id in active_lobby_members:
180 member.queue_free()
181 if !is_ready and everyone_ready:
182 is_ready = everyone_ready
183 global.get_node("RaceManager").everyone_ready()
184 if !letsgo and everyone_activated:
185 letsgo = true
186 time_to_start()
187
188
189func _on_p2p_session_request(remote_id: int) -> void:
190 if remote_id in active_lobby_members:
191 var _ignore = Steam.acceptP2PSessionWithUser(remote_id)
192
193
194func _read_p2p_packet() -> void:
195 var packet_size: int = Steam.getAvailableP2PPacketSize(0)
196 if packet_size > 0:
197 var packet: Dictionary = Steam.readP2PPacket(packet_size, 0)
198 var remote_id = packet["steam_id_remote"]
199 if remote_id in active_lobby_members:
200 var serialized: PoolByteArray = packet["data"]
201 var data: Dictionary = bytes2var(serialized, false)
202 if "global_transform" in data:
203 _receive_member_location(remote_id, data["global_transform"])
204 if "message_id" in data:
205 _send_p2p_packet(
206 {
207 "ack": data["message_id"],
208 },
209 remote_id,
210 Steam.P2P_SEND_RELIABLE_WITH_BUFFERING,
211 false
212 )
213 if "loaded" in data:
214 _update_lobby_members()
215 if "timeout" in data:
216 _update_lobby_members()
217 if "victory" in data:
218 _someone_victory(Steam.getFriendPersonaName(remote_id))
219 if "ack" in data:
220 messages_needing_ack.erase(data["ack"])
221
222
223func _receive_member_location(member_id: int, global_transform) -> void:
224 var member = active_lobby_members[member_id]
225 member.global_move_to(global_transform)
226 if !member.visible:
227 member.show()
228
229
230func _send_p2p_packet(data: Dictionary, recipient_id: int, mode: int, needs_ack: bool) -> void:
231 if recipient_id == RECIPIENT_BROADCAST_ALL:
232 for member_id in active_lobby_members.keys():
233 _send_p2p_packet(data.duplicate(), member_id, mode, needs_ack)
234 return
235
236 if needs_ack:
237 var message_id
238 if "message_id" in data:
239 message_id = data["message_id"]
240 else:
241 message_id = next_message_id
242 next_message_id += 1
243 data["message_id"] = message_id
244 if not message_id in messages_needing_ack:
245 messages_needing_ack[message_id] = {
246 "data": data,
247 "recipient_id": recipient_id,
248 "mode": mode,
249 }
250 var serialized: PoolByteArray = []
251 serialized.append_array(var2bytes(data))
252 var _ignore = Steam.sendP2PPacket(recipient_id, serialized, mode)
253
254
255func player_loaded():
256 is_loaded = true
257 if active_lobby_id != 0:
258 Steam.setLobbyMemberData(active_lobby_id, "loaded", "true")
259 _send_p2p_packet({"loaded": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true)
260 _update_lobby_members()
261
262
263func send_timeout():
264 is_activating = true
265 Steam.setLobbyMemberData(active_lobby_id, "timeout", "true")
266 _send_p2p_packet({"timeout": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true)
267 _update_lobby_members()
268
269
270func time_to_start():
271 var player = get_tree().get_root().get_node("Spatial/player")
272 player.playable = true
273 get_node("/root/Spatial/label").text = (
274 "Destination: %s" % global.get_node("RaceManager").end_pos[0]
275 )
276 get_node("/root/Spatial").ingame_achieve("Go!")
277
278
279func player_victory():
280 if !is_victory:
281 is_victory = true
282 _send_p2p_packet(
283 {"victory": "true"}, RECIPIENT_BROADCAST_ALL, Steam.P2P_SEND_RELIABLE, true
284 )
285 _someone_victory(Steam.getFriendPersonaName(player_steam_id))
286
287
288func _someone_victory(name):
289 if !victorious_players.has(name):
290 victorious_players.append(name)
291
292 var label = get_node("/root/Spatial/label")
293 label.text = "%s\n%s is #%d" % [label.text, name, victorious_players.size()]