1use serde::{Deserialize, Serialize};
7use chrono::{DateTime, Utc};
8
9use crate::spatial::WorldCoordinate;
10use crate::server::ServerId;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
15pub struct PlayerId(pub String);
16
17impl PlayerId {
18 pub fn new() -> Self {
20 Self(uuid::Uuid::new_v4().to_string())
21 }
22
23 pub fn from_string(s: impl Into<String>) -> Self {
25 Self(s.into())
26 }
27
28 pub fn as_str(&self) -> &str {
30 &self.0
31 }
32}
33
34impl std::fmt::Display for PlayerId {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "{}", self.0)
37 }
38}
39
40impl From<String> for PlayerId {
41 fn from(s: String) -> Self {
42 Self(s)
43 }
44}
45
46impl From<&str> for PlayerId {
47 fn from(s: &str) -> Self {
48 Self(s.to_string())
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
54#[serde(rename_all = "snake_case")]
55pub enum AuthenticationStatus {
56 #[default]
58 Unauthenticated,
59 Authenticating,
61 Authenticated,
63 AuthenticationFailed,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
69#[serde(rename_all = "snake_case")]
70pub enum ConnectionState {
71 #[default]
73 Connecting,
74 Connected,
76 Transferring,
78 Disconnecting,
80 Disconnected,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PlayerInfo {
87 pub id: PlayerId,
89 pub name: String,
91 pub auth_status: AuthenticationStatus,
93 pub connection_state: ConnectionState,
95 pub current_server: Option<ServerId>,
97 pub last_position: WorldCoordinate,
99 pub last_updated: DateTime<Utc>,
101}
102
103impl PlayerInfo {
104 pub fn new(id: PlayerId, name: String) -> Self {
106 Self {
107 id,
108 name,
109 auth_status: AuthenticationStatus::Unauthenticated,
110 connection_state: ConnectionState::Connecting,
111 current_server: None,
112 last_position: WorldCoordinate::zero(),
113 last_updated: Utc::now(),
114 }
115 }
116
117 pub fn update_position(&mut self, position: WorldCoordinate) {
119 self.last_position = position;
120 self.last_updated = Utc::now();
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PlayerState {
127 pub info: PlayerInfo,
129 pub velocity: WorldCoordinate,
131 pub health: f32,
133 #[serde(default)]
135 pub custom_data: std::collections::HashMap<String, serde_json::Value>,
136 #[serde(default)]
138 pub persistent_data: serde_json::Value,
139}
140
141impl PlayerState {
142 pub fn new(info: PlayerInfo) -> Self {
144 Self {
145 info,
146 velocity: WorldCoordinate::zero(),
147 health: 1.0,
148 custom_data: std::collections::HashMap::new(),
149 persistent_data: serde_json::Value::Null,
150 }
151 }
152
153 pub fn to_json(&self) -> Result<String, serde_json::Error> {
155 serde_json::to_string(self)
156 }
157
158 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
160 serde_json::from_str(json)
161 }
162}
163
164#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
166pub struct MovementData {
167 pub velocity: WorldCoordinate,
169 pub acceleration: WorldCoordinate,
171 pub timestamp_ms: u64,
173}
174
175impl MovementData {
176 pub fn predict_position(&self, current_pos: WorldCoordinate, delta_ms: u64) -> WorldCoordinate {
178 let t = delta_ms as f64 / 1000.0;
179 WorldCoordinate::new(
181 current_pos.x + self.velocity.x * t + 0.5 * self.acceleration.x * t * t,
182 current_pos.y + self.velocity.y * t + 0.5 * self.acceleration.y * t * t,
183 current_pos.z + self.velocity.z * t + 0.5 * self.acceleration.z * t * t,
184 )
185 }
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case")]
191pub enum DisconnectReason {
192 ClientDisconnect,
194 Timeout,
196 ServerShutdown,
198 Kicked { reason: String },
200 Transfer { target_server: ServerId },
202 Error { message: String },
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_player_state_serialization() {
212 let info = PlayerInfo::new(PlayerId::new(), "TestPlayer".to_string());
213 let state = PlayerState::new(info);
214 let json = state.to_json().unwrap();
215 let restored = PlayerState::from_json(&json).unwrap();
216 assert_eq!(restored.info.name, "TestPlayer");
217 }
218
219 #[test]
220 fn test_movement_prediction() {
221 let movement = MovementData {
222 velocity: WorldCoordinate::new(10.0, 0.0, 0.0),
223 acceleration: WorldCoordinate::zero(),
224 timestamp_ms: 0,
225 };
226 let pos = WorldCoordinate::zero();
227 let predicted = movement.predict_position(pos, 1000); assert!((predicted.x - 10.0).abs() < 0.001);
229 }
230}