diff --git a/CHANGELOG.md b/CHANGELOG.md index b373c2b..5f8f282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Who knows what the future holds... # 0.X.Y - DD/MM/YYYY ### Changes: -Nothing yet. +- Added [Valheim](https://store.steampowered.com/app/892970/Valheim/) support. ### Breaking: None, yaay! diff --git a/GAMES.md b/GAMES.md index 4a71917..ad1dcdf 100644 --- a/GAMES.md +++ b/GAMES.md @@ -62,6 +62,7 @@ Beware of the `Notes` column, as it contains information about query port offset | Creativerse | CREATIVERSE | Valve | Query Port offset: 1. | | Garry's Mod | GARRYSMOD | Valve | | | Barotrauma | BAROTRAUMA | Valve | Query Port offset: 1. | +| Valheim | VALHEIM | Valve | Query Port offset: 1. Does not respond to the A2S rules. | ## Planned to add support: _ diff --git a/examples/generic.rs b/examples/generic.rs index 4935bee..7c7ad27 100644 --- a/examples/generic.rs +++ b/examples/generic.rs @@ -2,23 +2,21 @@ use gamedig::{ protocols::types::{CommonResponse, ExtraRequestSettings, TimeoutSettings}, query_with_timeout_and_extra_settings, GDResult, + Game, GAMES, }; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; /// Make a query given the name of a game +/// The `game` argument is taken from the [GAMES](gamedig::GAMES) map. fn generic_query( - game_name: &str, + game: &Game, addr: &IpAddr, port: Option, timeout_settings: Option, extra_settings: Option, ) -> GDResult> { - let game = GAMES - .get(game_name) - .expect("Game doesn't exist, run without arguments to see a list of games"); - println!("Querying {:#?} with game {:#?}.", addr, game); let response = query_with_timeout_and_extra_settings(game, addr, port, timeout_settings, extra_settings)?; @@ -51,14 +49,18 @@ fn main() { ) .unwrap(); - let extra_settings = ExtraRequestSettings::default() + let game = GAMES + .get(&game_name) + .expect("Game doesn't exist, run without arguments to see a list of games"); + + let extra_settings = game + .request_settings + .clone() .set_hostname(hostname.to_string()) - .set_gather_rules(true) - .set_gather_players(true) .set_check_app_id(false); generic_query( - &game_name, + game, &addr.ip(), port, Some(timeout_settings), @@ -67,8 +69,7 @@ fn main() { .unwrap(); } else { // Without arguments print a list of games - - for (name, game) in gamedig::games::GAMES.entries() { + for (name, game) in GAMES.entries() { println!("{}\t{}", name, game.name); } } @@ -95,7 +96,12 @@ mod test { ) .unwrap(), ); - assert!(generic_query(game_name, &ADDR, None, timeout_settings, None).is_err()); + + let game = GAMES + .get(game_name) + .expect("Game doesn't exist, run without arguments to see a list of games"); + + assert!(generic_query(game, &ADDR, None, timeout_settings, None).is_err()); } #[test] diff --git a/src/games/definitions.rs b/src/games/definitions.rs index 4b3a7f9..1e36305 100644 --- a/src/games/definitions.rs +++ b/src/games/definitions.rs @@ -10,14 +10,25 @@ use crate::protocols::{ use crate::Game; use crate::protocols::types::ProprietaryProtocol; +use crate::protocols::valve::GatheringSettings; use phf::{phf_map, Map}; macro_rules! game { ($name: literal, $default_port: literal, $protocol: expr) => { + game!( + $name, + $default_port, + $protocol, + GatheringSettings::default().into_extra() + ) + }; + + ($name: literal, $default_port: literal, $protocol: expr, $extra_request_settings: expr) => { Game { name: $name, default_port: $default_port, protocol: $protocol, + request_settings: $extra_request_settings, } }; } @@ -86,6 +97,11 @@ pub static GAMES: Map<&'static str, Game> = phf_map! { "theship" => game!("The Ship", 27015, Protocol::PROPRIETARY(ProprietaryProtocol::TheShip)), "unturned" => game!("Unturned", 27015, Protocol::Valve(SteamApp::UNTURNED)), "unrealtournament" => game!("Unreal Tournament", 7778, Protocol::Gamespy(GameSpyVersion::One)), + "valheim" => game!("Valheim", 2457, Protocol::Valve(SteamApp::VALHEIM), GatheringSettings { + players: true, + rules: false, + check_app_id: true, + }.into_extra()), "vrising" => game!("V Rising", 27016, Protocol::Valve(SteamApp::VRISING)), "jc2m" => game!("Just Cause 2: Multiplayer", 7777, Protocol::PROPRIETARY(ProprietaryProtocol::JC2M)), "warsow" => game!("Warsow", 44400, Protocol::Quake(QuakeVersion::Three)), diff --git a/src/games/mod.rs b/src/games/mod.rs index 10129f7..9650f1f 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -39,6 +39,8 @@ pub struct Game { pub default_port: u16, /// The protocol the game's query uses pub protocol: Protocol, + /// Request settings. + pub request_settings: ExtraRequestSettings, } #[cfg(feature = "game_defs")] @@ -78,7 +80,9 @@ pub fn query_with_timeout_and_extra_settings( protocols::valve::query( &socket_addr, steam_app.as_engine(), - extra_settings.map(ExtraRequestSettings::into), + extra_settings + .or(Option::from(game.request_settings.clone())) + .map(ExtraRequestSettings::into), timeout_settings, ) .map(Box::new)? diff --git a/src/games/valve.rs b/src/games/valve.rs index e55594b..80b6ad9 100644 --- a/src/games/valve.rs +++ b/src/games/valve.rs @@ -53,4 +53,15 @@ game_query_mod!(teamfortress2, "Team Fortress 2", TEAMFORTRESS2, 27015); game_query_mod!(tfc, "Team Fortress Classic", TFC, 27015); game_query_mod!(theforest, "The Forest", THEFOREST, 27016); game_query_mod!(unturned, "Unturned", UNTURNED, 27015); +game_query_mod!( + valheim, + "Valheim", + VALHEIM, + 2457, + GatheringSettings { + players: true, + rules: false, + check_app_id: true, + } +); game_query_mod!(vrising, "V Rising", VRISING, 27016); diff --git a/src/protocols/valve/mod.rs b/src/protocols/valve/mod.rs index ab935b4..575a200 100644 --- a/src/protocols/valve/mod.rs +++ b/src/protocols/valve/mod.rs @@ -15,9 +15,21 @@ pub use types::*; /// * `steam_app`, `default_port` - Passed through to [game_query_fn]. macro_rules! game_query_mod { ($mod_name: ident, $pretty_name: expr, $steam_app: ident, $default_port: literal) => { + crate::protocols::valve::game_query_mod!( + $mod_name, + $pretty_name, + $steam_app, + $default_port, + GatheringSettings::default() + ); + }; + + ($mod_name: ident, $pretty_name: expr, $steam_app: ident, $default_port: literal, $gathering_settings: expr) => { #[doc = $pretty_name] pub mod $mod_name { - crate::protocols::valve::game_query_fn!($steam_app, $default_port); + use crate::protocols::valve::GatheringSettings; + + crate::protocols::valve::game_query_fn!($steam_app, $default_port, $gathering_settings); } }; } @@ -36,19 +48,20 @@ pub(crate) use game_query_mod; /// game_query_fn!(TEAMFORTRESS2, 27015); /// ``` macro_rules! game_query_fn { - ($steam_app: ident, $default_port: literal) => { + ($steam_app: ident, $default_port: literal, $gathering_settings: expr) => { + // TODO: By using $gathering_settings, also add to doc if a game doesnt respond to certain gathering settings crate::protocols::valve::game_query_fn!{@gen $steam_app, $default_port, concat!( "Make a valve query for ", stringify!($steam_app), " with default timeout settings and default extra request settings.\n\n", - "If port is `None`, then the default port (", stringify!($default_port), ") will be used.")} + "If port is `None`, then the default port (", stringify!($default_port), ") will be used."), $gathering_settings} }; - (@gen $steam_app: ident, $default_port: literal, $doc: expr) => { + (@gen $steam_app: ident, $default_port: literal, $doc: expr, $gathering_settings: expr) => { #[doc = $doc] pub fn query(address: &std::net::IpAddr, port: Option) -> crate::GDResult { let valve_response = crate::protocols::valve::query( &std::net::SocketAddr::new(*address, port.unwrap_or($default_port)), crate::protocols::valve::SteamApp::$steam_app.as_engine(), - None, + Some($gathering_settings), None, )?; diff --git a/src/protocols/valve/types.rs b/src/protocols/valve/types.rs index e3c888a..573f920 100644 --- a/src/protocols/valve/types.rs +++ b/src/protocols/valve/types.rs @@ -341,6 +341,8 @@ pub enum SteamApp { HLL, /// Barotrauma BAROTRAUMA, + /// Valheim + VALHEIM, } impl SteamApp { @@ -383,6 +385,7 @@ impl SteamApp { Self::BAROTRAUMA => Engine::new_source(602960), Self::ROR2 => Engine::new_source(632_360), Self::OHD => Engine::new_source_with_dedicated(736_590, 950_900), + Self::VALHEIM => Engine::new_source(892_970), Self::ONSET => Engine::new_source(1_105_810), Self::VRISING => Engine::new_source(1_604_030), Self::HLL => Engine::new_source(686_810), @@ -422,15 +425,29 @@ pub struct GatheringSettings { pub check_app_id: bool, } -impl Default for GatheringSettings { +impl GatheringSettings { /// Default values are true for both the players and the rules. - fn default() -> Self { + pub const fn default() -> Self { Self { players: true, rules: true, check_app_id: true, } } + + pub const fn into_extra(self) -> ExtraRequestSettings { + ExtraRequestSettings { + hostname: None, + protocol_version: None, + gather_players: Some(self.players), + gather_rules: Some(self.rules), + check_app_id: Some(self.check_app_id), + } + } +} + +impl Default for GatheringSettings { + fn default() -> Self { GatheringSettings::default() } } impl From for GatheringSettings {