-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* buffer: Add UTF8LengthPrefixed string decoder * games: Use expression for default port This allows us to refer to constants for the default ports if we want to (literals will still work). * games: Add support for mindustry
- Loading branch information
Showing
9 changed files
with
251 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
//! Mindustry game ping (v146) | ||
//! | ||
//! [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L225-L259) | ||
use std::{net::IpAddr, net::SocketAddr}; | ||
|
||
use crate::{GDResult, TimeoutSettings}; | ||
|
||
use self::types::ServerData; | ||
|
||
pub mod types; | ||
|
||
pub mod protocol; | ||
|
||
/// Default mindustry server port | ||
/// | ||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/Vars.java#L141-L142) | ||
pub const DEFAULT_PORT: u16 = 6567; | ||
|
||
/// Query a mindustry server. | ||
pub fn query(ip: &IpAddr, port: Option<u16>, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> { | ||
let address = SocketAddr::new(*ip, port.unwrap_or(DEFAULT_PORT)); | ||
|
||
protocol::query_with_retries(&address, timeout_settings) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
use std::net::SocketAddr; | ||
|
||
use crate::{ | ||
buffer::{self, Buffer}, | ||
socket::{Socket, UdpSocket}, | ||
utils, | ||
GDResult, | ||
TimeoutSettings, | ||
}; | ||
|
||
use super::types::ServerData; | ||
|
||
/// Mindustry max datagram packet size. | ||
pub const MAX_BUFFER_SIZE: usize = 500; | ||
|
||
/// Send a ping packet. | ||
/// | ||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/ArcNetProvider.java#L248) | ||
pub fn send_ping(socket: &mut UdpSocket) -> GDResult<()> { socket.send(&[-2i8 as u8, 1i8 as u8]) } | ||
|
||
/// Parse server data. | ||
/// | ||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135) | ||
pub fn parse_server_data<B: byteorder::ByteOrder, D: buffer::StringDecoder>( | ||
buffer: &mut Buffer<B>, | ||
) -> GDResult<ServerData> { | ||
Ok(ServerData { | ||
host: buffer.read_string::<D>(None)?, | ||
map: buffer.read_string::<D>(None)?, | ||
players: buffer.read()?, | ||
wave: buffer.read()?, | ||
version: buffer.read()?, | ||
version_type: buffer.read_string::<D>(None)?, | ||
gamemode: buffer.read::<u8>()?.try_into()?, | ||
player_limit: buffer.read()?, | ||
description: buffer.read_string::<D>(None)?, | ||
mode_name: buffer.read_string::<D>(None).ok(), | ||
}) | ||
} | ||
|
||
/// Query a Mindustry server (without retries). | ||
pub fn query(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> { | ||
let mut socket = UdpSocket::new(address, timeout_settings)?; | ||
|
||
send_ping(&mut socket)?; | ||
|
||
let socket_data = socket.receive(Some(MAX_BUFFER_SIZE))?; | ||
let mut buffer = Buffer::new(&socket_data); | ||
|
||
parse_server_data::<byteorder::BigEndian, buffer::Utf8LengthPrefixedDecoder>(&mut buffer) | ||
} | ||
|
||
/// Query a Mindustry server. | ||
pub fn query_with_retries(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<ServerData> { | ||
let retries = TimeoutSettings::get_retries_or_default(timeout_settings); | ||
|
||
utils::retry_on_timeout(retries, || query(address, timeout_settings)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use crate::{ | ||
protocols::types::{CommonResponse, GenericResponse}, | ||
GDErrorKind, | ||
}; | ||
#[cfg(feature = "serde")] | ||
use serde::{Deserialize, Serialize}; | ||
|
||
/// Mindustry sever data | ||
/// | ||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/net/NetworkIO.java#L122-L135) | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct ServerData { | ||
pub host: String, | ||
pub map: String, | ||
pub players: i32, | ||
pub wave: i32, | ||
pub version: i32, | ||
pub version_type: String, | ||
pub gamemode: GameMode, | ||
pub player_limit: i32, | ||
pub description: String, | ||
pub mode_name: Option<String>, | ||
} | ||
|
||
/// Mindustry game mode | ||
/// | ||
/// [Reference](https://github.com/Anuken/Mindustry/blob/a2e5fbdedb2fc1c8d3c157bf344d10ad6d321442/core/src/mindustry/game/Gamemode.java) | ||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub enum GameMode { | ||
Survival, | ||
Sandbox, | ||
Attack, | ||
PVP, | ||
Editor, | ||
} | ||
|
||
impl TryFrom<u8> for GameMode { | ||
type Error = GDErrorKind; | ||
fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
use GameMode::*; | ||
Ok(match value { | ||
0 => Survival, | ||
1 => Sandbox, | ||
2 => Attack, | ||
3 => PVP, | ||
4 => Editor, | ||
_ => return Err(GDErrorKind::TypeParse), | ||
}) | ||
} | ||
} | ||
|
||
impl GameMode { | ||
fn as_str(&self) -> &'static str { | ||
use GameMode::*; | ||
match self { | ||
Survival => "survival", | ||
Sandbox => "sandbox", | ||
Attack => "attack", | ||
PVP => "pvp", | ||
Editor => "editor", | ||
} | ||
} | ||
} | ||
|
||
impl CommonResponse for ServerData { | ||
fn as_original(&self) -> GenericResponse { GenericResponse::Mindustry(self) } | ||
|
||
fn players_online(&self) -> u32 { self.players.try_into().unwrap_or(0) } | ||
fn players_maximum(&self) -> u32 { self.player_limit.try_into().unwrap_or(0) } | ||
|
||
fn game_mode(&self) -> Option<&str> { Some(self.gamemode.as_str()) } | ||
|
||
fn map(&self) -> Option<&str> { Some(&self.map) } | ||
fn description(&self) -> Option<&str> { Some(&self.description) } | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use crate::protocols::types::CommonResponse; | ||
|
||
use super::ServerData; | ||
|
||
#[test] | ||
fn common_impl() { | ||
let data = ServerData { | ||
host: String::from("host"), | ||
map: String::from("map"), | ||
players: 5, | ||
wave: 2, | ||
version: 142, | ||
version_type: String::from("steam"), | ||
gamemode: super::GameMode::PVP, | ||
player_limit: 20, | ||
description: String::from("description"), | ||
mode_name: Some(String::from("campaign")), | ||
}; | ||
|
||
let common: &dyn CommonResponse = &data; | ||
|
||
assert_eq!(common.players_online(), 5); | ||
assert_eq!(common.players_maximum(), 20); | ||
assert_eq!(common.game_mode(), Some("pvp")); | ||
assert_eq!(common.map(), Some("map")); | ||
assert_eq!(common.description(), Some("description")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters