mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-05-18 09:35:50 +00:00
[Protocol] Implement generic response with dyn (#56)
* Implement generic response as enum * First draft of implementing into_common() * Make common response type generic * Use macros and generics to reduce repetition * [Games] Add dynamically dispatched CommonResponse trait This adds two traits: "CommonResponse", and "CommonPlayer", when the generic game query function returns a response it returns a pointer to its original response type that implements "CommonResponse". Both common traits require that "as_original()" be implemented, this returns an enum containing a pointer to the original type. Both traits have a concrete method "as_json()" that returns a struct containing data fetched from all of its methods as. This struct implements serde and can hence be serialized as required. The traits require a few other methods be implemented, those being the fields that are common across all types. All other methods have a default None implementation so that each response type only needs to implement methods for fields that it has. * [Game] Implement common traits for JCMP2 response * [Fmt] Run cargo fmt * Fix doctest failing * Run cargo fmt
This commit is contained in:
parent
bf14ecb4a4
commit
b368877031
17 changed files with 405 additions and 430 deletions
|
|
@ -1,15 +1,17 @@
|
|||
use gamedig::{protocols::GenericResponse, query, GDResult, GAMES};
|
||||
use gamedig::{protocols::types::CommonResponse, query, GDResult, GAMES};
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
fn generic_query(game_name: &str, addr: &IpAddr, port: Option<u16>) -> GDResult<GenericResponse> {
|
||||
fn generic_query(game_name: &str, addr: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||
let game = GAMES.get(game_name).expect("Game doesn't exist");
|
||||
|
||||
println!("Querying {:#?} with game {:#?}.", addr, game.name);
|
||||
println!("Querying {:#?} with game {:#?}.", addr, game);
|
||||
|
||||
let response = query(game, addr, port)?;
|
||||
println!("Response: {:#?}", response.as_json());
|
||||
|
||||
println!("{:#?}", response);
|
||||
let common = response.as_original();
|
||||
println!("Common response: {:#?}", common);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::protocols::types::{SpecificResponse, TimeoutSettings};
|
||||
use crate::protocols::types::{CommonResponse, TimeoutSettings};
|
||||
use crate::protocols::valve::{Engine, Environment, Server, ValveProtocol};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::GDResult;
|
||||
|
|
@ -44,51 +44,17 @@ pub struct Response {
|
|||
pub time_left: u16,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ExtraResponse {
|
||||
/// Protocol used by the server.
|
||||
pub protocol: u8,
|
||||
/// Map name.
|
||||
pub active_mod: String,
|
||||
/// Dedicated, NonDedicated or SourceTV
|
||||
pub server_type: Server,
|
||||
/// The Operating System that the server is on.
|
||||
pub environment_type: Environment,
|
||||
/// Indicates whether the server uses VAC.
|
||||
pub vac_secured: bool,
|
||||
/// Current round index.
|
||||
pub round: u8,
|
||||
/// Maximum amount of rounds.
|
||||
pub rounds_maximum: u8,
|
||||
/// Time left for the current round in seconds.
|
||||
pub time_left: u16,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::FFOW(self) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: Some(r.description),
|
||||
game: Some(r.game_mode),
|
||||
game_version: Some(r.version),
|
||||
map: Some(r.map),
|
||||
players_maximum: r.players_maximum.into(),
|
||||
players_online: r.players_online.into(),
|
||||
players_bots: None,
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::FFOW(ExtraResponse {
|
||||
protocol: r.protocol,
|
||||
active_mod: r.active_mod,
|
||||
server_type: r.server_type,
|
||||
environment_type: r.environment_type,
|
||||
vac_secured: r.vac_secured,
|
||||
round: r.round,
|
||||
rounds_maximum: r.rounds_maximum,
|
||||
time_left: r.time_left,
|
||||
}),
|
||||
}
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn game(&self) -> Option<&str> { Some(&self.game_mode) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.version) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u64 { self.players_online.into() }
|
||||
}
|
||||
|
||||
pub fn query(address: &IpAddr, port: Option<u16>) -> GDResult<Response> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::bufferer::{Bufferer, Endianess};
|
||||
use crate::protocols::gamespy::common::has_password;
|
||||
use crate::protocols::gamespy::three::{data_to_map, GameSpy3};
|
||||
use crate::protocols::types::SpecificResponse;
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::GenericResponse;
|
||||
use crate::{GDError, GDResult};
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -16,6 +16,12 @@ pub struct Player {
|
|||
ping: u16,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::JCMP2(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Response {
|
||||
|
|
@ -28,20 +34,27 @@ pub struct Response {
|
|||
players_online: usize,
|
||||
}
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: Some(r.description),
|
||||
game: None,
|
||||
game_version: Some(r.version),
|
||||
map: None,
|
||||
players_maximum: r.players_maximum as u64,
|
||||
players_online: r.players_online as u64,
|
||||
players_bots: None,
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::JC2MP,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::JC2MP(self) }
|
||||
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.version) }
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u64 {
|
||||
// If usize doesn't fit in u64 silently return 0 as this is extremely unlikely
|
||||
// for a player count
|
||||
self.players_maximum.try_into().unwrap_or(0)
|
||||
}
|
||||
fn players_online(&self) -> u64 { self.players_online.try_into().unwrap_or(0) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn crate::protocols::types::CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ pub mod vr;
|
|||
|
||||
use crate::protocols::gamespy::GameSpyVersion;
|
||||
use crate::protocols::quake::QuakeVersion;
|
||||
use crate::protocols::types::CommonResponse;
|
||||
use crate::protocols::{self, Protocol};
|
||||
use crate::GDResult;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
|
@ -137,42 +138,42 @@ mod definitions;
|
|||
pub use definitions::GAMES;
|
||||
|
||||
/// Make a query given a game definition
|
||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<protocols::GenericResponse> {
|
||||
pub fn query(game: &Game, address: &IpAddr, port: Option<u16>) -> GDResult<Box<dyn CommonResponse>> {
|
||||
let socket_addr = SocketAddr::new(*address, port.unwrap_or(game.default_port));
|
||||
Ok(match &game.protocol {
|
||||
Protocol::Valve(steam_app) => {
|
||||
protocols::valve::query(&socket_addr, steam_app.as_engine(), None, None).map(|r| r.into())?
|
||||
protocols::valve::query(&socket_addr, steam_app.as_engine(), None, None).map(Box::new)?
|
||||
}
|
||||
Protocol::Minecraft(version) => {
|
||||
match version {
|
||||
Some(protocols::minecraft::Server::Java) => {
|
||||
protocols::minecraft::query_java(&socket_addr, None).map(|r| r.into())?
|
||||
protocols::minecraft::query_java(&socket_addr, None).map(Box::new)?
|
||||
}
|
||||
Some(protocols::minecraft::Server::Bedrock) => {
|
||||
protocols::minecraft::query_bedrock(&socket_addr, None).map(|r| r.into())?
|
||||
protocols::minecraft::query_bedrock(&socket_addr, None).map(Box::new)?
|
||||
}
|
||||
Some(protocols::minecraft::Server::Legacy(group)) => {
|
||||
protocols::minecraft::query_legacy_specific(*group, &socket_addr, None).map(|r| r.into())?
|
||||
protocols::minecraft::query_legacy_specific(*group, &socket_addr, None).map(Box::new)?
|
||||
}
|
||||
None => protocols::minecraft::query(&socket_addr, None).map(|r| r.into())?,
|
||||
None => protocols::minecraft::query(&socket_addr, None).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::Gamespy(version) => {
|
||||
match version {
|
||||
GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, None).map(|r| r.into())?,
|
||||
GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, None).map(|r| r.into())?,
|
||||
GameSpyVersion::Three => protocols::gamespy::three::query(&socket_addr, None).map(|r| r.into())?,
|
||||
GameSpyVersion::One => protocols::gamespy::one::query(&socket_addr, None).map(Box::new)?,
|
||||
GameSpyVersion::Two => protocols::gamespy::two::query(&socket_addr, None).map(Box::new)?,
|
||||
GameSpyVersion::Three => protocols::gamespy::three::query(&socket_addr, None).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::Quake(version) => {
|
||||
match version {
|
||||
QuakeVersion::One => protocols::quake::one::query(&socket_addr, None).map(|r| r.into())?,
|
||||
QuakeVersion::Two => protocols::quake::two::query(&socket_addr, None).map(|r| r.into())?,
|
||||
QuakeVersion::Three => protocols::quake::three::query(&socket_addr, None).map(|r| r.into())?,
|
||||
QuakeVersion::One => protocols::quake::one::query(&socket_addr, None).map(Box::new)?,
|
||||
QuakeVersion::Two => protocols::quake::two::query(&socket_addr, None).map(Box::new)?,
|
||||
QuakeVersion::Three => protocols::quake::three::query(&socket_addr, None).map(Box::new)?,
|
||||
}
|
||||
}
|
||||
Protocol::TheShip => ts::query(address, port).map(|r| r.into())?,
|
||||
Protocol::FFOW => ffow::query(address, port).map(|r| r.into())?,
|
||||
Protocol::JC2MP => jc2mp::query(address, port).map(|r| r.into())?,
|
||||
Protocol::TheShip => ts::query(address, port).map(Box::new)?,
|
||||
Protocol::FFOW => ffow::query(address, port).map(Box::new)?,
|
||||
Protocol::JC2MP => jc2mp::query(address, port).map(Box::new)?,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
protocols::{
|
||||
types::SpecificResponse,
|
||||
types::{CommonPlayer, CommonResponse, GenericPlayer},
|
||||
valve::{self, get_optional_extracted_data, Server, ServerPlayer, SteamApp},
|
||||
GenericResponse,
|
||||
},
|
||||
|
|
@ -35,6 +35,13 @@ impl TheShipPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
impl CommonPlayer for TheShipPlayer {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::TheShip(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<u32> { Some(self.score) }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Response {
|
||||
|
|
@ -61,52 +68,24 @@ pub struct Response {
|
|||
pub duration: u8,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExtraResponse {
|
||||
pub protocol: u8,
|
||||
pub player_details: Vec<TheShipPlayer>,
|
||||
pub server_type: Server,
|
||||
pub vac_secured: bool,
|
||||
pub port: Option<u16>,
|
||||
pub steam_id: Option<u64>,
|
||||
pub tv_port: Option<u16>,
|
||||
pub tv_name: Option<String>,
|
||||
pub keywords: Option<String>,
|
||||
pub rules: HashMap<String, String>,
|
||||
pub mode: u8,
|
||||
pub witnesses: u8,
|
||||
pub duration: u8,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::TheShip(self) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: Some(r.game),
|
||||
game_version: Some(r.version),
|
||||
map: Some(r.map),
|
||||
players_maximum: r.max_players.into(),
|
||||
players_online: r.players.into(),
|
||||
players_bots: Some(r.bots.into()),
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::TheShip(ExtraResponse {
|
||||
protocol: r.protocol,
|
||||
player_details: r.players_details,
|
||||
server_type: r.server_type,
|
||||
vac_secured: r.vac_secured,
|
||||
steam_id: r.steam_id,
|
||||
port: r.port,
|
||||
tv_port: r.tv_port,
|
||||
tv_name: r.tv_name,
|
||||
keywords: r.keywords,
|
||||
rules: r.rules,
|
||||
mode: r.mode,
|
||||
witnesses: r.witnesses,
|
||||
duration: r.duration,
|
||||
}),
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn game(&self) -> Option<&str> { Some(&self.game) }
|
||||
fn players_maximum(&self) -> u64 { self.max_players.into() }
|
||||
fn players_online(&self) -> u64 { self.players.into() }
|
||||
fn players_bots(&self) -> Option<u64> { Some(self.bots.into()) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players_details
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
//! let response = query(game, &"127.0.0.1".parse().unwrap(), None); // None will use the default port
|
||||
//! match response {
|
||||
//! Err(error) => println!("Couldn't query, error: {}", error),
|
||||
//! Ok(r) => println!("{:#?}", r),
|
||||
//! Ok(r) => println!("{:#?}", r.as_json()),
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -16,11 +16,18 @@ pub enum GameSpyVersion {
|
|||
Three,
|
||||
}
|
||||
|
||||
/// Enum of versions and their ExtraResponse data
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
/// Versioned response type
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VersionedExtraResponse {
|
||||
One(one::ExtraResponse),
|
||||
Two(two::ExtraResponse),
|
||||
Three(three::ExtraResponse),
|
||||
pub enum VersionedResponse<'a> {
|
||||
One(&'a one::Response),
|
||||
Two(&'a two::Response),
|
||||
Three(&'a three::Response),
|
||||
}
|
||||
|
||||
/// Versioned player type
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VersionedPlayer<'a> {
|
||||
One(&'a one::Player),
|
||||
Two(&'a two::Player),
|
||||
Three(&'a three::Player),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ use std::collections::HashMap;
|
|||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::protocols::gamespy::VersionedExtraResponse;
|
||||
use crate::protocols::{types::SpecificResponse, GenericResponse};
|
||||
use crate::protocols::gamespy::{VersionedPlayer, VersionedResponse};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::GenericResponse;
|
||||
|
||||
/// A player’s details.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
@ -23,6 +24,15 @@ pub struct Player {
|
|||
pub secret: bool,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> crate::protocols::types::GenericPlayer {
|
||||
GenericPlayer::Gamespy(VersionedPlayer::One(self))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
// TODO: Maybe frags is score?
|
||||
}
|
||||
|
||||
/// A query response.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -43,40 +53,23 @@ pub struct Response {
|
|||
pub unused_entries: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Non-generic query response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtraResponse {
|
||||
pub map_title: Option<String>,
|
||||
pub admin_contact: Option<String>,
|
||||
pub admin_name: Option<String>,
|
||||
pub players_minimum: Option<u8>,
|
||||
pub tournament: bool,
|
||||
pub unused_entries: HashMap<String, String>,
|
||||
pub players: Vec<Player>,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::GameSpy(VersionedResponse::One(self)) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: Some(r.game_type),
|
||||
game_version: Some(r.game_version),
|
||||
map: Some(r.map),
|
||||
players_maximum: r.players_maximum.try_into().unwrap(), // FIXME: usize to u64 may fail
|
||||
players_online: r.players_online.try_into().unwrap(),
|
||||
players_bots: None,
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::Gamespy(VersionedExtraResponse::One(ExtraResponse {
|
||||
map_title: r.map_title,
|
||||
admin_contact: r.admin_contact,
|
||||
admin_name: r.admin_name,
|
||||
players_minimum: r.players_minimum,
|
||||
tournament: r.tournament,
|
||||
unused_entries: r.unused_entries,
|
||||
players: r.players,
|
||||
})),
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn game(&self) -> Option<&str> { Some(&self.game_type) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.try_into().unwrap_or(0) }
|
||||
fn players_online(&self) -> u64 { self.players_online.try_into().unwrap_or(0) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::protocols::gamespy::VersionedExtraResponse;
|
||||
use crate::protocols::{types::SpecificResponse, GenericResponse};
|
||||
use crate::protocols::gamespy::{VersionedPlayer, VersionedResponse};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::GenericResponse;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
@ -17,6 +18,15 @@ pub struct Player {
|
|||
pub skill: u32,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> crate::protocols::types::GenericPlayer {
|
||||
GenericPlayer::Gamespy(VersionedPlayer::Three(self))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<u32> { Some(self.score.try_into().unwrap_or(0)) }
|
||||
}
|
||||
|
||||
/// A team's details
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -43,36 +53,23 @@ pub struct Response {
|
|||
pub unused_entries: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Non-generic query response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtraResponse {
|
||||
pub players_minimum: Option<u8>,
|
||||
pub teams: Vec<Team>,
|
||||
pub tournament: bool,
|
||||
pub unused_entries: HashMap<String, String>,
|
||||
pub players: Vec<Player>,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::GameSpy(VersionedResponse::Three(self)) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: Some(r.game_type),
|
||||
game_version: Some(r.game_version),
|
||||
map: Some(r.map),
|
||||
players_maximum: r.players_maximum.try_into().unwrap(), // FIXME: usize to u64 may fail
|
||||
players_online: r.players_online.try_into().unwrap(),
|
||||
players_bots: None,
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::Gamespy(VersionedExtraResponse::Three(ExtraResponse {
|
||||
players_minimum: r.players_minimum,
|
||||
teams: r.teams,
|
||||
tournament: r.tournament,
|
||||
unused_entries: r.unused_entries,
|
||||
players: r.players,
|
||||
})),
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn game(&self) -> Option<&str> { Some(&self.game_type) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.game_version) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.try_into().unwrap_or(0) }
|
||||
fn players_online(&self) -> u64 { self.players_online.try_into().unwrap_or(0) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::protocols::gamespy::VersionedExtraResponse;
|
||||
use crate::protocols::types::SpecificResponse;
|
||||
use crate::protocols::gamespy::{VersionedPlayer, VersionedResponse};
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::protocols::GenericResponse;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -22,6 +23,13 @@ pub struct Player {
|
|||
pub team_index: u16,
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::Gamespy(VersionedPlayer::Two(self)) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<u32> { Some(self.score.into()) }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
|
|
@ -36,34 +44,21 @@ pub struct Response {
|
|||
pub unused_entries: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Non-generic query response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtraResponse {
|
||||
pub teams: Vec<Team>,
|
||||
pub players_minimum: Option<u8>,
|
||||
pub unused_entries: HashMap<String, String>,
|
||||
pub players: Vec<Player>,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::GameSpy(VersionedResponse::Two(self)) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: None,
|
||||
game_version: None,
|
||||
map: Some(r.map),
|
||||
players_maximum: r.players_maximum.try_into().unwrap(), // FIXME: usize to u64 may fail
|
||||
players_online: r.players_online.try_into().unwrap(),
|
||||
players_bots: None,
|
||||
has_password: Some(r.has_password),
|
||||
inner: SpecificResponse::Gamespy(VersionedExtraResponse::Two(ExtraResponse {
|
||||
teams: r.teams,
|
||||
players_minimum: r.players_minimum,
|
||||
unused_entries: r.unused_entries,
|
||||
players: r.players,
|
||||
})),
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.has_password) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.try_into().unwrap_or(0) }
|
||||
fn players_online(&self) -> u64 { self.players_online.try_into().unwrap_or(0) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
use crate::{
|
||||
bufferer::Bufferer,
|
||||
protocols::{types::SpecificResponse, GenericResponse},
|
||||
protocols::{
|
||||
types::{CommonPlayer, CommonResponse, GenericPlayer},
|
||||
GenericResponse,
|
||||
},
|
||||
GDError::{PacketBad, UnknownEnumCast},
|
||||
GDResult,
|
||||
};
|
||||
|
|
@ -44,11 +47,17 @@ pub struct Player {
|
|||
pub id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum VersionedExtraResponse {
|
||||
Bedrock(BedrockExtraResponse),
|
||||
Java(JavaExtraResponse),
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::Minecraft(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
}
|
||||
|
||||
/// Versioned response type
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VersionedResponse<'a> {
|
||||
Bedrock(&'a BedrockResponse),
|
||||
Java(&'a JavaResponse),
|
||||
}
|
||||
|
||||
/// A Java query response.
|
||||
|
|
@ -78,46 +87,18 @@ pub struct JavaResponse {
|
|||
pub server_type: Server,
|
||||
}
|
||||
|
||||
/// Non-generic java response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct JavaExtraResponse {
|
||||
/// Version protocol, example: 760 (for 1.19.2). Note that for versions
|
||||
/// below 1.6 this field is always -1.
|
||||
pub version_protocol: i32,
|
||||
/// Some online players (can be missing).
|
||||
pub players_sample: Option<Vec<Player>>,
|
||||
/// The favicon (can be missing).
|
||||
pub favicon: Option<String>,
|
||||
/// Tells if the chat preview is enabled (can be missing).
|
||||
pub previews_chat: Option<bool>,
|
||||
/// Tells if secure chat is enforced (can be missing).
|
||||
pub enforces_secure_chat: Option<bool>,
|
||||
/// Tell's the server type.
|
||||
pub server_type: Server,
|
||||
}
|
||||
impl CommonResponse for JavaResponse {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Java(self)) }
|
||||
|
||||
impl From<JavaResponse> for GenericResponse {
|
||||
fn from(r: JavaResponse) -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
description: Some(r.description),
|
||||
game: Some(String::from("Minecraft")),
|
||||
game_version: Some(r.version_name),
|
||||
map: None,
|
||||
players_maximum: r.players_maximum.into(),
|
||||
players_online: r.players_online.into(),
|
||||
players_bots: None,
|
||||
has_password: None,
|
||||
inner: SpecificResponse::Minecraft(VersionedExtraResponse::Java(JavaExtraResponse {
|
||||
version_protocol: r.version_protocol,
|
||||
players_sample: r.players_sample,
|
||||
favicon: r.favicon,
|
||||
previews_chat: r.previews_chat,
|
||||
enforces_secure_chat: r.enforces_secure_chat,
|
||||
server_type: r.server_type,
|
||||
})),
|
||||
}
|
||||
fn description(&self) -> Option<&str> { Some(&self.description) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u64 { self.players_online.into() }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.version_name) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
self.players_sample
|
||||
.as_ref()
|
||||
.map(|players| players.iter().map(|p| p as &dyn CommonPlayer).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,43 +128,14 @@ pub struct BedrockResponse {
|
|||
pub server_type: Server,
|
||||
}
|
||||
|
||||
/// Non-generic bedrock response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct BedrockExtraResponse {
|
||||
/// Server's edition.
|
||||
pub edition: String,
|
||||
/// Version protocol, example: 760 (for 1.19.2).
|
||||
pub version_protocol: String,
|
||||
/// Server id.
|
||||
pub id: Option<String>,
|
||||
/// Current game mode.
|
||||
pub game_mode: Option<GameMode>,
|
||||
/// Tells the server type.
|
||||
pub server_type: Server,
|
||||
}
|
||||
impl CommonResponse for BedrockResponse {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Minecraft(VersionedResponse::Bedrock(self)) }
|
||||
|
||||
impl From<BedrockResponse> for GenericResponse {
|
||||
fn from(r: BedrockResponse) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: None,
|
||||
game_version: Some(r.version_name),
|
||||
map: r.map,
|
||||
players_maximum: r.players_maximum.into(),
|
||||
players_online: r.players_online.into(),
|
||||
players_bots: None,
|
||||
has_password: None,
|
||||
inner: SpecificResponse::Minecraft(VersionedExtraResponse::Bedrock(BedrockExtraResponse {
|
||||
edition: r.edition,
|
||||
version_protocol: r.version_protocol,
|
||||
id: r.id,
|
||||
game_mode: r.game_mode,
|
||||
server_type: r.server_type,
|
||||
})),
|
||||
}
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { self.map.as_deref() }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.version_name) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u64 { self.players_online.into() }
|
||||
}
|
||||
|
||||
impl JavaResponse {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient};
|
||||
use crate::protocols::quake::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
|
||||
use crate::{GDError, GDResult};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::SocketAddr;
|
||||
use std::slice::Iter;
|
||||
|
||||
use super::QuakePlayerType;
|
||||
|
||||
/// Quake 1 player data.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -22,6 +24,17 @@ pub struct Player {
|
|||
pub color_secondary: u8,
|
||||
}
|
||||
|
||||
impl QuakePlayerType for Player {
|
||||
fn version(response: &Response<Self>) -> super::VersionedResponse { super::VersionedResponse::One(response) }
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::QuakeOne(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<u32> { Some(self.score.into()) }
|
||||
}
|
||||
|
||||
pub(crate) struct QuakeOne;
|
||||
impl QuakeClient for QuakeOne {
|
||||
type Player = Player;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
use crate::protocols::quake::client::{client_query, remove_wrapping_quotes, QuakeClient};
|
||||
use crate::protocols::quake::one::QuakeOne;
|
||||
use crate::protocols::quake::Response;
|
||||
use crate::protocols::types::TimeoutSettings;
|
||||
use crate::protocols::types::{CommonPlayer, GenericPlayer, TimeoutSettings};
|
||||
use crate::{GDError, GDResult};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::SocketAddr;
|
||||
use std::slice::Iter;
|
||||
|
||||
use super::QuakePlayerType;
|
||||
|
||||
/// Quake 2 player data.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
@ -17,6 +19,19 @@ pub struct Player {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
impl QuakePlayerType for Player {
|
||||
fn version(response: &Response<Self>) -> super::VersionedResponse {
|
||||
super::VersionedResponse::TwoAndThree(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommonPlayer for Player {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::QuakeTwo(self) }
|
||||
|
||||
fn name(&self) -> &str { &self.name }
|
||||
// TODO: Maybe frags is score?
|
||||
}
|
||||
|
||||
pub(crate) struct QuakeTwo;
|
||||
impl QuakeClient for QuakeTwo {
|
||||
type Player = Player;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::protocols::{types::SpecificResponse, GenericResponse};
|
||||
use crate::protocols::{
|
||||
types::{CommonPlayer, CommonResponse},
|
||||
GenericResponse,
|
||||
};
|
||||
|
||||
/// General server information's.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
@ -24,30 +27,32 @@ pub struct Response<P> {
|
|||
pub unused_entries: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Non-generic quake response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtraResponse {
|
||||
/// Other server entries that weren't used.
|
||||
pub unused_entries: HashMap<String, String>,
|
||||
pub trait QuakePlayerType: Sized + CommonPlayer {
|
||||
fn version(response: &Response<Self>) -> VersionedResponse;
|
||||
}
|
||||
|
||||
impl<T> From<Response<T>> for GenericResponse {
|
||||
fn from(r: Response<T>) -> Self {
|
||||
Self {
|
||||
name: Some(r.name),
|
||||
description: None,
|
||||
game: None,
|
||||
game_version: Some(r.version),
|
||||
map: Some(r.map),
|
||||
players_maximum: r.players_maximum.into(),
|
||||
players_online: r.players_online.into(),
|
||||
players_bots: None,
|
||||
has_password: None,
|
||||
inner: SpecificResponse::Quake(ExtraResponse {
|
||||
// TODO: Players
|
||||
unused_entries: r.unused_entries,
|
||||
}),
|
||||
}
|
||||
impl<P: QuakePlayerType> CommonResponse for Response<P> {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Quake(P::version(self)) }
|
||||
|
||||
fn name(&self) -> Option<&str> { Some(&self.name) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.map) }
|
||||
fn players_maximum(&self) -> u64 { self.players_maximum.into() }
|
||||
fn players_online(&self) -> u64 { self.players_online.into() }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.version) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
Some(
|
||||
self.players
|
||||
.iter()
|
||||
.map(|p| p as &dyn CommonPlayer)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Versioned response type
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VersionedResponse<'a> {
|
||||
One(&'a Response<crate::protocols::quake::one::Player>),
|
||||
TwoAndThree(&'a Response<crate::protocols::quake::two::Player>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,51 +14,124 @@ pub enum Protocol {
|
|||
Minecraft(Option<minecraft::types::Server>),
|
||||
Quake(quake::QuakeVersion),
|
||||
Valve(valve::SteamApp),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
TheShip,
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
FFOW,
|
||||
JC2MP,
|
||||
}
|
||||
|
||||
/// A generic version of a response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
/// All response types
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GenericResponse {
|
||||
/// The name of the server
|
||||
pub name: Option<String>,
|
||||
/// Description of the server
|
||||
pub description: Option<String>,
|
||||
/// Name of the current game or game mode
|
||||
pub game: Option<String>,
|
||||
/// Version of the game being run on the server
|
||||
pub game_version: Option<String>,
|
||||
/// The current map name
|
||||
pub map: Option<String>,
|
||||
/// Maximum number of players allowed to connect
|
||||
pub players_maximum: u64,
|
||||
/// Number of players currently connected
|
||||
pub players_online: u64,
|
||||
/// Number of bots currently connected
|
||||
pub players_bots: Option<u64>,
|
||||
/// Whether the server requires a password to join
|
||||
pub has_password: Option<bool>,
|
||||
/// Data specific to non-generic responses
|
||||
pub inner: SpecificResponse,
|
||||
pub enum GenericResponse<'a> {
|
||||
GameSpy(gamespy::VersionedResponse<'a>),
|
||||
Minecraft(minecraft::VersionedResponse<'a>),
|
||||
Quake(quake::VersionedResponse<'a>),
|
||||
Valve(&'a valve::Response),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
TheShip(&'a crate::games::ts::Response),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
FFOW(&'a crate::games::ffow::Response),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
JC2MP(&'a crate::games::jc2mp::Response),
|
||||
}
|
||||
|
||||
/// A specific response containing extra data that isn't generic
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
/// All player types
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SpecificResponse {
|
||||
Gamespy(gamespy::VersionedExtraResponse),
|
||||
Minecraft(minecraft::VersionedExtraResponse),
|
||||
Quake(quake::ExtraResponse),
|
||||
Valve(valve::ExtraResponse),
|
||||
pub enum GenericPlayer<'a> {
|
||||
Valve(&'a valve::ServerPlayer),
|
||||
QuakeOne(&'a quake::one::Player),
|
||||
QuakeTwo(&'a quake::two::Player),
|
||||
Minecraft(&'a minecraft::Player),
|
||||
Gamespy(gamespy::VersionedPlayer<'a>),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
TheShip(crate::games::ts::ExtraResponse),
|
||||
TheShip(&'a crate::games::ts::TheShipPlayer),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
FFOW(crate::games::ffow::ExtraResponse),
|
||||
#[cfg(not(feature = "no_games"))]
|
||||
JC2MP,
|
||||
JCMP2(&'a crate::games::jc2mp::Player),
|
||||
}
|
||||
|
||||
pub trait CommonResponse {
|
||||
/// Get the original response type
|
||||
fn as_original(&self) -> GenericResponse;
|
||||
/// Get a struct that can be stored as JSON (you don't need to override
|
||||
/// this)
|
||||
fn as_json(&self) -> CommonResponseJson {
|
||||
CommonResponseJson {
|
||||
name: self.name(),
|
||||
description: self.description(),
|
||||
game: self.game(),
|
||||
game_version: self.game_version(),
|
||||
has_password: self.has_password(),
|
||||
map: self.map(),
|
||||
players_maximum: self.players_maximum(),
|
||||
players_online: self.players_online(),
|
||||
players_bots: self.players_bots(),
|
||||
players: self
|
||||
.players()
|
||||
.map(|players| players.iter().map(|p| p.as_json()).collect()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the server
|
||||
fn name(&self) -> Option<&str> { None }
|
||||
/// Description of the server
|
||||
fn description(&self) -> Option<&str> { None }
|
||||
/// Name of the current game or game mode
|
||||
fn game(&self) -> Option<&str> { None }
|
||||
/// Version of the game being run on the server
|
||||
fn game_version(&self) -> Option<&str> { None }
|
||||
/// The current map name
|
||||
fn map(&self) -> Option<&str> { None }
|
||||
/// Maximum number of players allowed to connect
|
||||
fn players_maximum(&self) -> u64;
|
||||
/// Number of players currently connected
|
||||
fn players_online(&self) -> u64;
|
||||
/// Number of bots currently connected
|
||||
fn players_bots(&self) -> Option<u64> { None }
|
||||
/// Whether the server requires a password to join
|
||||
fn has_password(&self) -> Option<bool> { None }
|
||||
/// Currently connected players
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> { None }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CommonResponseJson<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub description: Option<&'a str>,
|
||||
pub game: Option<&'a str>,
|
||||
pub game_version: Option<&'a str>,
|
||||
pub map: Option<&'a str>,
|
||||
pub players_maximum: u64,
|
||||
pub players_online: u64,
|
||||
pub players_bots: Option<u64>,
|
||||
pub has_password: Option<bool>,
|
||||
pub players: Option<Vec<CommonPlayerJson<'a>>>,
|
||||
}
|
||||
|
||||
pub trait CommonPlayer {
|
||||
/// Get the original player type
|
||||
fn as_original(&self) -> GenericPlayer;
|
||||
/// Get a struct that can be stored as JSON (you don't need to override
|
||||
/// this)
|
||||
fn as_json(&self) -> CommonPlayerJson {
|
||||
CommonPlayerJson {
|
||||
name: self.name(),
|
||||
score: self.score(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Player name
|
||||
fn name(&self) -> &str;
|
||||
/// Player score
|
||||
fn score(&self) -> Option<u32> { None }
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CommonPlayerJson<'a> {
|
||||
pub name: &'a str,
|
||||
pub score: Option<u32>,
|
||||
}
|
||||
|
||||
/// Timeout settings for socket operations
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ use crate::{
|
|||
SteamApp,
|
||||
},
|
||||
},
|
||||
|
||||
socket::{Socket, UdpSocket},
|
||||
utils::u8_lower_upper,
|
||||
GDError::{BadGame, Decompress, UnknownEnumCast},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::protocols::types::{CommonPlayer, CommonResponse, GenericPlayer};
|
||||
use crate::GDError::UnknownEnumCast;
|
||||
use crate::GDResult;
|
||||
use crate::{
|
||||
bufferer::Bufferer,
|
||||
protocols::{types::SpecificResponse, GenericResponse},
|
||||
};
|
||||
use crate::{bufferer::Bufferer, protocols::GenericResponse};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -58,61 +56,22 @@ pub struct Response {
|
|||
pub rules: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// Non-generic valve response
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExtraResponse {
|
||||
pub players: Option<Vec<ServerPlayer>>,
|
||||
pub rules: Option<HashMap<String, String>>,
|
||||
/// Protocol used by the server.
|
||||
pub protocol: u8,
|
||||
/// Name of the folder containing the game files.
|
||||
pub folder: String,
|
||||
/// [Steam Application ID](https://developer.valvesoftware.com/wiki/Steam_Application_ID) of game.
|
||||
pub appid: u32,
|
||||
/// Dedicated, NonDedicated or SourceTV
|
||||
pub server_type: Server,
|
||||
/// The Operating System that the server is on.
|
||||
pub environment_type: Environment,
|
||||
/// Indicates whether the server uses VAC.
|
||||
pub vac_secured: bool,
|
||||
/// [The ship](https://developer.valvesoftware.com/wiki/The_Ship) extra data
|
||||
pub the_ship: Option<TheShip>,
|
||||
/// Some extra data that the server might provide or not.
|
||||
pub extra_data: Option<ExtraData>,
|
||||
/// GoldSrc only: Indicates whether the hosted game is a mod.
|
||||
pub is_mod: bool,
|
||||
/// GoldSrc only: If the game is a mod, provide additional data.
|
||||
pub mod_data: Option<ModData>,
|
||||
}
|
||||
impl CommonResponse for Response {
|
||||
fn as_original(&self) -> GenericResponse { GenericResponse::Valve(self) }
|
||||
|
||||
impl From<Response> for GenericResponse {
|
||||
fn from(r: Response) -> Self {
|
||||
GenericResponse {
|
||||
name: Some(r.info.name),
|
||||
description: None,
|
||||
game: Some(r.info.game),
|
||||
game_version: Some(r.info.version),
|
||||
map: Some(r.info.map),
|
||||
players_maximum: r.info.players_maximum.into(),
|
||||
players_online: r.info.players_online.into(),
|
||||
players_bots: Some(r.info.players_bots.into()),
|
||||
has_password: Some(r.info.has_password),
|
||||
inner: SpecificResponse::Valve(ExtraResponse {
|
||||
players: r.players,
|
||||
rules: r.rules,
|
||||
protocol: r.info.protocol,
|
||||
folder: r.info.folder,
|
||||
appid: r.info.appid,
|
||||
server_type: r.info.server_type,
|
||||
environment_type: r.info.environment_type,
|
||||
vac_secured: r.info.vac_secured,
|
||||
the_ship: r.info.the_ship,
|
||||
extra_data: r.info.extra_data,
|
||||
is_mod: r.info.is_mod,
|
||||
mod_data: r.info.mod_data,
|
||||
}),
|
||||
}
|
||||
fn name(&self) -> Option<&str> { Some(&self.info.name) }
|
||||
fn game(&self) -> Option<&str> { Some(&self.info.game) }
|
||||
fn game_version(&self) -> Option<&str> { Some(&self.info.version) }
|
||||
fn map(&self) -> Option<&str> { Some(&self.info.map) }
|
||||
fn players_maximum(&self) -> u64 { self.info.players_maximum.into() }
|
||||
fn players_online(&self) -> u64 { self.info.players_online.into() }
|
||||
fn players_bots(&self) -> Option<u64> { Some(self.info.players_bots.into()) }
|
||||
fn has_password(&self) -> Option<bool> { Some(self.info.has_password) }
|
||||
|
||||
fn players(&self) -> Option<Vec<&dyn CommonPlayer>> {
|
||||
self.players
|
||||
.as_ref()
|
||||
.map(|p| p.iter().map(|p| p as &dyn CommonPlayer).collect())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +133,12 @@ pub struct ServerPlayer {
|
|||
pub money: Option<u32>, // the_ship
|
||||
}
|
||||
|
||||
impl CommonPlayer for ServerPlayer {
|
||||
fn as_original(&self) -> GenericPlayer { GenericPlayer::Valve(self) }
|
||||
fn name(&self) -> &str { &self.name }
|
||||
fn score(&self) -> Option<u32> { Some(self.score) }
|
||||
}
|
||||
|
||||
/// Only present for [the ship](https://developer.valvesoftware.com/wiki/The_Ship).
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue