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}