Compare commits

...
Author SHA1 Message Date
eigenein fb9beb5ecc Fix: remove the explicit cargo login call 2023-04-17 00:16:23 +02:00
eigenein 06de455795 Fix: workflow status badge 2023-04-17 00:11:52 +02:00
eigenein ac256d4c9d New: add Author::team_number 2023-04-17 00:05:03 +02:00
eigenein e853224017 Break: rename parse into from_buffer 2023-04-16 23:09:05 +02:00
eigenein 04420da334 New: impl TryInto<BattleResults> for BattleResultsDat 2023-04-16 23:05:41 +02:00
eigenein cd933f6584 Opt: chores 2023-04-16 21:35:20 +02:00
eigenein ba28620be7 New: add AvatarInfo 2022-12-07 15:40:34 +01:00
eigenein 3913046be7 Fix: forgotten AccountId leftover 2022-12-07 15:22:11 +01:00
eigenein de6deb3a06 Break: extract the re-imports to prelude 2022-12-07 15:21:21 +01:00
eigenein e1d9c27bda Break: rename author_results into author and add author.account_id 2022-12-07 14:17:23 +01:00
eigenein d2fb61425a New: initial author_results 2022-12-06 19:45:01 +01:00
eigenein 93cea446f5 New: add winning_team 2022-12-06 19:08:56 +01:00
eigenein 8d45bb28ed Opt: add .gitattributes 2022-12-06 01:14:01 +01:00
eigenein 1e0ce05c18 New: derive Serialize on battle results 2022-12-05 22:26:47 +01:00
eigenein f63ba599ec Fix: improved the docstring 2022-12-05 20:11:00 +01:00
eigenein f619ffd93e New: add LICENSE 2022-12-05 19:47:14 +01:00
Pavel Perestoronin 7d99456aaa Opt: add the docstrings, make the BattleResultsDat's fields public 2022-12-05 16:03:26 +01:00
Pavel Perestoronin be4a6c21b5 New: extract Replay::read_battle_results_dat 2022-12-05 15:50:13 +01:00
Pavel Perestoronin 805a2af81b Opt: add the keywords and categories 2022-12-03 21:15:15 +01:00
Pavel Perestoronin 899456eaf7 Break: distinguish the victory points seized vs earned 2022-12-03 21:05:52 +01:00
Pavel Perestoronin 96e63f81f4 Opt: move the test to README.md 2022-12-03 20:52:51 +01:00
17 changed files with 216 additions and 59 deletions

No files matched your search

