Horizon_Network_Common/
player.rs

1//! Player-related types shared across the Horizon ecosystem.
2//!
3//! These types represent players and their state as they move between
4//! different Horizon instances managed by Atlas.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8use chrono::{DateTime, Utc};
9
10use crate::spatial::WorldCoordinate;
11use crate::server::ServerId;
12
13/// Unique identifier for a player.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct PlayerId(pub Uuid);
16
17impl PlayerId {
18    /// Creates a new random player ID.
19    pub fn new() -> Self {
20        Self(Uuid::new_v4())
21    }
22
23    /// Creates a player ID from a string.
24    pub fn from_str(s: &str) -> Result<Self, uuid::Error> {
25        Uuid::parse_str(s).map(Self)
26    }
27}
28
29impl Default for PlayerId {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl std::fmt::Display for PlayerId {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "{}", self.0)
38    }
39}
40
41/// Authentication status of a player.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
43#[serde(rename_all = "snake_case")]
44pub enum AuthenticationStatus {
45    /// Player is not authenticated
46    #[default]
47    Unauthenticated,
48    /// Player is in the process of authenticating
49    Authenticating,
50    /// Player is successfully authenticated
51    Authenticated,
52    /// Player authentication failed
53    AuthenticationFailed,
54}
55
56/// Connection state of a player.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
58#[serde(rename_all = "snake_case")]
59pub enum ConnectionState {
60    /// Player is connecting
61    #[default]
62    Connecting,
63    /// Player is connected and active
64    Connected,
65    /// Player is being transferred to another server
66    Transferring,
67    /// Player is disconnecting
68    Disconnecting,
69    /// Player is disconnected
70    Disconnected,
71}
72
73/// Basic player information tracked by Atlas.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PlayerInfo {
76    /// Unique player identifier
77    pub id: PlayerId,
78    /// Player display name
79    pub name: String,
80    /// Current authentication status
81    pub auth_status: AuthenticationStatus,
82    /// Current connection state
83    pub connection_state: ConnectionState,
84    /// Server the player is currently connected to
85    pub current_server: Option<ServerId>,
86    /// Last known position in world coordinates
87    pub last_position: WorldCoordinate,
88    /// Timestamp of last position update
89    pub last_updated: DateTime<Utc>,
90}
91
92impl PlayerInfo {
93    /// Creates new player info.
94    pub fn new(id: PlayerId, name: String) -> Self {
95        Self {
96            id,
97            name,
98            auth_status: AuthenticationStatus::Unauthenticated,
99            connection_state: ConnectionState::Connecting,
100            current_server: None,
101            last_position: WorldCoordinate::zero(),
102            last_updated: Utc::now(),
103        }
104    }
105
106    /// Updates the player's position.
107    pub fn update_position(&mut self, position: WorldCoordinate) {
108        self.last_position = position;
109        self.last_updated = Utc::now();
110    }
111}
112
113/// Player state that can be serialized for transfer between servers.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct PlayerState {
116    /// Player information
117    pub info: PlayerInfo,
118    /// Velocity vector
119    pub velocity: WorldCoordinate,
120    /// Player health (0.0 to 1.0)
121    pub health: f32,
122    /// Custom state data (game-specific)
123    #[serde(default)]
124    pub custom_data: std::collections::HashMap<String, serde_json::Value>,
125    /// Inventory or other persistent data
126    #[serde(default)]
127    pub persistent_data: serde_json::Value,
128}
129
130impl PlayerState {
131    /// Creates a new player state from player info.
132    pub fn new(info: PlayerInfo) -> Self {
133        Self {
134            info,
135            velocity: WorldCoordinate::zero(),
136            health: 1.0,
137            custom_data: std::collections::HashMap::new(),
138            persistent_data: serde_json::Value::Null,
139        }
140    }
141
142    /// Serializes the player state to JSON.
143    pub fn to_json(&self) -> Result<String, serde_json::Error> {
144        serde_json::to_string(self)
145    }
146
147    /// Deserializes player state from JSON.
148    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
149        serde_json::from_str(json)
150    }
151}
152
153/// Movement data for player position prediction.
154#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
155pub struct MovementData {
156    /// Current velocity
157    pub velocity: WorldCoordinate,
158    /// Acceleration
159    pub acceleration: WorldCoordinate,
160    /// Timestamp of this movement data
161    pub timestamp_ms: u64,
162}
163
164impl MovementData {
165    /// Predicts position after the given time delta.
166    pub fn predict_position(&self, current_pos: WorldCoordinate, delta_ms: u64) -> WorldCoordinate {
167        let t = delta_ms as f64 / 1000.0;
168        // Simple kinematic equation: p = p0 + v*t + 0.5*a*t^2
169        WorldCoordinate::new(
170            current_pos.x + self.velocity.x * t + 0.5 * self.acceleration.x * t * t,
171            current_pos.y + self.velocity.y * t + 0.5 * self.acceleration.y * t * t,
172            current_pos.z + self.velocity.z * t + 0.5 * self.acceleration.z * t * t,
173        )
174    }
175}
176
177/// Reasons for player disconnection.
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum DisconnectReason {
181    /// Player initiated disconnection
182    ClientDisconnect,
183    /// Connection timed out
184    Timeout,
185    /// Server is shutting down
186    ServerShutdown,
187    /// Player was kicked
188    Kicked { reason: String },
189    /// Player is being transferred
190    Transfer { target_server: ServerId },
191    /// An error occurred
192    Error { message: String },
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_player_state_serialization() {
201        let info = PlayerInfo::new(PlayerId::new(), "TestPlayer".to_string());
202        let state = PlayerState::new(info);
203        let json = state.to_json().unwrap();
204        let restored = PlayerState::from_json(&json).unwrap();
205        assert_eq!(restored.info.name, "TestPlayer");
206    }
207
208    #[test]
209    fn test_movement_prediction() {
210        let movement = MovementData {
211            velocity: WorldCoordinate::new(10.0, 0.0, 0.0),
212            acceleration: WorldCoordinate::zero(),
213            timestamp_ms: 0,
214        };
215        let pos = WorldCoordinate::zero();
216        let predicted = movement.predict_position(pos, 1000); // 1 second
217        assert!((predicted.x - 10.0).abs() < 0.001);
218    }
219}