1pub const PROTOCOL_VERSION: u32 = 3;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
12pub struct NetworkId(pub u64);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct LocalId(pub u64);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22pub struct ClientId(pub u64);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
36pub struct ComponentKind(pub u16);
37
38pub const INPUT_COMMAND_KIND: ComponentKind = ComponentKind(128);
41
42pub const MINING_BEAM_KIND: ComponentKind = ComponentKind(1024);
44
45pub const CARGO_HOLD_KIND: ComponentKind = ComponentKind(1025);
47
48pub const ASTEROID_KIND: ComponentKind = ComponentKind(1026);
50
51#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
53#[repr(C)]
54pub struct Transform {
55 pub x: f32,
57 pub y: f32,
59 pub z: f32,
61 pub rotation: f32,
63 pub entity_type: u16,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[repr(u8)]
70pub enum ShipClass {
71 Interceptor = 0,
72 Dreadnought = 1,
73 Hauler = 2,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
78pub struct WeaponId(pub u8);
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
82pub struct SectorId(pub u64);
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86#[repr(u8)]
87pub enum OreType {
88 RawOre = 0,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93#[repr(u8)]
94pub enum ProjectileType {
95 PulseLaser = 0,
96 SeekerMissile = 1,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[repr(u8)]
102pub enum AIState {
103 Patrol = 0,
104 Aggro = 1,
105 Combat = 2,
106 Return = 3,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
111pub enum RespawnLocation {
112 NearestSafeZone,
114 Station(u64),
116 Coordinate(f32, f32),
118}
119
120#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
122pub enum PlayerInputKind {
123 Move { x: f32, y: f32 },
125 ToggleMining { target: NetworkId },
127 FirePrimary,
129}
130
131pub const MAX_ACTIONS: usize = 128;
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct InputCommand {
138 pub tick: u64,
140 pub actions: Vec<PlayerInputKind>,
142}
143
144impl InputCommand {
145 #[must_use]
147 pub fn clamped(mut self) -> Self {
148 for action in &mut self.actions {
149 if let PlayerInputKind::Move { x, y } = action {
150 *x = x.clamp(-1.0, 1.0);
151 *y = y.clamp(-1.0, 1.0);
152 }
153 }
154 self
155 }
156
157 pub fn validate(&self) -> Result<(), &'static str> {
162 if self.actions.len() > MAX_ACTIONS {
163 return Err("Too many actions in InputCommand");
164 }
165 Ok(())
166 }
167}
168
169#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
171pub struct MiningBeam {
172 pub active: bool,
173 pub target: Option<NetworkId>,
174}
175
176#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
178pub struct CargoHold {
179 pub ore_count: u16,
180 pub capacity: u16,
181}
182
183#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
185pub struct Asteroid {
186 pub ore_remaining: u16,
187 pub total_capacity: u16,
188}
189
190#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
196pub struct ShipStats {
197 pub hp: u16,
198 pub max_hp: u16,
199 pub shield: u16,
200 pub max_shield: u16,
201 pub energy: u16,
202 pub max_energy: u16,
203 pub shield_regen_per_s: u16,
204 pub energy_regen_per_s: u16,
205}
206
207impl Default for ShipStats {
208 fn default() -> Self {
210 Self {
211 hp: 100,
212 max_hp: 100,
213 shield: 100,
214 max_shield: 100,
215 energy: 100,
216 max_energy: 100,
217 shield_regen_per_s: 0,
218 energy_regen_per_s: 0,
219 }
220 }
221}
222
223use std::sync::atomic::{AtomicU64, Ordering};
224use thiserror::Error;
225
226#[derive(Debug, Error, PartialEq, Eq)]
227pub enum AllocatorError {
228 #[error("NetworkId overflow (reached u64::MAX)")]
229 Overflow,
230 #[error("NetworkId allocator exhausted (reached limit)")]
231 Exhausted,
232}
233
234#[derive(Debug)]
239pub struct NetworkIdAllocator {
240 start_id: u64,
241 next: AtomicU64,
242}
243
244impl Default for NetworkIdAllocator {
245 fn default() -> Self {
246 Self::new(1)
247 }
248}
249
250impl NetworkIdAllocator {
251 #[must_use]
253 pub fn new(start_id: u64) -> Self {
254 Self {
255 start_id,
256 next: AtomicU64::new(start_id),
257 }
258 }
259
260 pub fn allocate(&self) -> Result<NetworkId, AllocatorError> {
265 let val = self
266 .next
267 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |curr| {
268 if curr == u64::MAX {
269 None
270 } else {
271 Some(curr + 1)
272 }
273 })
274 .map_err(|_| AllocatorError::Overflow)?;
275
276 if val == 0 {
277 return Err(AllocatorError::Exhausted);
278 }
279
280 Ok(NetworkId(val))
281 }
282
283 pub fn reset(&self) {
286 self.next.store(self.start_id, Ordering::Relaxed);
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_primitive_derives() {
296 let nid1 = NetworkId(42);
297 let nid2 = nid1;
298 assert_eq!(nid1, nid2);
299
300 let lid1 = LocalId(42);
301 let lid2 = LocalId(42);
302 assert_eq!(lid1, lid2);
303
304 let cid = ClientId(99);
305 assert_eq!(format!("{cid:?}"), "ClientId(99)");
306
307 let kind = ComponentKind(1);
308 assert_eq!(kind.0, 1);
309 }
310
311 #[test]
312 fn test_input_command_clamping() {
313 let cmd = InputCommand {
314 tick: 1,
315 actions: vec![PlayerInputKind::Move { x: 2.0, y: -5.0 }],
316 };
317 let clamped = cmd.clamped();
318 if let PlayerInputKind::Move { x, y } = clamped.actions[0] {
319 assert!((x - 1.0).abs() < f32::EPSILON);
320 assert!((y - -1.0).abs() < f32::EPSILON);
321 } else {
322 panic!("Expected Move action");
323 }
324
325 let valid = InputCommand {
326 tick: 1,
327 actions: vec![PlayerInputKind::Move { x: 0.5, y: -0.2 }],
328 };
329 let clamped = valid.clamped();
330 if let PlayerInputKind::Move { x, y } = clamped.actions[0] {
331 assert!((x - 0.5).abs() < f32::EPSILON);
332 assert!((y - -0.2).abs() < f32::EPSILON);
333 } else {
334 panic!("Expected Move action");
335 }
336 }
337
338 #[test]
339 fn test_ship_stats_non_zero_default() {
340 let stats = ShipStats::default();
341 assert!(stats.max_hp > 0);
342 assert!(stats.max_shield > 0);
343 assert!(stats.max_energy > 0);
344 assert_eq!(stats.hp, stats.max_hp);
345 }
346}