firefly_types/
stats.rs

1use crate::encode::Encode;
2use alloc::boxed::Box;
3use serde::{Deserialize, Serialize};
4
5/// Player-specific app stats, like playtime.
6#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
7pub struct Stats {
8    /// How many minutes the app was played for every player count from 1 to 4+.
9    ///
10    /// Games with more than 4 players are recorded as 4 players.
11    /// So, the last index (3) is "4+ players".
12    ///
13    /// The play time is calculated based on the number of the calls
14    /// to the `update` callback. So, the number is an approximation
15    /// and may drift a bit.
16    ///
17    /// Plays shorter than 1 minute are not recorded.
18    pub minutes: [u32; 4],
19
20    /// How many minutes the app was played the longest for every player count from 1 to 4+.
21    ///
22    /// Use this metric carefully. The runtime might or might not account for players
23    /// pausing the app for days instead of exiting it. Which means, multiple play
24    /// sessions may be counted as one.
25    pub longest_play: [u32; 4],
26
27    /// How many times the app was launched for every player count from 1 to 4+.
28    pub launches: [u32; 4],
29
30    /// The date when the app was installed.
31    ///
32    /// The date is a tuple of year, month, and day of month.
33    pub installed_on: (u16, u8, u8),
34
35    /// The date when the app was updated.
36    ///
37    /// The date is a tuple of year, month, and day of month.
38    pub updated_on: (u16, u8, u8),
39
40    /// The date when the app was launched last time.
41    ///
42    /// The date is a tuple of year, month, and day of month.
43    pub launched_on: (u16, u8, u8),
44
45    /// How much XP the player has earned in the game.
46    ///
47    /// Cannot be more than 1000.
48    pub xp: u16,
49
50    /// The progress of earning each badge.
51    ///
52    /// The len is equal to the number of badges that the app has.
53    /// See [`crate::Badges`].
54    pub badges: Box<[BadgeProgress]>,
55
56    /// The high scores
57    pub scores: Box<[BoardScores]>,
58}
59
60impl Encode<'_> for Stats {}
61
62#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
63pub struct BadgeProgress {
64    /// If true, the earning of the badge hasn't been shown to the player yet.
65    pub new: bool,
66
67    /// How many points are already earned for the badge.
68    pub done: u16,
69
70    /// How many points needed to earn the badge.
71    pub goal: u16,
72}
73
74impl BadgeProgress {
75    /// If the badge has been earned by the player.
76    #[must_use]
77    #[inline]
78    pub const fn earned(&self) -> bool {
79        self.done >= self.goal
80    }
81}
82
83#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
84pub struct BoardScores {
85    /// Top scores of the local player.
86    pub me: Box<[i16; 8]>,
87
88    /// Top scores of friends.
89    pub friends: Box<[FriendScore; 8]>,
90}
91
92#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
93pub struct FriendScore {
94    pub index: u16,
95    pub score: i16,
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_roundtrip() {
104        let given = Stats {
105            minutes: [11, 12, 13, 14],
106            longest_play: [21, 22, 23, 24],
107            launches: [31, 32, 33, 34],
108            installed_on: (2023, 12, 31),
109            updated_on: (2024, 1, 17),
110            launched_on: (2024, 2, 28),
111            xp: 32,
112            badges: Box::new([]),
113            scores: Box::new([]),
114        };
115        let mut buf = vec![0; given.size()];
116        let raw = given.encode_buf(&mut buf).unwrap();
117        let actual = Stats::decode(raw).unwrap();
118        assert_eq!(given, actual);
119    }
120}