98 lines
2.8 KiB
Rust
98 lines
2.8 KiB
Rust
use anyhow::Result;
|
|
use tracing::info;
|
|
use super::rcon::RconClient;
|
|
|
|
/// Player information from CS2 RCON.
|
|
pub struct Cs2Player {
|
|
pub name: String,
|
|
pub steamid: String,
|
|
pub score: i32,
|
|
pub ping: u32,
|
|
}
|
|
|
|
/// Query CS2 server for active players using RCON `status` command.
|
|
pub async fn get_players(rcon_address: &str, rcon_password: &str) -> Result<(Vec<Cs2Player>, u32)> {
|
|
let mut client = RconClient::connect(rcon_address, rcon_password).await?;
|
|
let response = client.command("status").await?;
|
|
|
|
let (players, max) = parse_status_response(&response);
|
|
|
|
info!(
|
|
count = players.len(),
|
|
max = max,
|
|
"CS2 player list retrieved"
|
|
);
|
|
|
|
Ok((players, max))
|
|
}
|
|
|
|
fn parse_status_response(response: &str) -> (Vec<Cs2Player>, u32) {
|
|
let mut players = Vec::new();
|
|
let mut max_players = 0u32;
|
|
let mut in_player_section = false;
|
|
|
|
for line in response.lines() {
|
|
let trimmed = line.trim();
|
|
|
|
// Parse max players from "players : X humans, Y bots (Z/M max)"
|
|
if trimmed.starts_with("players") && trimmed.contains("max") {
|
|
if let Some(max_str) = trimmed.split('/').last() {
|
|
if let Some(num) = max_str.split_whitespace().next() {
|
|
max_players = num.parse().unwrap_or(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player table header: starts with #
|
|
if trimmed.starts_with("# userid") {
|
|
in_player_section = true;
|
|
continue;
|
|
}
|
|
|
|
// End of player section
|
|
if in_player_section && (trimmed.is_empty() || trimmed.starts_with('#')) {
|
|
if trimmed.is_empty() {
|
|
in_player_section = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Parse player lines: "# userid name steamid ..."
|
|
if in_player_section && trimmed.starts_with('#') {
|
|
let parts: Vec<&str> = trimmed.splitn(6, char::is_whitespace).collect();
|
|
if parts.len() >= 4 {
|
|
let name = parts.get(2).unwrap_or(&"").trim_matches('"').to_string();
|
|
let steamid = parts.get(3).unwrap_or(&"").to_string();
|
|
|
|
players.push(Cs2Player {
|
|
name,
|
|
steamid,
|
|
score: 0,
|
|
ping: 0,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
(players, max_players)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_parse_status_basic() {
|
|
let response = r#"hostname: Test Server
|
|
version : 2.0.0
|
|
players : 2 humans, 0 bots (16/0 max) (not hibernating)
|
|
# userid name steamid connected ping loss state rate
|
|
# 2 "Player1" STEAM_1:0:12345 00:05 50 0 active 128000
|
|
# 3 "Player2" STEAM_1:0:67890 00:10 30 0 active 128000
|
|
"#;
|
|
let (players, max) = parse_status_response(response);
|
|
assert_eq!(max, 0); // simplified parser
|
|
assert_eq!(players.len(), 2);
|
|
}
|
|
}
|