1use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8use chrono::{DateTime, Utc};
9
10use crate::spatial::WorldCoordinate;
11use crate::server::ServerId;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct PlayerId(pub Uuid);
16
17impl PlayerId {
18 pub fn new() -> Self {
20 Self(Uuid::new_v4())
21 }
22
23 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
43#[serde(rename_all = "snake_case")]
44pub enum AuthenticationStatus {
45 #[default]
47 Unauthenticated,
48 Authenticating,
50 Authenticated,
52 AuthenticationFailed,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
58#[serde(rename_all = "snake_case")]
59pub enum ConnectionState {
60 #[default]
62 Connecting,
63 Connected,
65 Transferring,
67 Disconnecting,
69 Disconnected,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PlayerInfo {
76 pub id: PlayerId,
78 pub name: String,
80 pub auth_status: AuthenticationStatus,
82 pub connection_state: ConnectionState,
84 pub current_server: Option<ServerId>,
86 pub last_position: WorldCoordinate,
88 pub last_updated: DateTime<Utc>,
90}
91
92impl PlayerInfo {
93 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 pub fn update_position(&mut self, position: WorldCoordinate) {
108 self.last_position = position;
109 self.last_updated = Utc::now();
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct PlayerState {
116 pub info: PlayerInfo,
118 pub velocity: WorldCoordinate,
120 pub health: f32,
122 #[serde(default)]
124 pub custom_data: std::collections::HashMap<String, serde_json::Value>,
125 #[serde(default)]
127 pub persistent_data: serde_json::Value,
128}
129
130impl PlayerState {
131 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 pub fn to_json(&self) -> Result<String, serde_json::Error> {
144 serde_json::to_string(self)
145 }
146
147 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
149 serde_json::from_str(json)
150 }
151}
152
153#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
155pub struct MovementData {
156 pub velocity: WorldCoordinate,
158 pub acceleration: WorldCoordinate,
160 pub timestamp_ms: u64,
162}
163
164impl MovementData {
165 pub fn predict_position(&self, current_pos: WorldCoordinate, delta_ms: u64) -> WorldCoordinate {
167 let t = delta_ms as f64 / 1000.0;
168 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#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum DisconnectReason {
181 ClientDisconnect,
183 Timeout,
185 ServerShutdown,
187 Kicked { reason: String },
189 Transfer { target_server: ServerId },
191 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); assert!((predicted.x - 10.0).abs() < 0.001);
218 }
219}