diff --git a/crates/skate-game/src/multiplayer.rs b/crates/skate-game/src/multiplayer.rs index a4e08f02..533a4d90 100644 --- a/crates/skate-game/src/multiplayer.rs +++ b/crates/skate-game/src/multiplayer.rs @@ -200,11 +200,6 @@ impl Multiplayer { let Some(row) = self.browser_rows.get(index) else { return; }; - if !row.compatible { - self.browser_status = - "Incompatible physics or multiplayer protocol. Use compatible game versions.".into(); - return; - } if row.players >= row.capacity { self.browser_status = "Lobby is full. Refresh to check for a free slot.".into(); return; diff --git a/crates/skate-net/src/directory.rs b/crates/skate-net/src/directory.rs index 8d8d22aa..47ba5600 100644 --- a/crates/skate-net/src/directory.rs +++ b/crates/skate-net/src/directory.rs @@ -1,6 +1,10 @@ //! Bounded control messages between the game and the optional discovery helper. use serde::{Deserialize, Serialize}; pub const NAMESPACE: &str = "skate3rust-free-skate-v5"; +// Spacewar is shared with other games; identify ours without filtering versions. +pub fn is_game_lobby(namespace: &str) -> bool { + namespace.starts_with("skate3rust-free-skate-v") +} pub const PAGE_SIZE: usize = 5; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Request { diff --git a/crates/skate-net/src/lobby.rs b/crates/skate-net/src/lobby.rs index 35eb596a..09ea8b13 100644 --- a/crates/skate-net/src/lobby.rs +++ b/crates/skate-net/src/lobby.rs @@ -286,11 +286,8 @@ impl Session { if !r.0.is_empty() || info.id != actor || actor == self.local { return; } - let own = self.local_info(); - // Map identity is descriptive; joining different worlds is allowed. - let error = if info.physics != own.physics { - 2 - } else if !self.links.contains_key(&peer) && self.links.len() >= MAX_PLAYERS - 1 { + // Map, rig and physics fingerprints describe peers; they do not gate admission. + let error = if !self.links.contains_key(&peer) && self.links.len() >= MAX_PLAYERS - 1 { 3 } else { 0 @@ -344,7 +341,6 @@ impl Session { }; if info.id == 0 || infos.iter().any(|i: &Info| i.id == info.id) - || info.physics != self.local_info().physics { return; } diff --git a/crates/skate-net/tests/v2.rs b/crates/skate-net/tests/v2.rs index 694ff625..963bbb72 100644 --- a/crates/skate-net/tests/v2.rs +++ b/crates/skate-net/tests/v2.rs @@ -125,7 +125,7 @@ fn admission_full_lobby_mismatch_departure_and_rejoin() { bad.physics = 99; let mut guest = Session::new(1, bad, Some(1)); exchange(&mut host, &mut guest, 12, 1300); - assert!(guest.notice.contains("Physics")); + assert!(guest.notice.contains("full")); host.receive(2, &packed::header(1, 2, skate_net::lobby::GOODBYE, 0), 1400); assert_eq!(host.actors.len(), 9); exchange(&mut host, &mut extra, 11, 1700); @@ -261,10 +261,12 @@ fn occupied_driver_flag_preserves_root_without_native_collision_parts() { } #[test] -fn different_maps_can_join_and_exchange_body_updates() { +fn different_maps_physics_and_rigs_can_join_and_exchange_body_updates() { let mut host = Session::new(1, info(1), None); let mut other = info(2); other.map = 88; + other.physics = 99; + other.rig = 77; let mut guest = Session::new(1, other, Some(1)); exchange(&mut host, &mut guest, 2, 100); assert!(guest.connected()); @@ -275,3 +277,10 @@ fn different_maps_can_join_and_exchange_body_updates() { assert!(host.actors[&2].body.latest().is_some()); assert!(guest.actors[&1].body.latest().is_some()); } + +#[test] +fn discovery_accepts_other_game_versions_but_not_unrelated_spacewar_lobbies() { + assert!(skate_net::directory::is_game_lobby("skate3rust-free-skate-v4")); + assert!(skate_net::directory::is_game_lobby("skate3rust-free-skate-v5")); + assert!(!skate_net::directory::is_game_lobby("another-game")); +} diff --git a/crates/skate-steam-relay/src/directory.rs b/crates/skate-steam-relay/src/directory.rs index 338ecaf6..5ce01fd6 100644 --- a/crates/skate-steam-relay/src/directory.rs +++ b/crates/skate-steam-relay/src/directory.rs @@ -5,8 +5,7 @@ use std::{ time::{Duration, Instant}, }; use steamworks::{ - Client, DistanceFilter, LobbyId, LobbyKey, LobbyType, Matchmaking, StringFilter, - StringFilterKind, + Client, DistanceFilter, LobbyId, LobbyKey, LobbyType, Matchmaking, StringFilter, StringFilterKind, }; enum ResultEvent { List(u64, usize, u64, u64, Result, String>), @@ -73,10 +72,15 @@ impl Directory { let id = r.id; match r.command { Command::Browse { page, map, physics } => { + // Keep unrelated Spacewar lobbies out of Steam's bounded result set, + // while accepting every version of our namespace. mm.add_request_lobby_list_string_filter(StringFilter( - LobbyKey::new("sk8game"), - directory::NAMESPACE, - StringFilterKind::Equal, + LobbyKey::new("sk8game"), "skate3rust-free-skate-v", + StringFilterKind::EqualToOrGreaterThan, + )); + mm.add_request_lobby_list_string_filter(StringFilter( + LobbyKey::new("sk8game"), "skate3rust-free-skate-w", + StringFilterKind::LessThan, )); mm.set_request_lobby_list_distance_filter(DistanceFilter::Worldwide); mm.request_lobby_list(move |result| { @@ -124,10 +128,6 @@ impl Directory { }; None } - fn compatible(mm: &Matchmaking, l: LobbyId, _map: u64, physics: u64) -> bool { - mm.lobby_data(l, "sk8game").as_deref() == Some(directory::NAMESPACE) - && mm.lobby_data(l, "physics") == Some(physics.to_string()) - } fn owner_event(&mut self, mm: &Matchmaking, own: u64) -> Option { let l = self.lobby?; let owner = mm.lobby_owner(l).raw(); @@ -162,11 +162,11 @@ impl Directory { continue; } let event = match result { - ResultEvent::List(_, page, map, physics, result) => match result { + ResultEvent::List(_, page, _map, _physics, result) => match result { Err(e) => Event::Error(e), Ok(mut list) => { list.retain(|&l| { - mm.lobby_data(l, "sk8game").as_deref() == Some(directory::NAMESPACE) + mm.lobby_data(l, "sk8game").is_some_and(|name| directory::is_game_lobby(&name)) }); list.sort_by_key(LobbyId::raw); let total = list.len(); @@ -183,7 +183,7 @@ impl Directory { ), players: mm.lobby_member_count(l).min(10), capacity: mm.lobby_member_limit(l).unwrap_or(10).min(10), - compatible: Self::compatible(&mm, l, map, physics), + compatible: true, }) .collect(); Event::Rows { page, total, rows } @@ -206,17 +206,12 @@ impl Directory { } } }, - ResultEvent::Join(_, map, physics, result) => match result { + ResultEvent::Join(_, _map, _physics, result) => match result { Err(e) => Event::Error(e), Ok(l) => { - if !Self::compatible(&mm, l, map, physics) { - mm.leave_lobby(l); - Event::Error("Incompatible physics or multiplayer protocol. Use compatible game versions.".into()) - } else { - self.lobby = Some(l); - self.owner_event(&mm, own) - .unwrap_or(Event::Error("Steam lobby owner unavailable".into())) - } + self.lobby = Some(l); + self.owner_event(&mm, own) + .unwrap_or(Event::Error("Steam lobby owner unavailable".into())) } }, }; diff --git a/docs/multiplayer-test.md b/docs/multiplayer-test.md index 4474f616..b9a5a6a0 100644 --- a/docs/multiplayer-test.md +++ b/docs/multiplayer-test.md @@ -38,7 +38,7 @@ Test skating into each other, board impacts, tricks, bails, walking, board retri ## Online setup -On one PC choose **Esc > Multiplayer > Host via Steam / Spacewar**. On each other PC/account enter the same host code in the multiplayer menu and choose Join. Up to nine guests can join. Everyone needs compatible protocol and physical definitions. Map differences do not block joining; players on different maps can appear inside scenery or see different collisions. Visual outfit/rig differences use the default skater. Unknown identifiers never become asset paths. An incompatible visual hierarchy uses the default idle pose instead of foreign bone indices. +On one PC choose **Esc > Multiplayer > Host via Steam / Spacewar**. On each other PC/account enter the same host code in the multiplayer menu and choose Join. Up to nine guests can join. Map, physics and version fingerprints do not block joining. Different wire protocols may still fail to communicate; players on different maps can appear inside scenery or see different collisions. Visual outfit/rig differences use the default skater. Unknown identifiers never become asset paths. An incompatible visual hierarchy uses the default idle pose instead of foreign bone indices. The host is an ordinary player, not a dedicated server. All guests connect to that host; it forwards the newest state to the other players. Transport, player IDs, codec, membership, budgets and rendering are independent of Steam. Spacewar/AppID 480 is the optional connection/relay provider. The game has no Steam DLL dependency and neither executable invokes Steam Input. Two processes under one Steam account still require the local launcher for testing.