chore: initial commit for phase06
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user