Skip to main content

runecast_protocol/
player.rs

1//! Player identity and context types.
2//!
3//! These types are the single source of truth for player identity information,
4//! shared across the handlers crate and backend.
5
6use crate::ServerMessage;
7
8/// Core player identity - the immutable parts that identify a player.
9///
10/// This is embedded in all player-related structs to avoid field duplication.
11/// Contains the fields that are constant for a player's session.
12pub trait PlayerIdentity {
13    /// Get player ID.
14    fn player_id(&self) -> i64;
15
16    /// Get the raw Discord username (the unique handle). For display, use `name()`.
17    fn username(&self) -> &str;
18
19    /// Get avatar URL.
20    fn avatar_url(&self) -> &str;
21
22    /// What to call this player in their current context, if the server
23    /// resolved it. `None` means "show `username`".
24    fn name(&self) -> Option<&crate::PlayerName> {
25        None
26    }
27}
28
29#[async_trait::async_trait]
30pub trait PlayerContext: Send + Sync {
31    async fn send_message(&self, msg: ServerMessage);
32
33    /// Get the player's identity.
34    fn identity(&self) -> &dyn PlayerIdentity;
35
36    /// Check if the player has admin privileges.
37    fn is_admin(&self) -> bool;
38
39    /// Get the current lobby ID if in a lobby.
40    fn lobby_id(&self) -> Option<&str>;
41
42    /// Get the current game ID if in a game.
43    fn game_id(&self) -> Option<&str>;
44
45    /// Check if the player is spectating.
46    fn is_spectating(&self) -> bool;
47
48    /// Check if the player is connected.
49    fn is_connected(&self) -> bool;
50
51    // =========================================================================
52    // Convenience methods with default implementations
53    // =========================================================================
54
55    /// Get player ID directly (convenience for `identity().player_id()`).
56    fn player_id(&self) -> i64 {
57        self.identity().player_id()
58    }
59
60    /// Check if the player is in a lobby.
61    fn in_lobby(&self) -> bool {
62        self.lobby_id().is_some()
63    }
64
65    /// Check if the player is in a game.
66    fn in_game(&self) -> bool {
67        self.game_id().is_some()
68    }
69
70    // /// setters
71    // fn set_player_id(&mut self, player_id: i64);
72    // fn set_username(&mut self, username: &str);
73    // fn set_avatar_url(&mut self, avatar_url: Option<String>);
74    // fn set_is_admin(&mut self, is_admin: bool);
75    // fn set_lobby_id(&mut self, lobby_id: &str);
76    // fn set_game_id(&mut self, game_id: &str);
77    // fn set_is_spectating(&mut self, is_spectating: bool);
78    // fn set_is_connected(&mut self, is_connected: bool);
79}
80
81#[cfg(test)]
82mod tests {
83    use super::PlayerIdentity;
84
85    struct Bare;
86    impl PlayerIdentity for Bare {
87        fn player_id(&self) -> i64 {
88            1
89        }
90        fn username(&self) -> &str {
91            "u"
92        }
93        fn avatar_url(&self) -> &str {
94            ""
95        }
96    }
97
98    #[test]
99    fn an_identity_that_resolves_no_name_reports_none() {
100        assert!(Bare.name().is_none());
101    }
102}