+6
View File
@@ -49,3 +49,9 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-targets --all-features --tests -- -D warnings
- name: Publish the crates (dry run)
run: |
cargo publish --allow-dirty --dry-run
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
-1
View File
@@ -45,7 +45,6 @@ jobs:
- name: Publish the crates
run: |
cargo login
cargo publish --allow-dirty
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+2 -2
View File
@@ -7,8 +7,8 @@ authors = ["Pavel Perestoronin <[email protected]>"]
readme = "README.md"
repository = "https://github.com/eigenein/wotbreplay-parser"
license = "MIT"
keywords = []
categories = []
keywords = ["world-of-tanks-blitz", "replay", "parser", "wargaming", "results"]
categories = ["parsing"]
[features]
default = []
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Pavel Perestoronin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+29 -1
View File
@@ -4,10 +4,38 @@ World of Tanks Blitz replay parser in Rust.
[![Crates.io](https://img.shields.io/crates/v/wotbreplay-parser)](https://crates.io/crates/wotbreplay-parser)
[![Last commit](https://img.shields.io/github/last-commit/eigenein/wotbreplay-parser)](https://github.com/eigenein/wotbreplay-parser/commits/main)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/eigenein/wotbreplay-parser/Check)](https://github.com/eigenein/wotbreplay-parser/actions)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/eigenein/wotbreplay-parser/check.yaml)](https://github.com/eigenein/wotbreplay-parser/actions)
![License: MIT](https://img.shields.io/crates/l/wotbreplay-parser)
[![docs.rs](https://img.shields.io/docsrs/wotbreplay-parser)](https://docs.rs/wotbreplay-parser)
## Quickstart
```rust
use std::fs::File;
use anyhow::Result;
use wotbreplay_parser::prelude::*;
fn main() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/battle_results.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.timestamp, 1670018359);
assert_eq!(battle_results.players.len(), 14);
assert_eq!(battle_results.players[0].account_id, 520886428);
assert_eq!(battle_results.players[0].info.nickname, "77mmmr");
assert_eq!(battle_results.players[0].info.team(), TeamNumber::Two);
assert_eq!(battle_results.players[0].info.platoon_id, None);
assert_eq!(battle_results.players[1].info.nickname, "SNAK_THE_RIPPER");
assert_eq!(battle_results.players[1].info.team(), TeamNumber::One);
assert_eq!(battle_results.players[1].info.platoon_id, Some(547466834));
Ok(())
}
```
## Replay structure
`*.wotbreplay` is a ZIP-archive containing:
+8 -10
View File
@@ -2,23 +2,21 @@
#![warn(
clippy::all,
clippy::missing_const_for_fn,
clippy::trivially_copy_pass_by_ref,
clippy::map_unwrap_or,
clippy::explicit_into_iter_loop,
clippy::unused_self,
clippy::needless_pass_by_value
clippy::manual_let_else,
clippy::map_unwrap_or,
clippy::missing_const_for_fn,
clippy::needless_pass_by_value,
clippy::trivially_copy_pass_by_ref,
clippy::unused_self
)]
mod error;
pub mod models;
mod models;
pub mod prelude;
mod replay;
mod result;
pub use crate::error::Error;
pub use crate::replay::Replay;
pub use crate::result::Result;
#[cfg(doctest)]
mod test_readme {
macro_rules! external_doc_test {
+66 -13
View File
@@ -1,5 +1,6 @@
use prost::bytes::Buf;
use prost::Message;
use serde::Serialize;
use crate::error::Error;
use crate::result::Result;
@@ -8,18 +9,29 @@ impl BattleResults {
/// Parse battle results from the buffer.
///
/// Note, that this does NOT parse `battle_results.dat` itself, but the un-pickled tuple element.
pub fn parse(buffer: impl Buf) -> Result<Self> {
pub fn from_buffer(buffer: impl Buf) -> Result<Self> {
Self::decode(buffer).map_err(Error::DecodeFailed)
}
}
/// Parsed battle results.
#[derive(Message)]
#[derive(Message, Serialize)]
pub struct BattleResults {
/// Battle timestamp.
#[prost(int64, tag = "2")]
pub timestamp: i64,
#[prost(enumeration = "TeamNumber", tag = "3")]
pub winning_team: i32,
/// Replay's author results.
#[prost(message, required, tag = "8")]
pub author: Author,
/// Author's free XP, including premium.
#[prost(uint32, tag = "137")]
pub free_xp: u32,
/// Players in the battle.
#[prost(message, repeated, tag = "201")]
pub players: Vec<Player>,
@@ -30,7 +42,7 @@ pub struct BattleResults {
}
/// Battle player.
#[derive(Message)]
#[derive(Message, Serialize)]
pub struct Player {
/// Player's account ID.
#[prost(uint32, tag = "1")]
@@ -42,7 +54,7 @@ pub struct Player {
}
/// Player's extended information.
#[derive(Message)]
#[derive(Message, Serialize)]
pub struct PlayerInfo {
/// Player's nickname.
#[prost(string, tag = "1")]
@@ -56,13 +68,18 @@ pub struct PlayerInfo {
/// Player's team assignment.
#[prost(enumeration = "TeamNumber", tag = "3")]
pub team_number: i32,
pub team: i32,
#[prost(string, optional, tag = "5")]
pub clan_tag: Option<String>,
#[prost(message, required, tag = "7")]
pub avatar: Avatar,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, prost::Enumeration)]
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, prost::Enumeration, Serialize,
)]
#[repr(i32)]
pub enum TeamNumber {
One = 1,
@@ -70,7 +87,7 @@ pub enum TeamNumber {
}
/// Player's battle results.
#[derive(Message)]
#[derive(Message, Serialize)]
pub struct PlayerResults {
/// Looks like some sort of sequential result ID. They reference each other,
/// but I haven't figured this out yet.
@@ -83,11 +100,11 @@ pub struct PlayerResults {
}
/// Extended player's results information.
#[derive(Message)]
#[derive(Message, Serialize)]
pub struct PlayerResultsInfo {
/// Credits earned – without special awards and medals and premium account excluded.
#[prost(uint32, tag = "2")]
pub credits: u32,
pub credits_earned: u32,
/// Base XP (the total without multipliers).
#[prost(uint32, tag = "3")]
@@ -128,13 +145,11 @@ pub struct PlayerResultsInfo {
#[prost(uint32, tag = "18")]
pub n_enemies_destroyed: u32,
/// TODO: distinguish earned vs seized.
#[prost(uint32, tag = "32")]
pub victory_points_1: u32,
pub victory_points_earned: u32,
/// TODO: distinguish earned vs seized.
#[prost(uint32, tag = "33")]
pub victory_points_2: u32,
pub victory_points_seized: u32,
/// Player's account ID.
#[prost(uint32, tag = "101")]
@@ -162,3 +177,41 @@ impl PlayerResultsInfo {
.map(|mm_rating| (mm_rating * 10.0 + 3000.0) as u32)
}
}
#[derive(Message, Serialize)]
pub struct Author {
/// Earned credits, including premium and special awards and medals.
#[prost(uint32, tag = "2")]
pub credits_earned: u32,
/// Combat XP, including premium.
#[prost(uint32, tag = "3")]
pub combat_xp: u32,
#[prost(uint32, tag = "8")]
pub damage_dealt: u32,
#[prost(uint32, tag = "101")]
pub account_id: u32,
#[prost(enumeration = "TeamNumber", tag = "102")]
pub team_number: i32,
}
#[derive(Message, Serialize)]
pub struct Avatar {
#[prost(message, required, tag = "2")]
pub info: AvatarInfo,
}
#[derive(Message, Serialize)]
pub struct AvatarInfo {
#[prost(string, tag = "2")]
pub gfx_url: String,
#[prost(string, tag = "3")]
pub gfx2_url: String,
#[prost(string, tag = "4")]
pub kind: String,
}
+14 -3
View File
@@ -4,16 +4,19 @@ use prost::Message;
use serde::Deserialize;
use serde_bytes::ByteBuf;
use crate::error::Error;
use crate::models::BattleResults;
use crate::{Error, Result};
use crate::result::Result;
/// Represents un-pickled `battle_results.dat`.
#[derive(Debug, Deserialize)]
pub struct BattleResultsDat(
/// No idea what this is.
u64,
/// My best guess – some kind of checksum,
/// but none of MD5, SHA1, SHA224, SHA256, SHA384, SHA512, nor CRC64 over the buffer have matched.
pub u64,
/// Protobuf-serialized battle results.
ByteBuf,
pub ByteBuf,
);
impl BattleResultsDat {
@@ -27,3 +30,11 @@ impl BattleResultsDat {
BattleResults::decode(self.1.as_ref()).map_err(Error::DecodeFailed)
}
}
impl TryInto<BattleResults> for BattleResultsDat {
type Error = Error;
fn try_into(self) -> Result<BattleResults> {
self.decode_battle_results()
}
}
+4
View File
@@ -0,0 +1,4 @@
pub use crate::error::Error;
pub use crate::models::*;
pub use crate::replay::Replay;
pub use crate::result::Result;
+6 -1
View File
@@ -19,10 +19,15 @@ impl<R: Read + Seek> Replay<R> {
/// Reads and parses the battle results from the replay.
pub fn read_battle_results(&mut self) -> Result<BattleResults> {
self.read_battle_results_dat()?.decode_battle_results()
}
/// Reads and parses the included `battle_results.dat`.
pub fn read_battle_results_dat(&mut self) -> Result<BattleResultsDat> {
let pickled_battle_results = self
.0
.by_name("battle_results.dat")
.map_err(Error::OpenBattleResultsFailed)?;
BattleResultsDat::from_reader(pickled_battle_results)?.decode_battle_results()
BattleResultsDat::from_reader(pickled_battle_results)
}
}
+59 -28
View File
@@ -1,32 +1,11 @@
use std::fs::File;
use anyhow::Result;
use wotbreplay_parser::models::TeamNumber;
use wotbreplay_parser::Replay;
#[test]
fn battle_results_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/battle_results_ok.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.timestamp, 1670018359);
assert_eq!(battle_results.players.len(), 14);
assert_eq!(battle_results.players[0].account_id, 520886428);
assert_eq!(battle_results.players[0].info.nickname, "77mmmr");
assert_eq!(battle_results.players[0].info.team_number(), TeamNumber::Two);
assert_eq!(battle_results.players[0].info.platoon_id, None);
assert_eq!(battle_results.players[1].info.nickname, "SNAK_THE_RIPPER");
assert_eq!(battle_results.players[1].info.team_number(), TeamNumber::One);
assert_eq!(battle_results.players[1].info.platoon_id, Some(547466834));
Ok(())
}
use wotbreplay_parser::prelude::*;
#[test]
fn player_results_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/player_results_ok.wotbreplay")?)?
let battle_results = Replay::open(File::open("tests/replays/player_results.wotbreplay")?)?
.read_battle_results()?;
let snak_the_ripper_info = &battle_results.player_results[3].info;
@@ -55,10 +34,10 @@ fn player_results_ok() -> Result<()> {
assert_eq!(zeekrab_info.n_hits_received, 3);
assert_eq!(zeekrab_info.n_penetrations_received, 3);
assert_eq!(zeekrab_info.n_non_penetrating_hits_received, 0);
assert_eq!(zeekrab_info.victory_points_1, 40);
assert_eq!(zeekrab_info.victory_points_2, 40);
assert_eq!(zeekrab_info.victory_points_earned, 40);
assert_eq!(zeekrab_info.victory_points_seized, 40);
assert_eq!(zeekrab_info.tank_id, 26657);
assert_eq!(zeekrab_info.credits, 37679);
assert_eq!(zeekrab_info.credits_earned, 37679);
assert_eq!(zeekrab_info.n_enemies_damaged, 2);
assert_eq!(zeekrab_info.n_enemies_destroyed, 1);
@@ -88,13 +67,65 @@ fn player_results_ok() -> Result<()> {
}
#[test]
fn clan_tags_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/player_results_ok.wotbreplay")?)?
fn player_info_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/player_results.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.players[1].info.clan_tag.as_deref(), Some("AN0NY"));
assert_eq!(battle_results.players[10].info.clan_tag.as_deref(), Some("BBS"));
assert_eq!(battle_results.players[10].info.avatar.info.kind, "legendary");
assert_eq!(
battle_results.players[10].info.avatar.info.gfx_url,
"https://stufficons.wgcdn.co/Gfx/avatar_UFO_legendary_anim/220523_104739_752791.webp"
);
assert_eq!(
battle_results.players[10].info.avatar.info.gfx2_url,
"https://stufficons.wgcdn.co/Gfx2/avatar_UFO_legendary_anim/220523_104755_251331.webp"
);
assert_eq!(battle_results.players[11].info.clan_tag, None);
Ok(())
}
#[test]
fn victory_points_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/victory_points.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.player_results[1].info.victory_points_seized, 40);
assert_eq!(battle_results.player_results[1].info.victory_points_earned, 40);
assert_eq!(battle_results.player_results[3].info.victory_points_seized, 0);
assert_eq!(battle_results.player_results[3].info.victory_points_earned, 112);
assert_eq!(battle_results.player_results[5].info.victory_points_earned, 280);
assert_eq!(battle_results.player_results[5].info.victory_points_seized, 0);
Ok(())
}
#[test]
fn winning_team_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/winning_team.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.winning_team(), TeamNumber::One);
Ok(())
}
#[test]
fn author_results_ok() -> Result<()> {
let battle_results = Replay::open(File::open("tests/replays/author_results.wotbreplay")?)?
.read_battle_results()?;
assert_eq!(battle_results.author.credits_earned, 53623);
assert_eq!(battle_results.author.combat_xp, 2370);
assert_eq!(battle_results.author.damage_dealt, 1738);
assert_eq!(battle_results.author.account_id, 594778041);
assert_eq!(battle_results.free_xp, 116);
Ok(())
}
+1
View File
@@ -0,0 +1 @@
*.wotbreplay linguist-generated
Binary file not shown.
File renamed without changes.
File renamed without changes.
Binary file not shown.
Binary file not shown.