From c986abca5d7d97e7e911b8d8fecbd311934c3fde Mon Sep 17 00:00:00 2001 From: Pavel Perestoronin Date: Mon, 8 May 2023 23:39:52 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A5=20Improve=20the=20docs=20and=20err?= =?UTF-8?q?or=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/error.rs | 12 ++++++++---- src/models/battle_results.rs | 15 +++++++++------ src/models/data.rs | 11 +++++++++-- src/models/data/base_player_create.rs | 14 +++++++++++++- src/models/data/entity_method.rs | 2 +- src/models/data/packet.rs | 15 ++++++++++++++- src/models/data/payload.rs | 2 +- src/replay.rs | 2 +- tests/data.rs | 2 +- 10 files changed, 58 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7535927..cc022e5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ use wotbreplay_parser::replay::Replay; fn main() -> Result<()> { let battle_results = Replay::open(File::open("replays/20221203_player_results.wotbreplay")?)?.read_battle_results()?; - assert_eq!(battle_results.timestamp, 1670083956); + assert_eq!(battle_results.timestamp_secs, 1670083956); assert_eq!(battle_results.players.len(), 14); assert_eq!(battle_results.players[0].account_id, 595693744); diff --git a/src/error.rs b/src/error.rs index 7914a44..2bfe7fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,9 +21,13 @@ pub enum Error { #[error("JSON error")] JsonError(#[from] serde_json::Error), - #[error("invalid magic: {0:#X}, expected: {1:#X}")] - InvalidMagic(u32, u32), + #[error("invalid magic: {actual:#X}, expected: {expected:#X}")] + InvalidMagic { expected: u32, actual: u32 }, - #[error("failed to read a packet length, got {0} bytes")] - PacketLengthError(usize), + #[error("failed to parse type {type_} packet's payload at replay time {clock_secs}s")] + PacketPayloadParsingError { + source: Box, + type_: u32, + clock_secs: f32, + }, } diff --git a/src/models/battle_results.rs b/src/models/battle_results.rs index 484574e..a56c73a 100644 --- a/src/models/battle_results.rs +++ b/src/models/battle_results.rs @@ -9,7 +9,7 @@ impl BattleResults { /// /// # Note /// - /// This does **not** parse `battle_results.dat` itself, but the un-pickled tuple element. + /// This can **not** parse `battle_results.dat` itself, but the un-pickled tuple element. /// To parse `battle_results.dat`, use [`crate::models::battle_results_dat::BattleResultsDat`]. pub fn from_buffer(buffer: impl Buf) -> Result { Ok(Self::decode(buffer)?) @@ -23,7 +23,7 @@ impl BattleResults { pub struct BattleResults { /// Battle timestamp. #[prost(int64, tag = "2")] - pub timestamp: i64, + pub timestamp_secs: i64, #[prost(enumeration = "TeamNumber", optional, tag = "3")] pub winner_team_number: Option, @@ -40,7 +40,7 @@ pub struct BattleResults { #[prost(message, repeated, tag = "201")] pub players: Vec, - /// Player's results. + /// Individual player's results. #[prost(message, repeated, tag = "301")] pub player_results: Vec, } @@ -65,8 +65,9 @@ pub struct PlayerInfo { pub nickname: String, /// Some sort of platoon ID: - /// - contains same ID for a platoon members, or - /// - `None` for non-platoon players + /// + /// - contains same ID for the platoon members, or + /// - [`None`] for non-platoon players #[prost(uint32, optional, tag = "2")] pub platoon_id: Option, @@ -165,6 +166,8 @@ pub struct PlayerResultsInfo { /// /// Note, that this is **not** the game client's displayed rating. /// This field matches the `mm_rating` as returned by the Wargaming.net API. + /// + /// The display rating is calculated as: `3000.0 + mm_rating * 10.0`. #[prost(float, optional, tag = "107")] pub mm_rating: Option, @@ -173,7 +176,7 @@ pub struct PlayerResultsInfo { } impl PlayerResultsInfo { - /// Returns displayed rating as per the game client. + /// Calculate displayed rating according to the game client. pub fn display_rating(&self) -> Option { self.mm_rating .map(|mm_rating| (mm_rating * 10.0 + 3000.0) as u32) diff --git a/src/models/data.rs b/src/models/data.rs index 8858a6b..9e94e9a 100644 --- a/src/models/data.rs +++ b/src/models/data.rs @@ -53,7 +53,10 @@ fn assert_magic + PartialEq>(actual: T, expected: T) -> Result { if actual == expected { Ok(()) } else { - Err(Error::InvalidMagic(actual.into(), expected.into())) + Err(Error::InvalidMagic { + actual: actual.into(), + expected: expected.into(), + }) } } @@ -82,7 +85,11 @@ fn read_pickled(reader: &mut impl Read, length: usize) -> R /// Read 2-byte length surrounded by the magical `0xFF` and `0x00`, /// unless it's a simple 1-byte length. /// -/// P.S. Okay, Wargaming, I've no idea what you smoke, but I want some! +/// # Context +/// +/// It seems that for payloads larger than 254 bytes, Wargaming writes `00 FF`, +/// where the word in the middle is the actual length of a payload, and the `00` and `FF` +/// are magic values. #[inline] fn read_quirky_length(reader: &mut impl Read) -> Result { match reader.read_u8()? { diff --git a/src/models/data/base_player_create.rs b/src/models/data/base_player_create.rs index 4ca906a..928cbe4 100644 --- a/src/models/data/base_player_create.rs +++ b/src/models/data/base_player_create.rs @@ -22,8 +22,16 @@ pub struct BasePlayerCreate { #[serde(rename(deserialize = "camouflageSlot"))] pub camouflage_slot: u8, + /// Average team ratings for rating battles. + /// + /// # Contains + /// + /// - [`Some`] 2-vector, if the attribute is present in the replay. Each element represents one team: + /// - [`Some`] rating, if known + /// - [`None`], if the average rating is unknown (all the players are calibrating) + /// - [`None`], if the attribute is missing in the replay #[serde(default, rename(deserialize = "avgMmr"))] - pub average_mmr: Option>>, + pub average_mm_ratings: Option>>, #[serde(default, rename(deserialize = "playerWaitTimes"))] pub player_wait_times: Option>, @@ -35,6 +43,10 @@ pub struct BasePlayerCreate { pub turbo_battles_statistics: Option, } +/// I don't know what that is: +/// +/// - On EU, Asia, and NA the values are just constants +/// - On Russia, the `alpha` and `beta` are different constants, and the chances actually differ for different replays #[derive(Debug, Serialize, Deserialize)] pub struct TurboBattlesStatistics { #[serde(rename(deserialize = "turboBattleChance"))] diff --git a/src/models/data/entity_method.rs b/src/models/data/entity_method.rs index 70e2746..51b3866 100644 --- a/src/models/data/entity_method.rs +++ b/src/models/data/entity_method.rs @@ -26,7 +26,7 @@ pub enum EntityMethod { } impl EntityMethod { - /// Parse the entity method payload. + /// Parse the entity method raw payload. pub fn new(raw_payload: &[u8]) -> Result { let mut raw_payload = raw_payload; diff --git a/src/models/data/packet.rs b/src/models/data/packet.rs index c7e7cb8..98f402f 100644 --- a/src/models/data/packet.rs +++ b/src/models/data/packet.rs @@ -6,6 +6,7 @@ use serde_with::serde_as; use crate::models::data::payload::Payload; use crate::result::Result; +use crate::Error; /// «Packet» is a single traffic message in a replay, /// packets tell us what is going on during the battle. @@ -28,14 +29,26 @@ pub struct Packet { } impl Packet { + /// Parse next packet from the reader. + /// + /// # Returns + /// + /// Parsed packet, or [`None`], when no packets are left. pub fn from_reader(reader: &mut impl Read) -> Result> { let Ok(length) = reader.read_u32::() else { return Ok(None) }; let type_ = reader.read_u32::()?; let clock_secs = reader.read_f32::()?; let raw_payload = Self::read_raw_payload(reader, length as usize)?; + let payload = Payload::new(type_, &raw_payload).map_err(|source| { + Error::PacketPayloadParsingError { + source: Box::new(source), + type_, + clock_secs, + } + })?; let this = Self { clock_secs, - payload: Payload::new(type_, &raw_payload)?, + payload, #[cfg(feature = "raw-payload")] raw_payload, diff --git a/src/models/data/payload.rs b/src/models/data/payload.rs index 99a3376..a2edee6 100644 --- a/src/models/data/payload.rs +++ b/src/models/data/payload.rs @@ -29,7 +29,7 @@ pub enum Payload { } impl Payload { - /// Parse the packet payload. + /// Parse the raw packet payload. pub fn new(packet_type: u32, raw_payload: &[u8]) -> Result { let mut raw_payload = raw_payload; diff --git a/src/replay.rs b/src/replay.rs index 89ad361..41cfbe6 100644 --- a/src/replay.rs +++ b/src/replay.rs @@ -21,7 +21,7 @@ use crate::result::Result; pub struct Replay(pub(crate) ZipArchive); impl Replay { - /// Opens a replay from the reader which contains a `*.wotbreplay`. + /// Open a replay from the reader which contains a `*.wotbreplay`. pub fn open(reader: R) -> Result { Ok(Self(ZipArchive::new(reader)?)) } diff --git a/tests/data.rs b/tests/data.rs index 62aa9f6..f465c89 100644 --- a/tests/data.rs +++ b/tests/data.rs @@ -50,7 +50,7 @@ fn base_player_create_ok() -> Result<()> { assert_eq!(arguments.matchmaker_type, 2); assert_eq!(arguments.camouflage_slot, 1); assert_eq!( - arguments.average_mmr, + arguments.average_mm_ratings, Some(vec![Some(59.29457753400008), Some(99.34182121604681)]) ); assert_eq!(