diff --git a/GAMES.md b/GAMES.md index d1b32e9..7ea42f0 100644 --- a/GAMES.md +++ b/GAMES.md @@ -7,6 +7,8 @@ | CSGO | Counter-Strike: Global Offensive | Valve Protocol | Rules doesnt work on this title. | | CSS | Counter-Strike: Source | Valve Protocol | | | DODS | Day of Defeat: Source | Valve Protocol | | +| L4D | Left 4 Dead | Valve Protocol | | +| L4D2 | Left 4 Dead 2 | Valve Protocol | | ## Planned to add support: All Valve titles. diff --git a/examples/l4d.rs b/examples/l4d.rs new file mode 100644 index 0000000..4c0895d --- /dev/null +++ b/examples/l4d.rs @@ -0,0 +1,10 @@ + +use gamedig::games::l4d; + +fn main() { + let response = l4d::query("207.246.72.170", Some(26999)); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/examples/l4d2.rs b/examples/l4d2.rs new file mode 100644 index 0000000..754815b --- /dev/null +++ b/examples/l4d2.rs @@ -0,0 +1,10 @@ + +use gamedig::games::l4d2; + +fn main() { + let response = l4d2::query("74.91.124.246", None); + match response { + Err(error) => println!("Couldn't query, error: {error}"), + Ok(r) => println!("{:?}", r) + } +} diff --git a/src/games/l4d.rs b/src/games/l4d.rs new file mode 100644 index 0000000..deb42e5 --- /dev/null +++ b/src/games/l4d.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::L4D, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/l4d2.rs b/src/games/l4d2.rs new file mode 100644 index 0000000..45a3e43 --- /dev/null +++ b/src/games/l4d2.rs @@ -0,0 +1,83 @@ +use crate::{GDResult, valve}; +use crate::valve::{ValveProtocol, App, GatheringSettings, Server, ServerRule, ServerPlayer}; + +#[derive(Debug)] +pub struct Player { + pub name: String, + pub score: u32, + pub duration: f32 +} + +impl Player { + fn from_valve_response(player: &ServerPlayer) -> Self { + Self { + name: player.name.clone(), + score: player.score, + duration: player.duration + } + } +} + +#[derive(Debug)] +pub struct Response { + pub protocol: u8, + pub name: String, + pub map: String, + pub game: String, + pub players: u8, + pub players_details: Vec, + pub max_players: u8, + pub bots: u8, + pub server_type: Server, + pub has_password: bool, + pub vac_secured: bool, + pub version: String, + pub port: Option, + pub steam_id: Option, + pub tv_port: Option, + pub tv_name: Option, + pub keywords: Option, + pub rules: Vec +} + +impl Response { + pub fn new_from_valve_response(response: valve::Response) -> Self { + let (port, steam_id, tv_port, tv_name, keywords) = match response.info.extra_data { + None => (None, None, None, None, None), + Some(ed) => (ed.port, ed.steam_id, ed.tv_port, ed.tv_name, ed.keywords) + }; + + Self { + protocol: response.info.protocol, + name: response.info.name, + map: response.info.map, + game: response.info.game, + players: response.info.players, + players_details: response.players.unwrap().iter().map(|p| Player::from_valve_response(p)).collect(), + max_players: response.info.max_players, + bots: response.info.bots, + server_type: response.info.server_type, + has_password: response.info.has_password, + vac_secured: response.info.vac_secured, + version: response.info.version, + port, + steam_id, + tv_port, + tv_name, + keywords, + rules: response.rules.unwrap() + } + } +} + +pub fn query(address: &str, port: Option) -> GDResult { + let valve_response = ValveProtocol::query(App::L4D2, address, match port { + None => 27015, + Some(port) => port + }, GatheringSettings { + players: true, + rules: true + })?; + + Ok(Response::new_from_valve_response(valve_response)) +} diff --git a/src/games/mod.rs b/src/games/mod.rs index 8e23504..4486384 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -7,3 +7,5 @@ pub mod csgo; pub mod css; pub mod dods; pub mod gm; +pub mod l4d; +pub mod l4d2; diff --git a/src/protocols/valve.rs b/src/protocols/valve.rs index 0c3670d..6fa10ea 100644 --- a/src/protocols/valve.rs +++ b/src/protocols/valve.rs @@ -130,6 +130,10 @@ pub enum App { DODS = 300, /// Team Fortress 2 TF2 = 440, + /// Left 4 Dead + L4D = 500, + /// Left 4 Dead + L4D2 = 550, /// Counter-Strike: Global Offensive CSGO = 730, /// The Ship @@ -146,6 +150,8 @@ impl TryFrom for App { x if x == App::CSS as u16 => Ok(App::CSS), x if x == App::DODS as u16 => Ok(App::DODS), x if x == App::TF2 as u16 => Ok(App::TF2), + x if x == App::L4D as u16 => Ok(App::L4D), + x if x == App::L4D2 as u16 => Ok(App::L4D2), x if x == App::CSGO as u16 => Ok(App::CSGO), x if x == App::TS as u16 => Ok(App::TS), x if x == App::GM as u16 => Ok(App::GM),