pub const PROTOCOL_VERSION: u32 = 2;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NetworkId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LocalId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ClientId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ComponentKind(pub u16);
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub struct Transform {
pub x: f32,
pub y: f32,
pub z: f32,
pub rotation: f32,
pub entity_type: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ShipClass {
Interceptor = 0,
Dreadnought = 1,
Hauler = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct WeaponId(pub u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct SectorId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum OreType {
RawOre = 0,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ProjectileType {
PulseLaser = 0,
SeekerMissile = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AIState {
Patrol = 0,
Aggro = 1,
Combat = 2,
Return = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum RespawnLocation {
NearestSafeZone,
Station(u64),
Coordinate(f32, f32),
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct InputCommand {
pub tick: u64,
pub move_x: f32,
pub move_y: f32,
pub actions: u32,
}
impl InputCommand {
#[must_use]
pub fn clamped(mut self) -> Self {
self.move_x = self.move_x.clamp(-1.0, 1.0);
self.move_y = self.move_y.clamp(-1.0, 1.0);
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ShipStats {
pub hp: u16,
pub max_hp: u16,
pub shield: u16,
pub max_shield: u16,
pub energy: u16,
pub max_energy: u16,
pub shield_regen_per_s: u16,
pub energy_regen_per_s: u16,
}
impl Default for ShipStats {
fn default() -> Self {
Self {
hp: 100,
max_hp: 100,
shield: 100,
max_shield: 100,
energy: 100,
max_energy: 100,
shield_regen_per_s: 0,
energy_regen_per_s: 0,
}
}
}
use std::sync::atomic::{AtomicU64, Ordering};
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum AllocatorError {
#[error("NetworkId overflow (reached u64::MAX)")]
Overflow,
#[error("NetworkId allocator exhausted (reached limit)")]
Exhausted,
}
#[derive(Debug)]
pub struct NetworkIdAllocator {
start_id: u64,
next: AtomicU64,
}
impl Default for NetworkIdAllocator {
fn default() -> Self {
Self::new(1)
}
}
impl NetworkIdAllocator {
#[must_use]
pub fn new(start_id: u64) -> Self {
Self {
start_id,
next: AtomicU64::new(start_id),
}
}
pub fn allocate(&self) -> Result<NetworkId, AllocatorError> {
let val = self
.next
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |curr| {
if curr == u64::MAX {
None
} else {
Some(curr + 1)
}
})
.map_err(|_| AllocatorError::Overflow)?;
if val == 0 {
return Err(AllocatorError::Exhausted);
}
Ok(NetworkId(val))
}
pub fn reset(&self) {
self.next.store(self.start_id, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primitive_derives() {
let nid1 = NetworkId(42);
let nid2 = nid1;
assert_eq!(nid1, nid2);
let lid1 = LocalId(42);
let lid2 = LocalId(42);
assert_eq!(lid1, lid2);
let cid = ClientId(99);
assert_eq!(format!("{cid:?}"), "ClientId(99)");
let kind = ComponentKind(1);
assert_eq!(kind.0, 1);
}
#[test]
fn test_input_command_clamping() {
let cmd = InputCommand {
tick: 1,
move_x: 2.0,
move_y: -5.0,
actions: 0,
};
let clamped = cmd.clamped();
assert!((clamped.move_x - 1.0).abs() < f32::EPSILON);
assert!((clamped.move_y - -1.0).abs() < f32::EPSILON);
let valid = InputCommand {
tick: 1,
move_x: 0.5,
move_y: -0.2,
actions: 0,
};
let clamped = valid.clamped();
assert!((clamped.move_x - 0.5).abs() < f32::EPSILON);
assert!((clamped.move_y - -0.2).abs() < f32::EPSILON);
}
#[test]
fn test_ship_stats_non_zero_default() {
let stats = ShipStats::default();
assert!(stats.max_hp > 0);
assert!(stats.max_shield > 0);
assert!(stats.max_energy > 0);
assert_eq!(stats.hp, stats.max_hp);
}
}