bones_matchmaker/
lobbies.rs1use super::matchmaker::{start_game, GameLobbies, MATCHMAKER_STATE};
2use crate::helpers::{generate_unique_id, hash_password};
3use anyhow::Result;
4use bones_matchmaker_proto::{
5 GameID, LobbyId, LobbyInfo, LobbyListItem, MatchInfo, MatchmakerResponse,
6};
7use iroh::{endpoint::Connection, Endpoint};
8use std::collections::HashMap;
9
10pub async fn handle_list_lobbies(
12 game_id: GameID,
13 send: &mut iroh::endpoint::SendStream,
14) -> Result<()> {
15 let state = MATCHMAKER_STATE.lock().await;
16 let lobbies = state
18 .game_lobbies
19 .get(&game_id)
20 .map(|game_lobbies| {
21 game_lobbies
22 .lobbies
23 .iter()
24 .map(|(id, lobby_info)| {
25 let current_players = state
26 .lobby_connections
27 .get(&(game_id.clone(), id.clone()))
28 .map(|entry| entry.get().len() as u32)
29 .unwrap_or(0);
30 LobbyListItem {
31 id: id.clone(),
32 name: lobby_info.name.clone(),
33 current_players,
34 max_players: lobby_info.max_players,
35 has_password: lobby_info.password_hash.is_some(),
36 game_id: game_id.clone(),
37 }
38 })
39 .collect::<Vec<_>>()
40 })
41 .unwrap_or_default();
42
43 let message = postcard::to_allocvec(&MatchmakerResponse::LobbiesList(lobbies))?;
45 send.write_all(&message).await?;
46 send.finish()?;
47 send.stopped().await?;
48
49 Ok(())
50}
51
52pub async fn handle_create_lobby(
54 conn: Connection,
55 lobby_info: LobbyInfo,
56 send: &mut iroh::endpoint::SendStream,
57) -> Result<()> {
58 let lobby_id = LobbyId(generate_unique_id());
59 let mut state = MATCHMAKER_STATE.lock().await;
60
61 state
63 .game_lobbies
64 .entry(lobby_info.game_id.clone())
65 .or_insert_with(|| GameLobbies {
66 game_id: lobby_info.game_id.clone(),
67 lobbies: HashMap::new(),
68 })
69 .lobbies
70 .insert(lobby_id.clone(), lobby_info.clone());
71
72 if let Err(e) = state
74 .lobby_connections
75 .insert((lobby_info.game_id.clone(), lobby_id.clone()), vec![conn])
76 {
77 error!("Failed to inserting lobby during creation: {:?}", e);
78 }
79
80 let message = postcard::to_allocvec(&MatchmakerResponse::LobbyCreated(lobby_id))?;
82 send.write_all(&message).await?;
83 send.finish()?;
84 send.stopped().await?;
85
86 Ok(())
87}
88
89pub async fn handle_join_lobby(
91 ep: &Endpoint,
92 conn: Connection,
93 game_id: GameID,
94 lobby_id: LobbyId,
95 password: Option<String>,
96 send: &mut iroh::endpoint::SendStream,
97) -> Result<()> {
98 let mut state = MATCHMAKER_STATE.lock().await;
99
100 if let Some(game_lobbies) = state.game_lobbies.get_mut(&game_id) {
101 if let Some(lobby_info) = game_lobbies.lobbies.get(&lobby_id) {
102 if let Some(hash) = &lobby_info.password_hash {
104 if password.as_ref().map(|p| hash_password(p)) != Some(hash.clone()) {
105 let message = postcard::to_allocvec(&MatchmakerResponse::Error(
106 "Incorrect password".to_string(),
107 ))?;
108 send.write_all(&message).await?;
109 send.finish()?;
110 send.stopped().await?;
111 return Ok(());
112 }
113 }
114
115 let max_players = lobby_info.max_players;
116 let match_data = lobby_info.match_data.clone();
117 let player_idx_assignment = lobby_info.player_idx_assignment.clone();
118
119 let join_result = state.lobby_connections.update(
121 &(game_id.clone(), lobby_id.clone()),
122 |_exists, connections| {
123 if connections.len() < max_players as usize {
124 connections.push(conn.clone());
125 Some(connections.len())
126 } else {
127 None
128 }
129 },
130 );
131
132 match join_result {
133 Some(Some(count)) => {
134 let message =
136 postcard::to_allocvec(&MatchmakerResponse::LobbyJoined(lobby_id.clone()))?;
137 send.write_all(&message).await?;
138 send.finish()?;
139 send.stopped().await?;
140
141 let lobby_update_message =
143 postcard::to_allocvec(&MatchmakerResponse::LobbyUpdate {
144 player_count: count as u32,
145 })?;
146 if let Some(connections) = state
147 .lobby_connections
148 .get(&(game_id.clone(), lobby_id.clone()))
149 {
150 for connection in connections.get().iter() {
151 let mut send = connection.open_uni().await?;
152 send.write_all(&lobby_update_message).await?;
153 send.finish()?;
154 send.stopped().await?;
155 }
156 }
157
158 if count == max_players as usize {
160 let match_info = MatchInfo {
161 max_players,
162 match_data,
163 game_id: game_id.clone(),
164 player_idx_assignment,
165 };
166 if let Some(connections) = state
167 .lobby_connections
168 .remove(&(game_id.clone(), lobby_id.clone()))
169 {
170 let members = connections.1;
171 drop(state);
172 let ep = ep.clone();
173 tokio::spawn(async move {
174 if let Err(e) = start_game(ep, members, &match_info).await {
175 error!("Error starting match from full lobby: {:?}", e);
176 }
177 });
178 }
179 }
180 }
181 _ => {
182 let message = postcard::to_allocvec(&MatchmakerResponse::Error(
184 "Lobby is full".to_string(),
185 ))?;
186 send.write_all(&message).await?;
187 send.finish()?;
188 send.stopped().await?;
189 }
190 }
191 } else {
192 let message =
193 postcard::to_allocvec(&MatchmakerResponse::Error("Lobby not found".to_string()))?;
194 send.write_all(&message).await?;
195 send.finish()?;
196 send.stopped().await?;
197 }
198 } else {
199 let message =
200 postcard::to_allocvec(&MatchmakerResponse::Error("Game not found".to_string()))?;
201 send.write_all(&message).await?;
202 send.finish()?;
203 send.stopped().await?;
204 }
205
206 Ok(())
207}