firefly_types/
stats.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use crate::encode::Encode;
use alloc::boxed::Box;
use serde::{Deserialize, Serialize};

/// Player-specific app stats, like playtime.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct Stats {
    /// How many minutes the app was played for every player count from 1 to 4+.
    ///
    /// Games with more than 4 players are recorded as 4 players.
    /// So, the last index (3) is "4+ players".
    ///
    /// The play time is calculated based on the number of the calls
    /// to the `update` callback. So, the number is an approximation
    /// and may drift a bit.
    ///
    /// Plays shorter than 1 minute are not recorded.
    pub minutes: [u32; 4],

    /// How many minutes the app was played the longest for every player count from 1 to 4+.
    ///
    /// Use this metric carefully. The runtime might or might not account for players
    /// pausing the app for days instead of exiting it. Which means, multiple play
    /// sessions may be counted as one.
    pub longest_play: [u32; 4],

    /// How many times the app was launched for every player count from 1 to 4+.
    pub launches: [u32; 4],

    /// The date when the app was installed.
    ///
    /// The date is a tuple of year, month, and day of month.
    pub installed_on: (u16, u8, u8),

    /// The date when the app was updated.
    ///
    /// The date is a tuple of year, month, and day of month.
    pub updated_on: (u16, u8, u8),

    /// The date when the app was launched last time.
    ///
    /// The date is a tuple of year, month, and day of month.
    pub launched_on: (u16, u8, u8),

    /// How much XP the player has earned in the game.
    ///
    /// Cannot be more than 1000.
    pub xp: u16,

    /// The progress of earning each badge.
    ///
    /// The len is equal to the number of badges that the app has.
    /// See [`crate::Badges`].
    pub badges: Box<[BadgeProgress]>,

    /// The high scores
    pub scores: Box<[BoardScores]>,
}

impl<'a> Encode<'a> for Stats {}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct BadgeProgress {
    /// If true, the earning of the badge hasn't been shown to the player yet.
    pub new: bool,

    /// How many points are already earned for the badge.
    pub done: u16,

    /// How many points needed to earn the badge.
    pub goal: u16,
}

impl BadgeProgress {
    /// If the badge has been earned by the player.
    #[must_use]
    #[inline]
    pub const fn earned(&self) -> bool {
        self.done >= self.goal
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct BoardScores {
    /// Top scores of the local player.
    pub me: Box<[i16; 8]>,

    /// Top scores of friends.
    pub friends: Box<[FriendScore; 8]>,
}

#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
pub struct FriendScore {
    pub index: u16,
    pub score: i16,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_roundtrip() {
        let given = Stats {
            minutes: [11, 12, 13, 14],
            longest_play: [21, 22, 23, 24],
            launches: [31, 32, 33, 34],
            installed_on: (2023, 12, 31),
            updated_on: (2024, 1, 17),
            launched_on: (2024, 2, 28),
            xp: 32,
            badges: Box::new([]),
            scores: Box::new([]),
        };
        let mut buf = vec![0; given.size()];
        let raw = given.encode(&mut buf).unwrap();
        let actual = Stats::decode(raw).unwrap();
        assert_eq!(given, actual);
    }
}