use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use crate::spatial::WorldCoordinate;
use crate::server::ServerId;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct PlayerId(pub String);
impl PlayerId {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for PlayerId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for PlayerId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for PlayerId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AuthenticationStatus {
#[default]
Unauthenticated,
Authenticating,
Authenticated,
AuthenticationFailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionState {
#[default]
Connecting,
Connected,
Transferring,
Disconnecting,
Disconnected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub id: PlayerId,
pub name: String,
pub auth_status: AuthenticationStatus,
pub connection_state: ConnectionState,
pub current_server: Option<ServerId>,
pub last_position: WorldCoordinate,
pub last_updated: DateTime<Utc>,
}
impl PlayerInfo {
pub fn new(id: PlayerId, name: String) -> Self {
Self {
id,
name,
auth_status: AuthenticationStatus::Unauthenticated,
connection_state: ConnectionState::Connecting,
current_server: None,
last_position: WorldCoordinate::zero(),
last_updated: Utc::now(),
}
}
pub fn update_position(&mut self, position: WorldCoordinate) {
self.last_position = position;
self.last_updated = Utc::now();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerState {
pub info: PlayerInfo,
pub velocity: WorldCoordinate,
pub health: f32,
#[serde(default)]
pub custom_data: std::collections::HashMap<String, serde_json::Value>,
#[serde(default)]
pub persistent_data: serde_json::Value,
}
impl PlayerState {
pub fn new(info: PlayerInfo) -> Self {
Self {
info,
velocity: WorldCoordinate::zero(),
health: 1.0,
custom_data: std::collections::HashMap::new(),
persistent_data: serde_json::Value::Null,
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct MovementData {
pub velocity: WorldCoordinate,
pub acceleration: WorldCoordinate,
pub timestamp_ms: u64,
}
impl MovementData {
pub fn predict_position(&self, current_pos: WorldCoordinate, delta_ms: u64) -> WorldCoordinate {
let t = delta_ms as f64 / 1000.0;
WorldCoordinate::new(
current_pos.x + self.velocity.x * t + 0.5 * self.acceleration.x * t * t,
current_pos.y + self.velocity.y * t + 0.5 * self.acceleration.y * t * t,
current_pos.z + self.velocity.z * t + 0.5 * self.acceleration.z * t * t,
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DisconnectReason {
ClientDisconnect,
Timeout,
ServerShutdown,
Kicked { reason: String },
Transfer { target_server: ServerId },
Error { message: String },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_player_state_serialization() {
let info = PlayerInfo::new(PlayerId::new(), "TestPlayer".to_string());
let state = PlayerState::new(info);
let json = state.to_json().unwrap();
let restored = PlayerState::from_json(&json).unwrap();
assert_eq!(restored.info.name, "TestPlayer");
}
#[test]
fn test_movement_prediction() {
let movement = MovementData {
velocity: WorldCoordinate::new(10.0, 0.0, 0.0),
acceleration: WorldCoordinate::zero(),
timestamp_ms: 0,
};
let pos = WorldCoordinate::zero();
let predicted = movement.predict_position(pos, 1000); assert!((predicted.x - 10.0).abs() < 0.001);
}
}