mirror of
https://github.com/maciejpedzich/f1-game-packet-parser.git
synced 2025-04-12 00:21:11 +02:00
docs: add frontpage doc comments and tweak various existing ones
This commit is contained in:
parent
1bdf45a1ee
commit
72a4a21115
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# f1-game-packet-parser
|
||||
|
||||
This is a Rust crate that allows you to convert raw binary data from F1 24, F1 23, and F1 22 UDP telemetry into organised structs.
|
||||
|
||||
## Getting started
|
||||
|
||||
Add `f1_game_packet_parser` to your project's `Cargo.toml` file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
f1_game_packet_parser = "1.0.0"
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
This crate doesn't provide a UDP client out of the box. Here's how to write one that will parse and pretty-print incoming packets:
|
||||
|
||||
```rust
|
||||
use f1_game_packet_parser::parse;
|
||||
use std::error::Error;
|
||||
use std::net::UdpSocket;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// This IP and port should be set in the game's options by default.
|
||||
let socket = UdpSocket::bind("127.0.0.1:20777")?;
|
||||
let mut buf = [0u8; 1460];
|
||||
|
||||
loop {
|
||||
// Receive raw packet data from the game.
|
||||
// The buf array should be large enough for all types of packets.
|
||||
let (amt, _) = socket.recv_from(&mut buf)?;
|
||||
|
||||
// Convert received bytes to an F1Packet struct and print it.
|
||||
let packet = parse(&buf[..amt])?;
|
||||
println!("{:#?}", packet);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Minimum supported Rust version
|
||||
|
||||
The minimum supported Rust version is documented in the Cargo.toml file. This may be bumped in minor releases if necessary.
|
||||
|
||||
## Original documentation links
|
||||
|
||||
- [F1 24](https://forums.ea.com/discussions/f1-24-general-discussion-en/f1-24-udp-specification/8369125)
|
||||
- [F1 23](https://forums.ea.com/discussions/f1-23-en/f1-23-udp-specification/8390745)
|
||||
- [F1 22](https://forums.ea.com/discussions/f1-games-franchise-discussion-en/f1-22-udp-specification/8418392)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
@ -1,8 +1,16 @@
|
||||
/// Unique identifiers of drivers.
|
||||
pub mod driver_id;
|
||||
pub mod team_id;
|
||||
/// The wheel order is: `REAR_LEFT`, `REAR_RIGHT`, `FRONT_LEFT`, `FRONT_RIGHT`.
|
||||
pub mod wheel_index;
|
||||
/// Unique identifiers of session types.
|
||||
pub mod session_type;
|
||||
/// Unique identifiers of teams.
|
||||
pub mod team_id;
|
||||
/// Indexes of wheels in wheel-oriented arrays.
|
||||
/// The order is:
|
||||
/// [`REAR_LEFT`](const@wheel_index::REAR_LEFT),
|
||||
/// [`REAR_RIGHT`](const@wheel_index::REAR_RIGHT),
|
||||
/// [`FRONT_LEFT`](const@wheel_index::FRONT_LEFT),
|
||||
/// [`FRONT_RIGHT`](const@wheel_index::FRONT_RIGHT).
|
||||
pub mod wheel_index;
|
||||
|
||||
use binrw::BinRead;
|
||||
use bitflags::bitflags;
|
||||
@ -10,6 +18,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) const MAX_NUM_CARS: usize = 22;
|
||||
|
||||
/// Unique identifier of the type of this packet.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -28,7 +38,7 @@ pub(crate) const MAX_NUM_CARS: usize = 22;
|
||||
pub enum PacketId {
|
||||
Motion = 0,
|
||||
Session = 1,
|
||||
LapData = 2,
|
||||
Laps = 2,
|
||||
Event = 3,
|
||||
Participants = 4,
|
||||
CarSetups = 5,
|
||||
@ -43,6 +53,9 @@ pub enum PacketId {
|
||||
TimeTrial = 14,
|
||||
}
|
||||
|
||||
/// Flag that's currently being waved in
|
||||
/// a [`MarshalZone`](crate::packets::session::MarshalZone).
|
||||
/// Represents an [`i8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -67,6 +80,7 @@ pub enum MarshalZoneFlag {
|
||||
Red = 4,
|
||||
}
|
||||
|
||||
/// Session/forecast weather type. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -91,6 +105,7 @@ pub enum Weather {
|
||||
Storm = 5,
|
||||
}
|
||||
|
||||
/// Temperature change direction. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -112,6 +127,7 @@ pub enum TemperatureChange {
|
||||
NoChange = 2,
|
||||
}
|
||||
|
||||
/// Unique circuit ID. Represents an [`i8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -128,42 +144,79 @@ pub enum TemperatureChange {
|
||||
)]
|
||||
#[br(little, repr(i8))]
|
||||
pub enum TrackId {
|
||||
/// Unknown circuit.
|
||||
Unknown = -1,
|
||||
Melbourne = 0,
|
||||
/// Australian Grand Prix.
|
||||
AlbertPark = 0,
|
||||
/// French Grand Prix.
|
||||
PaulRicard = 1,
|
||||
/// Chinese Grand Prix.
|
||||
Shanghai = 2,
|
||||
/// Bahrain Grand Prix.
|
||||
Sakhir = 3,
|
||||
/// Spanish Grand Prix.
|
||||
Catalunya = 4,
|
||||
Monaco = 5,
|
||||
/// Monaco Grand Prix.
|
||||
MonteCarlo = 5,
|
||||
/// Canadian Grand Prix.
|
||||
Montreal = 6,
|
||||
/// British Grand Prix.
|
||||
Silverstone = 7,
|
||||
/// German Grand Prix.
|
||||
Hockenheim = 8,
|
||||
/// Hungarian Grand Prix.
|
||||
Hungaroring = 9,
|
||||
/// Belgian Grand Prix.
|
||||
Spa = 10,
|
||||
/// Italian Grand Prix.
|
||||
Monza = 11,
|
||||
Singapore = 12,
|
||||
/// Singapore Grand Prix.
|
||||
MarinaBay = 12,
|
||||
/// Japanese Grand Prix.
|
||||
Suzuka = 13,
|
||||
AbuDhabi = 14,
|
||||
Texas = 15,
|
||||
Brazil = 16,
|
||||
Austria = 17,
|
||||
/// Abu Dhabi Grand Prix.
|
||||
YasMarina = 14,
|
||||
/// Circuit of the Americas. United States (Texas) Grand Prix.
|
||||
Cota = 15,
|
||||
/// Brazilian (Sao Paulo) Grand Prix.
|
||||
Interlagos = 16,
|
||||
/// Austrian Grand Prix.
|
||||
RedBullRing = 17,
|
||||
/// Russian Grand Prix.
|
||||
Sochi = 18,
|
||||
Mexico = 19,
|
||||
/// Mexican Grand Prix.
|
||||
MexicoCity = 19,
|
||||
/// Azerbaijan Grand Prix.
|
||||
Baku = 20,
|
||||
/// Short variant of the [`Sakhir`](TrackId::Sakhir) circuit.
|
||||
SakhirShort = 21,
|
||||
/// Short variant of the [`Silverstone`](TrackId::Silverstone) circuit.
|
||||
SilverstoneShort = 22,
|
||||
TexasShort = 23,
|
||||
/// Short variant of the [`Cota`](TrackId::Cota) circuit.
|
||||
CotaShort = 23,
|
||||
/// Short variant of the [`Suzuka`](TrackId::Suzuka) circuit.
|
||||
SuzukaShort = 24,
|
||||
/// Vietnamese Grand Prix.
|
||||
Hanoi = 25,
|
||||
/// Dutch Grand Prix.
|
||||
Zandvoort = 26,
|
||||
/// ~~San Marino~~ Emilia-Romagna Grand Prix.
|
||||
Imola = 27,
|
||||
/// Portuguese Grand Prix.
|
||||
Portimao = 28,
|
||||
/// Saudi Arabian Grand Prix.
|
||||
Jeddah = 29,
|
||||
/// Miami Grand Prix.
|
||||
Miami = 30,
|
||||
/// Las Vegas Grand Prix.
|
||||
LasVegas = 31,
|
||||
/// Qatar Grand Prix.
|
||||
Losail = 32,
|
||||
}
|
||||
|
||||
/// Type of cars being raced in
|
||||
/// [`F1PacketSession`](struct@crate::F1PacketSession).
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -192,6 +245,8 @@ pub enum Formula {
|
||||
F1Elimination = 9,
|
||||
}
|
||||
|
||||
/// Safety car deployment status in [`F1PacketSession`](struct@crate::F1PacketSession).
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -214,6 +269,9 @@ pub enum SafetyCarStatus {
|
||||
FormationLap = 3,
|
||||
}
|
||||
|
||||
/// Accuracy of a
|
||||
/// [`WeatherForecastSample`](struct@crate::packets::session::WeatherForecastSample).
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -234,6 +292,7 @@ pub enum ForecastAccuracy {
|
||||
Approximate = 1,
|
||||
}
|
||||
|
||||
/// Type of enabled braking assist. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -256,6 +315,7 @@ pub enum BrakingAssist {
|
||||
High = 3,
|
||||
}
|
||||
|
||||
/// Type of enabled gearbox assist. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -278,6 +338,7 @@ pub enum GearboxAssist {
|
||||
Automatic = 3,
|
||||
}
|
||||
|
||||
/// Type of enabled racing line assist. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -299,6 +360,7 @@ pub enum DynamicRacingLine {
|
||||
Full = 2,
|
||||
}
|
||||
|
||||
/// Shape of the racing line. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -319,6 +381,7 @@ pub enum DynamicRacingLineType {
|
||||
ThreeDimensional = 1,
|
||||
}
|
||||
|
||||
/// Game mode that's currently in use. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -359,6 +422,7 @@ pub enum GameMode {
|
||||
Benchmark = 127,
|
||||
}
|
||||
|
||||
/// Set of rules that's in use for this session. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -386,6 +450,7 @@ pub enum RuleSet {
|
||||
RivalDuel = 11,
|
||||
}
|
||||
|
||||
/// Length of the ongoing session. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -411,6 +476,7 @@ pub enum SessionLength {
|
||||
Full = 7,
|
||||
}
|
||||
|
||||
/// Whether the car is outside/entering/in the pit lane. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -432,6 +498,29 @@ pub enum PitStatus {
|
||||
InPitArea = 2,
|
||||
}
|
||||
|
||||
/// Zero-based sector number. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[br(little, repr(u8))]
|
||||
pub enum Sector {
|
||||
First = 0,
|
||||
Second = 1,
|
||||
Third = 2,
|
||||
}
|
||||
|
||||
/// Status of a driver in the current session. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -455,6 +544,8 @@ pub enum DriverStatus {
|
||||
OnTrack = 4,
|
||||
}
|
||||
|
||||
/// Status of a driver's result in the current session and final classification.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -481,6 +572,7 @@ pub enum ResultStatus {
|
||||
Retired = 7,
|
||||
}
|
||||
|
||||
/// Type of penalty awarded to a driver. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -517,6 +609,7 @@ pub enum PenaltyType {
|
||||
BlackFlagTimer = 17,
|
||||
}
|
||||
|
||||
/// Type of offence commited by a driver. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -588,6 +681,9 @@ pub enum InfringementType {
|
||||
AttributeAssigned = 54,
|
||||
}
|
||||
|
||||
/// Bit flags of specific controller buttons being pressed
|
||||
/// in a [`Buttons` event](variant@crate::packets::event::EventDetails::Buttons).
|
||||
/// Represents a [`u32`].
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
@ -672,6 +768,7 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique identifier of a driver's nationality. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -781,6 +878,7 @@ pub enum Nationality {
|
||||
Filipino = 90,
|
||||
}
|
||||
|
||||
/// "Your telemetry" UDP setting value. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -801,6 +899,7 @@ pub enum YourTelemetry {
|
||||
Public = 1,
|
||||
}
|
||||
|
||||
/// Type of surface a tyre is on. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -831,6 +930,8 @@ pub enum Surface {
|
||||
Ridged = 11,
|
||||
}
|
||||
|
||||
/// Bit flags of lit rev lights on a steering wheel.
|
||||
/// Represents a [`u16`].
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
@ -871,6 +972,7 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Index of currently open multi-function display panel. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -895,6 +997,7 @@ pub enum MfdPanelIndex {
|
||||
Closed = 255,
|
||||
}
|
||||
|
||||
/// Type of enabled traction control assist. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -916,6 +1019,7 @@ pub enum TractionControl {
|
||||
Full = 2,
|
||||
}
|
||||
|
||||
/// Type of fuel mix that's currently in use. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -938,6 +1042,7 @@ pub enum FuelMix {
|
||||
Max = 3,
|
||||
}
|
||||
|
||||
/// ERS deployment mode that's currently in use. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -960,6 +1065,7 @@ pub enum ErsDeployMode {
|
||||
Hotlap = 3,
|
||||
}
|
||||
|
||||
/// Flag the driver is currently being shown. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -984,6 +1090,7 @@ pub enum VehicleFiaFlag {
|
||||
Red = 4,
|
||||
}
|
||||
|
||||
/// Global DRS activation permission status. Represents an [`i8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1005,6 +1112,7 @@ pub enum DrsAllowed {
|
||||
Allowed = 1,
|
||||
}
|
||||
|
||||
/// Session-independent tyre compound type. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1022,14 +1130,14 @@ pub enum DrsAllowed {
|
||||
#[br(little, repr(u8))]
|
||||
pub enum ActualTyreCompound {
|
||||
Unknown = 0,
|
||||
F1C5 = 16,
|
||||
F1C4 = 17,
|
||||
F1C3 = 18,
|
||||
F1C2 = 19,
|
||||
F1C1 = 20,
|
||||
F1C0 = 21,
|
||||
F1Inter = 7,
|
||||
F1Wet = 8,
|
||||
C5 = 16,
|
||||
C4 = 17,
|
||||
C3 = 18,
|
||||
C2 = 19,
|
||||
C1 = 20,
|
||||
C0 = 21,
|
||||
Inter = 7,
|
||||
Wet = 8,
|
||||
ClassicDry = 9,
|
||||
ClassicWet = 10,
|
||||
F2SuperSoft = 11,
|
||||
@ -1039,6 +1147,8 @@ pub enum ActualTyreCompound {
|
||||
F2Wet = 15,
|
||||
}
|
||||
|
||||
/// Visual indicator of a tyre compound's type in a given session.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1070,6 +1180,7 @@ pub enum VisualTyreCompound {
|
||||
F2Wet = 15,
|
||||
}
|
||||
|
||||
/// Readiness of a player in an online lobby. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1091,6 +1202,8 @@ pub enum ReadyStatus {
|
||||
Spectating = 2,
|
||||
}
|
||||
|
||||
/// Bit flags of lap validity across all three sectors and overall.
|
||||
/// Represents a [`u8`].
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
@ -1109,6 +1222,7 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Speed unit used by a player. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1129,6 +1243,7 @@ pub enum SpeedUnit {
|
||||
KilometresPerHour = 1,
|
||||
}
|
||||
|
||||
/// Temperature unit used by a player. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1149,6 +1264,8 @@ pub enum TemperatureUnit {
|
||||
Fahrenheit = 1,
|
||||
}
|
||||
|
||||
/// Console or PC game distribution platform used by a player.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1173,6 +1290,8 @@ pub enum Platform {
|
||||
Unknown = 255,
|
||||
}
|
||||
|
||||
/// Recovery mode assist that's currently enabled.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1194,6 +1313,8 @@ pub enum RecoveryMode {
|
||||
AutoRecovery = 2,
|
||||
}
|
||||
|
||||
/// Flashback usage limit that's currently enabled.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1216,6 +1337,8 @@ pub enum FlashbackLimit {
|
||||
Unlimited = 3,
|
||||
}
|
||||
|
||||
/// Type of surface simulation that's currently enabled.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1231,11 +1354,12 @@ pub enum FlashbackLimit {
|
||||
Deserialize,
|
||||
)]
|
||||
#[br(little, repr(u8))]
|
||||
pub enum SurfaceType {
|
||||
pub enum SurfaceSimType {
|
||||
Simplified = 0,
|
||||
Realistic = 1,
|
||||
}
|
||||
|
||||
/// Difficulty of driving with low fuel. Represent a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1256,6 +1380,8 @@ pub enum LowFuelMode {
|
||||
Hard = 1,
|
||||
}
|
||||
|
||||
/// Race starts assist that's currently in use.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1276,6 +1402,8 @@ pub enum RaceStarts {
|
||||
Assisted = 1,
|
||||
}
|
||||
|
||||
/// Type of tyre temperature simulation that's currently in use.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1296,6 +1424,8 @@ pub enum TyreTemperature {
|
||||
SurfaceAndCarcass = 1,
|
||||
}
|
||||
|
||||
/// Type of car damage simulation that's currently in use.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1318,6 +1448,7 @@ pub enum CarDamage {
|
||||
Simulation = 3,
|
||||
}
|
||||
|
||||
/// Car damage severity. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1339,6 +1470,8 @@ pub enum CarDamageRate {
|
||||
Simulation = 2,
|
||||
}
|
||||
|
||||
/// Type of collision simulation that's currently enabled.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1360,6 +1493,8 @@ pub enum Collisions {
|
||||
On = 2,
|
||||
}
|
||||
|
||||
/// Type of corner cutting and track limits punishability.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1380,6 +1515,7 @@ pub enum CornerCuttingStringency {
|
||||
Strict = 1,
|
||||
}
|
||||
|
||||
/// The way the game handles pit stops. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1401,6 +1537,8 @@ pub enum PitStopExperience {
|
||||
Immersive = 2,
|
||||
}
|
||||
|
||||
/// The likelihood of safety car getting deployed with hazard on track.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1416,13 +1554,14 @@ pub enum PitStopExperience {
|
||||
Deserialize,
|
||||
)]
|
||||
#[br(little, repr(u8))]
|
||||
pub enum SafetyCar {
|
||||
pub enum SafetyCarIntensity {
|
||||
Off = 0,
|
||||
Reduced = 1,
|
||||
Standard = 2,
|
||||
Increased = 3,
|
||||
}
|
||||
|
||||
/// The way the game handles safety car periods. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1444,6 +1583,7 @@ pub enum SafetyCarExperience {
|
||||
Unknown = 255,
|
||||
}
|
||||
|
||||
/// The way the game handles formation laps. Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1465,6 +1605,8 @@ pub enum FormationLapExperience {
|
||||
Unknown = 255,
|
||||
}
|
||||
|
||||
/// The likelihood of the game using a red flag after a serious incident.
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1480,13 +1622,16 @@ pub enum FormationLapExperience {
|
||||
Deserialize,
|
||||
)]
|
||||
#[br(little, repr(u8))]
|
||||
pub enum RedFlags {
|
||||
pub enum RedFlagIntensity {
|
||||
Off = 0,
|
||||
Reduced = 1,
|
||||
Standard = 2,
|
||||
Increased = 3,
|
||||
}
|
||||
|
||||
/// Type of safety car being deployed in a
|
||||
/// [`SafetyCar` event](variant@crate::packets::event::EventDetails::SafetyCar).
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
@ -1509,6 +1654,8 @@ pub enum SafetyCarType {
|
||||
FormationLap = 3,
|
||||
}
|
||||
|
||||
/// Type of [`SafetyCar` event](variant@crate::packets::event::EventDetails::SafetyCar).
|
||||
/// Represents a [`u8`].
|
||||
#[non_exhaustive]
|
||||
#[derive(
|
||||
BinRead,
|
||||
|
@ -11,16 +11,16 @@ pub const MCLAREN: u8 = 8;
|
||||
pub const ALFA_ROMEO: u8 = 9;
|
||||
pub const SAUBER: u8 = 9;
|
||||
pub const F1_GENERIC: u8 = 41;
|
||||
pub const MERCEDES_2020: u8 = 85;
|
||||
pub const FERRARI_2020: u8 = 86;
|
||||
pub const RED_BULL_2020: u8 = 87;
|
||||
pub const WILLIAMS_2020: u8 = 88;
|
||||
pub const RACING_POINT_2020: u8 = 89;
|
||||
pub const RENAULT_2020: u8 = 90;
|
||||
pub const ALPHA_TAURI_2020: u8 = 91;
|
||||
pub const HAAS_2020: u8 = 92;
|
||||
pub const MCLAREN_2020: u8 = 93;
|
||||
pub const ALFA_ROMEO_2020: u8 = 94;
|
||||
pub const BRAKING_POINT_MERCEDES_2020: u8 = 85;
|
||||
pub const BRAKING_POINT_FERRARI_2020: u8 = 86;
|
||||
pub const BRAKING_POINT_RED_BULL_2020: u8 = 87;
|
||||
pub const BRAKING_POINT_WILLIAMS_2020: u8 = 88;
|
||||
pub const BRAKING_POINT_RACING_POINT_2020: u8 = 89;
|
||||
pub const BRAKING_POINT_RENAULT_2020: u8 = 90;
|
||||
pub const BRAKING_POINT_ALPHA_TAURI_2020: u8 = 91;
|
||||
pub const BRAKING_POINT_HAAS_2020: u8 = 92;
|
||||
pub const BRAKING_POINT_MCLAREN_2020: u8 = 93;
|
||||
pub const BRAKING_POINT_ALFA_ROMEO_2020: u8 = 94;
|
||||
pub const ASTON_MARTIN_DB11_V12: u8 = 95;
|
||||
pub const ASTON_MARTIN_VANTAGE: u8 = 96;
|
||||
pub const ASTON_MARTIN_SAFETY_CAR: u8 = 97;
|
||||
@ -54,18 +54,18 @@ pub const F1_22_DAMS_2022: u8 = 125;
|
||||
pub const F1_22_CAMPOS_2022: u8 = 126;
|
||||
pub const F1_22_VAN_AMERSFOORT_2022: u8 = 127;
|
||||
pub const F1_22_TRIDENT_2022: u8 = 128;
|
||||
pub const MERCEDES_2022: u8 = 118;
|
||||
pub const FERRARI_2022: u8 = 119;
|
||||
pub const RED_BULL_2022: u8 = 120;
|
||||
pub const WILLIAMS_2022: u8 = 121;
|
||||
pub const ASTON_MARTIN_2022: u8 = 122;
|
||||
pub const ALPINE_2022: u8 = 123;
|
||||
pub const ALPHA_TAURI_2022: u8 = 124;
|
||||
pub const HAAS_2022: u8 = 125;
|
||||
pub const MCLAREN_2022: u8 = 126;
|
||||
pub const ALFA_ROMEO_2022: u8 = 127;
|
||||
pub const KONNERSPORT_2022: u8 = 128;
|
||||
pub const KONNERSPORT: u8 = 129;
|
||||
pub const BRAKING_POINT_MERCEDES_2022: u8 = 118;
|
||||
pub const BRAKING_POINT_FERRARI_2022: u8 = 119;
|
||||
pub const BRAKING_POINT_RED_BULL_2022: u8 = 120;
|
||||
pub const BRAKING_POINT_WILLIAMS_2022: u8 = 121;
|
||||
pub const BRAKING_POINT_ASTON_MARTIN_2022: u8 = 122;
|
||||
pub const BRAKING_POINT_ALPINE_2022: u8 = 123;
|
||||
pub const BRAKING_POINT_ALPHA_TAURI_2022: u8 = 124;
|
||||
pub const BRAKING_POINT_HAAS_2022: u8 = 125;
|
||||
pub const BRAKING_POINT_MCLAREN_2022: u8 = 126;
|
||||
pub const BRAKING_POINT_ALFA_ROMEO_2022: u8 = 127;
|
||||
pub const BRAKING_POINT_KONNERSPORT_2022: u8 = 128;
|
||||
pub const BRAKING_POINT_KONNERSPORT_2023: u8 = 129;
|
||||
pub const F1_23_PREMA_2022: u8 = 130;
|
||||
pub const F1_23_VIRTUOSI_2022: u8 = 131;
|
||||
pub const F1_23_CARLIN_2022: u8 = 132;
|
||||
|
390
src/lib.rs
390
src/lib.rs
@ -1,11 +1,195 @@
|
||||
//! Convert raw binary data from F1 24, F1 23, and F1 22 UDP telemetry into organised structs.
|
||||
//! ## Getting started
|
||||
//!
|
||||
//! Add `f1_game_packet_parser` to your project's `Cargo.toml` file:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! f1_game_packet_parser = "1.0.0"
|
||||
//! ```
|
||||
//!
|
||||
//! ### Basic UDP client
|
||||
//!
|
||||
//! This crate doesn't provide a UDP client out of the box.
|
||||
//! Here's how to write one that will parse and pretty-print incoming packets:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use f1_game_packet_parser::parse;
|
||||
//! use std::error::Error;
|
||||
//! use std::net::UdpSocket;
|
||||
//!
|
||||
//! fn main() -> Result<(), Box<dyn Error>> {
|
||||
//! // This IP and port should be set in the game's options by default.
|
||||
//! let socket = UdpSocket::bind("127.0.0.1:20777")?;
|
||||
//! let mut buf = [0u8; 1460];
|
||||
//!
|
||||
//! loop {
|
||||
//! // Receive raw packet data from the game.
|
||||
//! // The buf array should be large enough for all types of packets.
|
||||
//! let (amt, _) = socket.recv_from(&mut buf)?;
|
||||
//!
|
||||
//! // Convert received bytes to an F1Packet struct and print it.
|
||||
//! let packet = parse(&buf[..amt])?;
|
||||
//! println!("{:#?}", packet);
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Determining a packet's type and extracting its payload
|
||||
//!
|
||||
//! An [`F1Packet`] consists of a universal [`header`](field@F1Packet::header)
|
||||
//! and an [`Option`] field for a payload of every single packet type.
|
||||
//! Only one of these can be set to [`Some`] for a given [`F1Packet`] instance.
|
||||
//!
|
||||
//! Therefore, you can use the following if-else-if chain to
|
||||
//! differentiate between all packet types and extract their payloads.
|
||||
//! Of course, you can remove the branches you don't need.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use f1_game_packet_parser::parse;
|
||||
//!
|
||||
//! let placeholder_data = include_bytes!("placeholder.bin");
|
||||
//! let packet = parse(placeholder_data)?;
|
||||
//!
|
||||
//! if let Some(motion) = &packet.motion {
|
||||
//! // Do whatever with motion.
|
||||
//! } else if let Some(session) = &packet.session {
|
||||
//! // Do whatever with session.
|
||||
//! } else if let Some(laps) = &packet.laps {
|
||||
//! // Do whatever with laps.
|
||||
//! } else if let Some(event) = &packet.event {
|
||||
//! // Do whatever with event.
|
||||
//! } else if let Some(participants) = &packet.participants {
|
||||
//! // Do whatever with participants.
|
||||
//! } else if let Some(car_setups) = &packet.car_setups {
|
||||
//! // Do whatever with car_setups.
|
||||
//! } else if let Some(car_telemetry) = &packet.car_telemetry {
|
||||
//! // Do whatever with car_telemetry.
|
||||
//! } else if let Some(car_status) = &packet.car_status {
|
||||
//! // Do whatever with car_status.
|
||||
//! } else if let Some(final_classification) = &packet.final_classification {
|
||||
//! // Do whatever with final_classification.
|
||||
//! } else if let Some(lobby) = &packet.lobby {
|
||||
//! // Do whatever with lobby.
|
||||
//! } else if let Some(car_damage) = &packet.car_damage {
|
||||
//! // Do whatever with car_damage.
|
||||
//! } else if let Some(session_history) = &packet.session_history {
|
||||
//! // Do whatever with session_history.
|
||||
//! } else if let Some(tyre_sets) = &packet.tyre_sets {
|
||||
//! // Available from the 2023 format onwards.
|
||||
//! // Do whatever with tyre_sets.
|
||||
//! } else if let Some(motion_ex) = &packet.motion_ex {
|
||||
//! // Available from the 2023 format onwards.
|
||||
//! // Do whatever with motion_ex.
|
||||
//! } else if let Some(time_trial) = &packet.time_trial {
|
||||
//! // Available from the 2024 format onwards.
|
||||
//! // Do whatever with time_trial.
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Working with [event packets](F1PacketEvent)
|
||||
//!
|
||||
//! [`F1PacketEvent`] is unique among other kinds of packets.
|
||||
//! Its payload consists of a 4-letter code that determines
|
||||
//! the type of the event, followed by optional details
|
||||
//! about this event.
|
||||
//!
|
||||
//! These extra details are represented by the [`EventDetails`](enum@packets::event::EventDetails)
|
||||
//! enum (even if a certain event doesn't come with additional data).
|
||||
//! You can import the enum and use a matcher to determine an event's type
|
||||
//! and extract its payload (if available) like so:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use f1_game_packet_parser::packets::event::EventDetails;
|
||||
//! use f1_game_packet_parser::parse;
|
||||
//!
|
||||
//! let placeholder_data = include_bytes!("placeholder.bin");
|
||||
//! let packet = parse(placeholder_data)?;
|
||||
//!
|
||||
//! if let Some(event) = &packet.event {
|
||||
//! match event.details {
|
||||
//! /// Event with no extra details.
|
||||
//! EventDetails::LightsOut => {
|
||||
//! println!("It's lights out, and away we go!");
|
||||
//! }
|
||||
//! /// You can skip the details if you don't need them.
|
||||
//! EventDetails::Flashback { .. } => {
|
||||
//! println!("Flashback has been triggered!");
|
||||
//! }
|
||||
//! /// Extracting details from an event.
|
||||
//! EventDetails::RaceWinner { vehicle_index } => {
|
||||
//! println!(
|
||||
//! "Driver at index {} is the winner!",
|
||||
//! vehicle_index
|
||||
//! );
|
||||
//! }
|
||||
//! _ => (),
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Working with bitmaps
|
||||
//!
|
||||
//! There are 3 fields that use a [`bitflags`]-powered bitmap struct:
|
||||
//!
|
||||
//! - [`EventDetails::Buttons::button_status`](field@packets::event::EventDetails::Buttons::button_status)
|
||||
//! - [`CarTelemetryData::rev_lights_bit_value`](field@packets::car_telemetry::CarTelemetryData::rev_lights_bit_value)
|
||||
//! - [`LapHistoryData::lap_valid_bit_flags`](field@packets::session_history::LapHistoryData::lap_valid_bit_flags)
|
||||
//!
|
||||
//! Each bitmap struct is publicly available via the [`constants`] module
|
||||
//! and comes with a handful of constants representing specific bit values,
|
||||
//! as well as methods and operator overloads for common bit operations.
|
||||
//!
|
||||
//! Here's an example that checks if a given binary file is
|
||||
//! a [car telemetry packet](F1PacketCarTelemetry).
|
||||
//! If so, it will grab player car's telemetry data
|
||||
//! and determine whether the revs are high, medium or low
|
||||
//! based on the specific bit values being set.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use f1_game_packet_parser::constants::RevLights;
|
||||
//! use f1_game_packet_parser::parse;
|
||||
//!
|
||||
//! let placeholder_data = include_bytes!("placeholder.bin");
|
||||
//! let packet = parse(placeholder_data)?;
|
||||
//! let player_car_index = packet.header.player_car_index;
|
||||
//!
|
||||
//! if let Some(car_telemetry) = &packet.car_telemetry {
|
||||
//! let player = car_telemetry.data[player_car_index];
|
||||
//! let is_high_rev =
|
||||
//! player.rev_lights_bit_value.contains(RevLights::RIGHT_1);
|
||||
//! let is_medium_rev =
|
||||
//! player.rev_lights_bit_value.contains(RevLights::MIDDLE_1);
|
||||
//!
|
||||
//! let revs_desc = if is_high_rev {
|
||||
//! "High"
|
||||
//! } else if is_medium_rev {
|
||||
//! "Medium"
|
||||
//! } else {
|
||||
//! "Low"
|
||||
//! };
|
||||
//!
|
||||
//! println!("{} revs", revs_desc);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Original documentation links
|
||||
//!
|
||||
//! - [F1 24](https://forums.ea.com/discussions/f1-24-general-discussion-en/f1-24-udp-specification/8369125)
|
||||
//! - [F1 23](https://forums.ea.com/discussions/f1-23-en/f1-23-udp-specification/8390745)
|
||||
//! - [F1 22](https://forums.ea.com/discussions/f1-games-franchise-discussion-en/f1-22-udp-specification/8418392)
|
||||
|
||||
/// Contains appendix constants and enums for various packet-specific struct field values.
|
||||
pub mod constants;
|
||||
/// Contains structures for each kind of packet payload
|
||||
/// and submodules for packet-specific structs.
|
||||
pub mod packets;
|
||||
|
||||
use crate::constants::PacketId;
|
||||
use crate::constants::{PacketId, MAX_NUM_CARS};
|
||||
use crate::packets::{
|
||||
u8_to_usize, F1PacketCarDamage, F1PacketCarSetups, F1PacketCarStatus,
|
||||
F1PacketCarTelemetry, F1PacketEvent, F1PacketFinalClassification, F1PacketLap,
|
||||
F1PacketLobbyInfo, F1PacketMotion, F1PacketMotionEx, F1PacketParticipants,
|
||||
F1PacketCarTelemetry, F1PacketEvent, F1PacketFinalClassification, F1PacketLaps,
|
||||
F1PacketLobby, F1PacketMotion, F1PacketMotionEx, F1PacketParticipants,
|
||||
F1PacketSession, F1PacketSessionHistory, F1PacketTimeTrial, F1PacketTyreSets,
|
||||
};
|
||||
|
||||
@ -13,29 +197,136 @@ use binrw::io::Cursor;
|
||||
use binrw::{BinRead, BinReaderExt, BinResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Attempts to extract game packet data from a slice of bytes.
|
||||
/// See [`binrw::Error`] for possible error variants.
|
||||
/// Attempts to extract F1 game packet data from a shared slice of bytes.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// - [`binrw::Error::AssertFail`] when a certain field's value
|
||||
/// is outside the expected range. This generally applies to
|
||||
/// `_index` fields, percentage values, and fields that
|
||||
/// have the aforementioned range specified in their documentation
|
||||
/// - [`binrw::Error::BadMagic`] when [`F1PacketEvent`] has a code
|
||||
/// that doesn't match any [known event type](packets::event::EventDetails)
|
||||
/// - [`binrw::Error::Custom`] when the parser encounters an invalid bool value
|
||||
/// in a read byte (i.e. neither 0, nor 1)
|
||||
/// - [`binrw::Error::EnumErrors`] when there's no matching value for an enum field
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ### Basic UDP client
|
||||
///
|
||||
/// ```no_run
|
||||
/// use f1_game_packet_parser::parse;
|
||||
/// use std::error::Error;
|
||||
/// use std::net::UdpSocket;
|
||||
///
|
||||
/// fn main() -> Result<(), Box<dyn Error>> {
|
||||
/// // This IP and port should be set in the game's options by default.
|
||||
/// let socket = UdpSocket::bind("127.0.0.1:20777")?;
|
||||
/// let mut buf = [0u8; 1460];
|
||||
///
|
||||
/// loop {
|
||||
/// // Receive raw packet data from the game.
|
||||
/// // The buf array should be large enough for all types of packets.
|
||||
/// let (amt, _) = socket.recv_from(&mut buf)?;
|
||||
///
|
||||
/// // Convert received bytes to an F1Packet struct and print it.
|
||||
/// let packet = parse(&buf[..amt])?;
|
||||
/// println!("{:#?}", packet);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Invalid/unsupported packet format
|
||||
///
|
||||
/// ```
|
||||
/// let invalid_format = 2137u16.to_le_bytes();
|
||||
/// let parse_result = f1_game_packet_parser::parse(&invalid_format);
|
||||
///
|
||||
/// assert!(parse_result.is_err());
|
||||
/// assert_eq!(
|
||||
/// parse_result.unwrap_err().to_string(),
|
||||
/// "Invalid or unsupported packet format: 2137 at 0x0"
|
||||
/// );
|
||||
/// ```
|
||||
pub fn parse(data: &[u8]) -> BinResult<F1Packet> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
let header: F1PacketHeader = cursor.read_le()?;
|
||||
let body: F1PacketBody =
|
||||
cursor.read_le_args((header.packet_format, header.packet_id))?;
|
||||
let packet: F1Packet = cursor.read_le()?;
|
||||
|
||||
Ok(F1Packet { header, body })
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
/// Structured representation of raw F1 game packet data that's
|
||||
/// returned as a successful result of the [`parse`] function.
|
||||
///
|
||||
/// Each [`Option`] field acts as a slot for a payload of a packet of a certain type.
|
||||
/// Only one of these fields can be [`Some`] for a given `F1Packet` instance.
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little)]
|
||||
pub struct F1Packet {
|
||||
/// Universal packet header.
|
||||
pub header: F1PacketHeader,
|
||||
pub body: F1PacketBody,
|
||||
/// Physics data for all cars in the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::Motion), args(header.packet_format))]
|
||||
pub motion: Option<F1PacketMotion>,
|
||||
/// Data about the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::Session), args(header.packet_format))]
|
||||
pub session: Option<F1PacketSession>,
|
||||
/// Lap data for all cars on track.
|
||||
#[br(if(header.packet_id == PacketId::Laps), args(header.packet_format))]
|
||||
pub laps: Option<F1PacketLaps>,
|
||||
/// Details of events that happen during the course of the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::Event), args(header.packet_format))]
|
||||
pub event: Option<F1PacketEvent>,
|
||||
/// List of participants in the session.
|
||||
#[br(if(header.packet_id == PacketId::Participants), args(header.packet_format))]
|
||||
pub participants: Option<F1PacketParticipants>,
|
||||
/// Setup data for all cars in the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::CarSetups), args(header.packet_format))]
|
||||
pub car_setups: Option<F1PacketCarSetups>,
|
||||
/// Telemetry data for all cars in the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::CarTelemetry), args(header.packet_format))]
|
||||
pub car_telemetry: Option<F1PacketCarTelemetry>,
|
||||
/// Status data for all cars in the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::CarStatus), args(header.packet_format))]
|
||||
pub car_status: Option<F1PacketCarStatus>,
|
||||
/// Final classification confirmation at the end of the session.
|
||||
#[br(
|
||||
if(header.packet_id == PacketId::FinalClassification),
|
||||
args(header.packet_format)
|
||||
)]
|
||||
pub final_classification: Option<F1PacketFinalClassification>,
|
||||
/// Details of players in a multiplayer lobby.
|
||||
#[br(if(header.packet_id == PacketId::LobbyInfo), args(header.packet_format))]
|
||||
pub lobby: Option<F1PacketLobby>,
|
||||
/// Car damage parameters for all cars in the ongoing session.
|
||||
#[br(if(header.packet_id == PacketId::CarDamage), args(header.packet_format))]
|
||||
pub car_damage: Option<F1PacketCarDamage>,
|
||||
/// Session history data for a specific car.
|
||||
#[br(if(header.packet_id == PacketId::SessionHistory), args(header.packet_format))]
|
||||
pub session_history: Option<F1PacketSessionHistory>,
|
||||
/// In-depth details about tyre sets assigned to a vehicle during the session.
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(header.packet_id == PacketId::TyreSets), args(header.packet_format))]
|
||||
pub tyre_sets: Option<F1PacketTyreSets>,
|
||||
/// Extended player car only motion data.
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(header.packet_id == PacketId::MotionEx), args(header.packet_format))]
|
||||
pub motion_ex: Option<F1PacketMotionEx>,
|
||||
/// Extra information that's only relevant to time trial game mode.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(header.packet_id == PacketId::TimeTrial), args(header.packet_format))]
|
||||
pub time_trial: Option<F1PacketTimeTrial>,
|
||||
}
|
||||
|
||||
/// F1 game packet's header.
|
||||
/// F1 game packet's header. It contains metadata about the game,
|
||||
/// the ongoing session, the frame this packet was sent on, and player car indexes.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little)]
|
||||
pub struct F1PacketHeader {
|
||||
/// Value of the "UDP Format" option in the game's telemetry settings.
|
||||
/// This crate currently supports formats in range `(2022..=2024)`.
|
||||
#[br(
|
||||
assert(
|
||||
(2022..=2024).contains(&packet_format),
|
||||
@ -44,7 +335,7 @@ pub struct F1PacketHeader {
|
||||
)
|
||||
)]
|
||||
pub packet_format: u16,
|
||||
/// Game year (last two digits)
|
||||
/// Game year (last two digits).
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(packet_format >= 2023))]
|
||||
pub game_year: u8,
|
||||
@ -54,79 +345,32 @@ pub struct F1PacketHeader {
|
||||
pub game_minor_version: u8,
|
||||
/// Version of this packet type, all start from 1.
|
||||
pub packet_version: u8,
|
||||
/// Identifier for the packet type.
|
||||
/// Unique identifier for the packet type.
|
||||
pub packet_id: PacketId,
|
||||
/// Unique identifier for the session.
|
||||
pub session_uid: u64,
|
||||
/// Session timestamp.
|
||||
pub session_time: f32,
|
||||
/// Identifier for the frame the data was retrieved on.
|
||||
/// Goes back after a flashback is triggered.
|
||||
pub frame_identifier: u32,
|
||||
/// Overall identifier for the frame the data was retrieved on
|
||||
/// (doesn't go back after flashbacks).
|
||||
/// (i.e. it doesn't go back after flashbacks).
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(packet_format >= 2023))]
|
||||
pub overall_frame_identifier: u32,
|
||||
/// Index of player's car in the array.
|
||||
#[br(map(u8_to_usize))]
|
||||
/// Index of player 1's car.
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
player_car_index < MAX_NUM_CARS,
|
||||
"Header has an invalid player 1 car index: {}",
|
||||
player_car_index
|
||||
)
|
||||
)]
|
||||
pub player_car_index: usize,
|
||||
/// Index of secondary player's car in the array in splitscreen mode.
|
||||
/// Index of player 2's car in splitscreen mode.
|
||||
/// Set to 255 if not in splitscreen mode.
|
||||
#[br(map(u8_to_usize))]
|
||||
pub secondary_player_car_index: usize,
|
||||
}
|
||||
|
||||
/// F1 game packet's body.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16, packet_id: PacketId))]
|
||||
pub struct F1PacketBody {
|
||||
/// Physics data for all cars in the session.
|
||||
#[br(if(packet_id == PacketId::Motion), args(packet_format))]
|
||||
pub motion: Option<F1PacketMotion>,
|
||||
/// Data about the ongoing session.
|
||||
#[br(if(packet_id == PacketId::Session), args(packet_format))]
|
||||
pub session: Option<F1PacketSession>,
|
||||
/// Lap data for all cars on track.
|
||||
#[br(if(packet_id == PacketId::LapData), args(packet_format))]
|
||||
pub lap: Option<F1PacketLap>,
|
||||
/// Details of events that happen during the course of a session.
|
||||
#[br(if(packet_id == PacketId::Event), args(packet_format))]
|
||||
pub event: Option<F1PacketEvent>,
|
||||
/// List of participants in the race.
|
||||
#[br(if(packet_id == PacketId::Participants), args(packet_format))]
|
||||
pub participants: Option<F1PacketParticipants>,
|
||||
/// Car setups for all cars in the session.
|
||||
#[br(if(packet_id == PacketId::CarSetups), args(packet_format))]
|
||||
pub car_setups: Option<F1PacketCarSetups>,
|
||||
/// Telemetry data for all cars in the session.
|
||||
#[br(if(packet_id == PacketId::CarTelemetry), args(packet_format))]
|
||||
pub car_telemetry: Option<F1PacketCarTelemetry>,
|
||||
/// Status data for all cars in the session.
|
||||
#[br(if(packet_id == PacketId::CarStatus), args(packet_format))]
|
||||
pub car_status: Option<F1PacketCarStatus>,
|
||||
/// Final classification confirmation at the end of a race.
|
||||
#[br(if(packet_id == PacketId::FinalClassification), args(packet_format))]
|
||||
pub final_classification: Option<F1PacketFinalClassification>,
|
||||
/// Details of players in a multiplayer lobby.
|
||||
#[br(if(packet_id == PacketId::LobbyInfo), args(packet_format))]
|
||||
pub lobby_info: Option<F1PacketLobbyInfo>,
|
||||
/// Car damage parameters for all cars in the session.
|
||||
#[br(if(packet_id == PacketId::CarDamage), args(packet_format))]
|
||||
pub car_damage: Option<F1PacketCarDamage>,
|
||||
/// Session history data for a specific car.
|
||||
#[br(if(packet_id == PacketId::SessionHistory), args(packet_format))]
|
||||
pub session_history: Option<F1PacketSessionHistory>,
|
||||
/// In-depth details about tyre sets assigned to a vehicle during the session.
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(packet_id == PacketId::TyreSets), args(packet_format))]
|
||||
pub tyre_sets: Option<F1PacketTyreSets>,
|
||||
/// Extended player car only motion data.
|
||||
/// Available from the 2023 format onwards.
|
||||
#[br(if(packet_id == PacketId::MotionEx), args(packet_format))]
|
||||
pub motion_ex: Option<F1PacketMotionEx>,
|
||||
/// Extra information that's only relevant to time trial game mode.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_id == PacketId::TimeTrial), args(packet_format))]
|
||||
pub time_trial: Option<F1PacketTimeTrial>,
|
||||
}
|
||||
|
@ -10,8 +10,22 @@ pub struct CarSetupData {
|
||||
/// Rear wing aero.
|
||||
pub rear_wing: u8,
|
||||
/// Differential adjustment on throttle (percentage).
|
||||
#[br(
|
||||
assert(
|
||||
on_throttle <= 100,
|
||||
"Car setup entry has an invalid on-throttle percentage value: {}",
|
||||
on_throttle
|
||||
)
|
||||
)]
|
||||
pub on_throttle: u8,
|
||||
/// Differential adjustment off throttle (percentage).
|
||||
#[br(
|
||||
assert(
|
||||
off_throttle <= 100,
|
||||
"Car setup entry has an invalid off-throttle percentage value: {}",
|
||||
off_throttle
|
||||
)
|
||||
)]
|
||||
pub off_throttle: u8,
|
||||
/// Front camber angle (suspension geometry).
|
||||
pub front_camber: f32,
|
||||
@ -34,6 +48,13 @@ pub struct CarSetupData {
|
||||
/// Rear ride height.
|
||||
pub rear_suspension_height: u8,
|
||||
/// Brake pressure (percentage).
|
||||
#[br(
|
||||
assert(
|
||||
brake_pressure <= 100,
|
||||
"Car setup entry has an invalid brake pressure percentage value: {}",
|
||||
brake_pressure
|
||||
)
|
||||
)]
|
||||
pub brake_pressure: u8,
|
||||
/// Brake bias.
|
||||
pub brake_bias: u8,
|
||||
|
@ -93,5 +93,5 @@ pub struct CarTelemetryData {
|
||||
/// Driving surface of each tyre.
|
||||
/// See [`wheel_index`](mod@crate::constants::wheel_index)
|
||||
/// for wheel order.
|
||||
pub surface_type: Option<[Surface; 4]>,
|
||||
pub surface_type: [Surface; 4],
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::{u8_to_bool, u8_to_usize};
|
||||
use crate::constants::{
|
||||
ButtonStatus, InfringementType, PenaltyType, SafetyCarEventType, SafetyCarType,
|
||||
MAX_NUM_CARS,
|
||||
};
|
||||
|
||||
use binrw::BinRead;
|
||||
@ -20,7 +21,14 @@ pub enum EventDetails {
|
||||
#[br(magic = b"FTLP")]
|
||||
FastestLap {
|
||||
/// Index of the car that's achieved the fastest lap.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Fastest lap event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
/// Lap time in seconds.
|
||||
lap_time: f32,
|
||||
@ -29,7 +37,14 @@ pub enum EventDetails {
|
||||
#[br(magic = b"RTMT")]
|
||||
Retirement {
|
||||
/// Index of the retiring car.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Retirement event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
},
|
||||
/// Sent when race control enable DRS.
|
||||
@ -42,17 +57,31 @@ pub enum EventDetails {
|
||||
#[br(magic = b"TMPT")]
|
||||
TeamMateInPits {
|
||||
/// Index of teammate's car.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Teammate in pits event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
},
|
||||
/// Sent when the chequered flag has been waved.
|
||||
#[br(magic = b"CHQF")]
|
||||
ChequeredFlag,
|
||||
/// Sent when the race winner is announced.
|
||||
/// Sent when the race winner has been announced.
|
||||
#[br(magic = b"RCWN")]
|
||||
RaceWinner {
|
||||
/// Index of race winner's car.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Race winner event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
},
|
||||
/// Sent when a penalty has been issued.
|
||||
@ -63,10 +92,24 @@ pub enum EventDetails {
|
||||
/// Infringement type.
|
||||
infringement_type: InfringementType,
|
||||
/// Index of the car the penalty is applied to.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Penalty event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
/// Index of the other car involved.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
other_vehicle_index < MAX_NUM_CARS,
|
||||
"Penalty event has an invalid other vehicle index: {}",
|
||||
other_vehicle_index
|
||||
)
|
||||
)]
|
||||
other_vehicle_index: usize,
|
||||
/// Time gained/spent doing the action in seconds.
|
||||
time: u8,
|
||||
@ -79,7 +122,14 @@ pub enum EventDetails {
|
||||
#[br(magic = b"SPTP")]
|
||||
SpeedTrap {
|
||||
/// Index of the car that's triggered the speed trap.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Speed trap event has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
vehicle_index: usize,
|
||||
/// Top speed achieved in kilometres per hour.
|
||||
speed: f32,
|
||||
@ -90,7 +140,14 @@ pub enum EventDetails {
|
||||
#[br(try_map(u8_to_bool))]
|
||||
is_driver_fastest_in_session: bool,
|
||||
/// Index of the vehicle that's the fastest in the session.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Speed trap event has an invalid fastest vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
fastest_vehicle_index: usize,
|
||||
/// Fastest speed in the session in kilometres per hour.
|
||||
fastest_speed_in_session: f32,
|
||||
@ -147,7 +204,12 @@ pub enum EventDetails {
|
||||
/// Sent when safety car gets deployed.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(magic = b"SCAR")]
|
||||
SafetyCar { safety_car_type: SafetyCarType, event_type: SafetyCarEventType },
|
||||
SafetyCar {
|
||||
/// Type of the safety car that's been deployed.
|
||||
safety_car_type: SafetyCarType,
|
||||
/// New safety car deployment status.
|
||||
event_type: SafetyCarEventType,
|
||||
},
|
||||
/// Sent when two vehicles collide.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(magic = b"COLL")]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::u8_to_bool;
|
||||
use crate::constants::{DriverStatus, PitStatus, ResultStatus};
|
||||
use crate::constants::{DriverStatus, PitStatus, ResultStatus, Sector};
|
||||
|
||||
use binrw::BinRead;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -7,15 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
/// Lap data for a car on track.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(
|
||||
little,
|
||||
import(packet_format: u16),
|
||||
assert(
|
||||
sector <= 2,
|
||||
"Lap data entry has an invalid sector number: {}",
|
||||
sector
|
||||
)
|
||||
)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct LapData {
|
||||
/// Last lap time in milliseconds.
|
||||
pub last_lap_time_ms: u32,
|
||||
@ -66,8 +58,7 @@ pub struct LapData {
|
||||
/// Number of pit stops taken in this race.
|
||||
pub num_pit_stops: u8,
|
||||
/// Zero-based number of the sector the driver is currently going through.
|
||||
/// S1 = 0, S2 = 1, S3 = 2.
|
||||
pub sector: u8,
|
||||
pub sector: Sector,
|
||||
/// Whether the current lap is invalid.
|
||||
#[br(try_map(u8_to_bool))]
|
||||
pub current_lap_invalid: bool,
|
||||
@ -104,7 +95,7 @@ pub struct LapData {
|
||||
#[br(if(packet_format >= 2024))]
|
||||
pub speed_trap_fastest_speed: f32,
|
||||
/// Number of the lap the fastest speed was achieved on
|
||||
/// (255 means "not set")
|
||||
/// (255 means "not set").
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
pub speed_trap_fastest_lap: u8,
|
@ -14,8 +14,7 @@ pub struct LobbyInfoData {
|
||||
#[br(try_map(u8_to_bool))]
|
||||
pub ai_controlled: bool,
|
||||
/// Team's ID.
|
||||
/// See [`team_id`](mod@crate::constants::team_id)
|
||||
/// for possible values.
|
||||
/// See [`team_id`](mod@crate::constants::team_id) for possible values.
|
||||
pub team_id: u8,
|
||||
/// Driver's nationality.
|
||||
pub nationality: Nationality,
|
||||
|
@ -4,7 +4,7 @@ pub mod car_status;
|
||||
pub mod car_telemetry;
|
||||
pub mod event;
|
||||
pub mod final_classification;
|
||||
pub mod lap;
|
||||
pub mod laps;
|
||||
pub mod lobby;
|
||||
pub mod motion;
|
||||
pub mod participants;
|
||||
@ -17,17 +17,17 @@ use crate::constants::{
|
||||
BrakingAssist, CarDamage, CarDamageRate, Collisions, CornerCuttingStringency,
|
||||
DynamicRacingLine, DynamicRacingLineType, FlashbackLimit, ForecastAccuracy,
|
||||
FormationLapExperience, Formula, GameMode, GearboxAssist, LowFuelMode, MfdPanelIndex,
|
||||
PitStopExperience, RaceStarts, RecoveryMode, RedFlags, RuleSet, SafetyCar,
|
||||
SafetyCarExperience, SafetyCarStatus, SessionLength, SpeedUnit,
|
||||
SurfaceType, TemperatureUnit, TrackId, TyreTemperature, Weather, MAX_NUM_CARS,
|
||||
PitStopExperience, RaceStarts, RecoveryMode, RedFlagIntensity, RuleSet,
|
||||
SafetyCarExperience, SafetyCarIntensity, SafetyCarStatus, SessionLength, SpeedUnit,
|
||||
SurfaceSimType, TemperatureUnit, TrackId, TyreTemperature, Weather, MAX_NUM_CARS,
|
||||
};
|
||||
use crate::packets::car_damage::CarDamageData;
|
||||
use crate::packets::car_setups::CarSetupData;
|
||||
use crate::packets::car_status::CarStatusData;
|
||||
use crate::packets::car_telemetry::CarTelemetryData;
|
||||
use crate::packets::event::EventDataDetails;
|
||||
use crate::packets::event::EventDetails;
|
||||
use crate::packets::final_classification::FinalClassificationData;
|
||||
use crate::packets::lap::LapData;
|
||||
use crate::packets::laps::LapData;
|
||||
use crate::packets::lobby::LobbyInfoData;
|
||||
use crate::packets::motion::CarMotionData;
|
||||
use crate::packets::participants::ParticipantsData;
|
||||
@ -45,24 +45,26 @@ use crate::packets::tyre_sets::{TyreSetData, NUM_TYRE_SETS};
|
||||
|
||||
use binrw::BinRead;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
/// Physics data for all the cars being driven.
|
||||
/// The motion packet gives physics data for all the cars being driven.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketMotion {
|
||||
/// Motion data for all cars on track.
|
||||
/// Motion data for all cars on track. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub car_motion_data: Vec<CarMotionData>,
|
||||
/// Extra player car only motion data.
|
||||
pub data: Vec<CarMotionData>,
|
||||
/// Extra player-car-only motion data.
|
||||
/// Available only in the 2022 format.
|
||||
#[br(if(packet_format == 2022))]
|
||||
pub motion_ex_data: Option<F1PacketMotionEx>,
|
||||
pub motion_ex: Option<F1PacketMotionEx>,
|
||||
}
|
||||
|
||||
/// Data about the ongoing session.
|
||||
/// The session packet includes details about the current session in progress.
|
||||
/// ## Example
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
@ -248,7 +250,7 @@ pub struct F1PacketSession {
|
||||
/// Surface simulation type.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
pub surface_type: Option<SurfaceType>,
|
||||
pub surface_sim_type: Option<SurfaceSimType>,
|
||||
/// Low fuel driving difficulty.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
@ -305,7 +307,7 @@ pub struct F1PacketSession {
|
||||
/// Safety car intensity.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
pub safety_car: Option<SafetyCar>,
|
||||
pub safety_car_intensity: Option<SafetyCarIntensity>,
|
||||
/// Safety car experience.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
@ -321,7 +323,7 @@ pub struct F1PacketSession {
|
||||
/// Red flag intensity.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024))]
|
||||
pub red_flags: Option<RedFlags>,
|
||||
pub red_flag_intensity: Option<RedFlagIntensity>,
|
||||
/// Whether this single player game affects the license level.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(if(packet_format >= 2024), try_map(u8_to_bool))]
|
||||
@ -343,6 +345,8 @@ pub struct F1PacketSession {
|
||||
/// List of sessions that shows this weekend's structure.
|
||||
/// Should have a size equal to
|
||||
/// [`num_sessions_in_weekend`](field@crate::packets::F1PacketSession::num_sessions_in_weekend).
|
||||
/// See [`session_type`](mod@crate::constants::session_type)
|
||||
/// for possible values.
|
||||
/// Available from the 2024 format onwards.
|
||||
#[br(
|
||||
if(packet_format >= 2024),
|
||||
@ -364,10 +368,10 @@ pub struct F1PacketSession {
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketLap {
|
||||
/// Lap data for all cars on track.
|
||||
pub struct F1PacketLaps {
|
||||
/// Lap data for all cars on track. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub lap_data: Vec<LapData>,
|
||||
pub data: Vec<LapData>,
|
||||
/// Index of personal best car in time trial mode (255 if invalid).
|
||||
#[br(map(u8_to_usize))]
|
||||
pub time_trial_pb_car_index: usize,
|
||||
@ -386,10 +390,10 @@ pub struct F1PacketEvent {
|
||||
try_map(|bytes: [u8; 4]| String::from_utf8(bytes.to_vec())),
|
||||
restore_position
|
||||
)]
|
||||
pub event_string_code: String,
|
||||
pub code: String,
|
||||
/// Extra data for this event.
|
||||
#[br(args(packet_format))]
|
||||
pub event_details: EventDataDetails,
|
||||
pub details: EventDetails,
|
||||
}
|
||||
|
||||
/// Data of participants in the session, mostly relevant for multiplayer.
|
||||
@ -412,7 +416,7 @@ pub struct F1PacketParticipants {
|
||||
/// Should have a size equal to
|
||||
/// [`num_active_cars`](field@crate::packets::F1PacketParticipants::num_active_cars).
|
||||
#[br(count(num_active_cars), args{ inner: (packet_format,) })]
|
||||
pub participants_data: Vec<ParticipantsData>,
|
||||
pub data: Vec<ParticipantsData>,
|
||||
}
|
||||
|
||||
/// Car setups for all cars in the race.
|
||||
@ -422,9 +426,9 @@ pub struct F1PacketParticipants {
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketCarSetups {
|
||||
/// Setup data for all cars on track.
|
||||
/// Setup data for all cars on track. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub car_setups_data: Vec<CarSetupData>,
|
||||
pub data: Vec<CarSetupData>,
|
||||
/// Value of front wing after next pit stop - player only.
|
||||
/// Available from the 2024 format onwards
|
||||
#[br(if(packet_format >= 2024))]
|
||||
@ -436,12 +440,12 @@ pub struct F1PacketCarSetups {
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketCarTelemetry {
|
||||
/// Telemetry data for all cars on track.
|
||||
/// Telemetry data for all cars on track. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub car_telemetry_data: Vec<CarTelemetryData>,
|
||||
/// Index of currently open MFD panel.
|
||||
pub data: Vec<CarTelemetryData>,
|
||||
/// Index of currently open MFD panel for player 1.
|
||||
pub mfd_panel_index: MfdPanelIndex,
|
||||
/// See `mfd_panel_index`.
|
||||
/// Index of currently open MFD panel for player 2.
|
||||
pub mfd_panel_index_secondary_player: MfdPanelIndex,
|
||||
/// Suggested gear (0 if no gear suggested).
|
||||
#[br(
|
||||
@ -459,9 +463,9 @@ pub struct F1PacketCarTelemetry {
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketCarStatus {
|
||||
/// Status data for all cars.
|
||||
/// Car status data for all cars. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub car_status_data: Vec<CarStatusData>,
|
||||
pub data: Vec<CarStatusData>,
|
||||
}
|
||||
|
||||
/// Final classification confirmation at the end of a race.
|
||||
@ -483,14 +487,14 @@ pub struct F1PacketFinalClassification {
|
||||
/// Should have a size equal to
|
||||
/// [`num_cars`](field@crate::packets::F1PacketFinalClassification::num_cars).
|
||||
#[br(count(num_cars), args{ inner: (packet_format,) })]
|
||||
pub final_classification_data: Vec<FinalClassificationData>,
|
||||
pub data: Vec<FinalClassificationData>,
|
||||
}
|
||||
|
||||
/// Packet detailing all the players that are currently in a multiplayer lobby.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketLobbyInfo {
|
||||
pub struct F1PacketLobby {
|
||||
/// Number of players in the lobby.
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
@ -503,9 +507,9 @@ pub struct F1PacketLobbyInfo {
|
||||
pub num_players: usize,
|
||||
/// Lobby info data for all players.
|
||||
/// Should have a size equal to
|
||||
/// [`num_players`](field@crate::packets::F1PacketLobbyInfo::num_players).
|
||||
/// [`num_players`](field@crate::packets::F1PacketLobby::num_players).
|
||||
#[br(count(num_players), args{ inner: (packet_format,) })]
|
||||
pub lobby_info_data: Vec<LobbyInfoData>,
|
||||
pub data: Vec<LobbyInfoData>,
|
||||
}
|
||||
|
||||
/// Car damage parameters for all cars in the session.
|
||||
@ -513,12 +517,12 @@ pub struct F1PacketLobbyInfo {
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
pub struct F1PacketCarDamage {
|
||||
/// Car damage data.
|
||||
/// Car damage data. Should have a size of 22.
|
||||
#[br(count(MAX_NUM_CARS), args{ inner: (packet_format,) })]
|
||||
pub car_damage_data: Vec<CarDamageData>,
|
||||
pub data: Vec<CarDamageData>,
|
||||
}
|
||||
|
||||
/// Packet detailing lap and tyre data history for a given driver in the session
|
||||
/// Packet detailing lap and tyre data history for a given driver in the session.
|
||||
#[non_exhaustive]
|
||||
#[derive(BinRead, PartialEq, PartialOrd, Clone, Debug, Serialize, Deserialize)]
|
||||
#[br(little, import(packet_format: u16))]
|
||||
@ -591,7 +595,7 @@ pub struct F1PacketTyreSets {
|
||||
pub vehicle_index: usize,
|
||||
/// 13 dry + 7 wet tyre sets.
|
||||
#[br(count(NUM_TYRE_SETS), args{ inner: (packet_format,) })]
|
||||
pub tyre_set_data: Vec<TyreSetData>,
|
||||
pub data: Vec<TyreSetData>,
|
||||
/// Index of fitted tyre set.
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
@ -718,19 +722,21 @@ pub struct F1PacketTimeTrial {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) struct InvalidBoolValue(u8);
|
||||
pub(crate) struct MapBoolError(u8);
|
||||
|
||||
impl fmt::Display for InvalidBoolValue {
|
||||
impl fmt::Display for MapBoolError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Invalid bool value: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn u8_to_bool(value: u8) -> Result<bool, InvalidBoolValue> {
|
||||
impl Error for MapBoolError {}
|
||||
|
||||
pub(crate) fn u8_to_bool(value: u8) -> Result<bool, MapBoolError> {
|
||||
match value {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => Err(InvalidBoolValue(value)),
|
||||
_ => Err(MapBoolError(value)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,32 +43,38 @@ pub struct CarMotionData {
|
||||
}
|
||||
|
||||
impl CarMotionData {
|
||||
/// Returns [`self.world_forward_dir_x`] divided by `32767.0f32`.
|
||||
/// Returns [`world_forward_dir_x`](field@CarMotionData::world_forward_dir_x)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_forward_dir_x_as_f32(&self) -> f32 {
|
||||
self.world_forward_dir_x as f32 / 32767.0
|
||||
}
|
||||
|
||||
/// Returns [`self.world_forward_dir_y`] divided by `32767.0f32`.
|
||||
/// Returns [`world_forward_dir_y`](field@CarMotionData::world_forward_dir_y)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_forward_dir_y_as_f32(&self) -> f32 {
|
||||
self.world_forward_dir_y as f32 / 32767.0
|
||||
}
|
||||
|
||||
/// Returns [`self.world_forward_dir_z`] divided by `32767.0f32`.
|
||||
/// Returns [`world_forward_dir_z`](field@CarMotionData::world_forward_dir_z)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_forward_dir_z_as_f32(&self) -> f32 {
|
||||
self.world_forward_dir_z as f32 / 32767.0
|
||||
}
|
||||
|
||||
/// Returns [`self.world_right_dir_x`] divided by `32767.0f32`.
|
||||
/// Returns [`world_right_dir_x`](field@CarMotionData::world_right_dir_x)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_right_dir_x_as_f32(&self) -> f32 {
|
||||
self.world_right_dir_x as f32 / 32767.0
|
||||
}
|
||||
|
||||
/// Returns [`self.world_right_dir_y`] divided by `32767.0f32`.
|
||||
/// Returns [`world_right_dir_y`](field@CarMotionData::world_right_dir_y)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_right_dir_y_as_f32(&self) -> f32 {
|
||||
self.world_right_dir_y as f32 / 32767.0
|
||||
}
|
||||
|
||||
/// Returns [`self.world_right_dir_z`] divided by `32767.0f32`.
|
||||
/// Returns [`world_right_dir_z`](field@CarMotionData::world_right_dir_z)
|
||||
/// divided by `32767.0f32`.
|
||||
pub fn world_right_dir_z_as_f32(&self) -> f32 {
|
||||
self.world_right_dir_z as f32 / 32767.0
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ pub struct ParticipantsData {
|
||||
/// Unique ID for network players.
|
||||
pub network_id: u8,
|
||||
/// Team's ID.
|
||||
/// See [`team_id`](mod@crate::constants::team_id)
|
||||
/// for possible values.
|
||||
/// See [`team_id`](mod@crate::constants::team_id) for possible values.
|
||||
pub team_id: u8,
|
||||
/// Whether my team is being used.
|
||||
#[br(try_map(u8_to_bool))]
|
||||
|
@ -77,7 +77,7 @@ pub(super) fn get_forecast_samples_padding(
|
||||
|
||||
#[inline(always)]
|
||||
fn get_max_num_samples(packet_format: u16) -> usize {
|
||||
if packet_format == 2024 {
|
||||
if packet_format >= 2024 {
|
||||
64
|
||||
} else {
|
||||
56
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::{u8_to_bool, u8_to_usize};
|
||||
use crate::constants::{GearboxAssist, TractionControl};
|
||||
use crate::constants::{GearboxAssist, TractionControl, MAX_NUM_CARS};
|
||||
|
||||
use binrw::BinRead;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -11,9 +11,17 @@ use serde::{Deserialize, Serialize};
|
||||
#[br(little, import(_packet_format: u16))]
|
||||
pub struct TimeTrialDataSet {
|
||||
/// Index of the car this data set relates to.
|
||||
#[br(map(u8_to_usize))]
|
||||
#[br(
|
||||
map(u8_to_usize),
|
||||
assert(
|
||||
vehicle_index < MAX_NUM_CARS,
|
||||
"Time trial data set has an invalid vehicle index: {}",
|
||||
vehicle_index
|
||||
)
|
||||
)]
|
||||
pub vehicle_index: usize,
|
||||
/// Team's ID.
|
||||
/// See [`team_id`](mod@crate::constants::team_id) for possible values.
|
||||
pub team_id: u8,
|
||||
/// Lap time in milliseconds.
|
||||
pub lap_time_ms: u32,
|
||||
|
Loading…
x
Reference in New Issue
Block a user