1pub const PROTOCOL_VERSION: u32 = 2;
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)]
30pub struct ComponentKind(pub u16);
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
34#[repr(C)]
35pub struct Transform {
36 pub x: f32,
38 pub y: f32,
40 pub z: f32,
42 pub rotation: f32,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[repr(u8)]
49pub enum ShipClass {
50 Interceptor = 0,
51 Dreadnought = 1,
52 Hauler = 2,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
57pub struct WeaponId(pub u8);
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
61pub struct SectorId(pub u64);
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65#[repr(u8)]
66pub enum OreType {
67 RawOre = 0,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72#[repr(u8)]
73pub enum ProjectileType {
74 PulseLaser = 0,
75 SeekerMissile = 1,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[repr(u8)]
81pub enum AIState {
82 Patrol = 0,
83 Aggro = 1,
84 Combat = 2,
85 Return = 3,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
90pub enum RespawnLocation {
91 NearestSafeZone,
93 Station(u64),
95 Coordinate(f32, f32),
97}
98
99#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
101pub struct InputCommand {
102 pub tick: u64,
104 pub move_x: f32,
106 pub move_y: f32,
108 pub actions: u32,
110}
111
112impl InputCommand {
113 #[must_use]
115 pub fn clamped(mut self) -> Self {
116 self.move_x = self.move_x.clamp(-1.0, 1.0);
117 self.move_y = self.move_y.clamp(-1.0, 1.0);
118 self
119 }
120}
121
122#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
128pub struct ShipStats {
129 pub hp: u16,
130 pub max_hp: u16,
131 pub shield: u16,
132 pub max_shield: u16,
133 pub energy: u16,
134 pub max_energy: u16,
135 pub shield_regen_per_s: u16,
136 pub energy_regen_per_s: u16,
137}
138
139impl Default for ShipStats {
140 fn default() -> Self {
142 Self {
143 hp: 100,
144 max_hp: 100,
145 shield: 100,
146 max_shield: 100,
147 energy: 100,
148 max_energy: 100,
149 shield_regen_per_s: 0,
150 energy_regen_per_s: 0,
151 }
152 }
153}
154
155use std::sync::atomic::{AtomicU64, Ordering};
156use thiserror::Error;
157
158#[derive(Debug, Error, PartialEq, Eq)]
159pub enum AllocatorError {
160 #[error("NetworkId overflow (reached u64::MAX)")]
161 Overflow,
162 #[error("NetworkId allocator exhausted (reached limit)")]
163 Exhausted,
164}
165
166#[derive(Debug)]
171pub struct NetworkIdAllocator {
172 start_id: u64,
173 next: AtomicU64,
174}
175
176impl Default for NetworkIdAllocator {
177 fn default() -> Self {
178 Self::new(1)
179 }
180}
181
182impl NetworkIdAllocator {
183 #[must_use]
185 pub fn new(start_id: u64) -> Self {
186 Self {
187 start_id,
188 next: AtomicU64::new(start_id),
189 }
190 }
191
192 pub fn allocate(&self) -> Result<NetworkId, AllocatorError> {
197 let val = self
198 .next
199 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |curr| {
200 if curr == u64::MAX {
201 None
202 } else {
203 Some(curr + 1)
204 }
205 })
206 .map_err(|_| AllocatorError::Overflow)?;
207
208 if val == 0 {
209 return Err(AllocatorError::Exhausted);
210 }
211
212 Ok(NetworkId(val))
213 }
214
215 pub fn reset(&self) {
218 self.next.store(self.start_id, Ordering::Relaxed);
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_primitive_derives() {
228 let nid1 = NetworkId(42);
229 let nid2 = nid1;
230 assert_eq!(nid1, nid2);
231
232 let lid1 = LocalId(42);
233 let lid2 = LocalId(42);
234 assert_eq!(lid1, lid2);
235
236 let cid = ClientId(99);
237 assert_eq!(format!("{cid:?}"), "ClientId(99)");
238
239 let kind = ComponentKind(1);
240 assert_eq!(kind.0, 1);
241 }
242
243 #[test]
244 fn test_input_command_clamping() {
245 let cmd = InputCommand {
246 tick: 1,
247 move_x: 2.0,
248 move_y: -5.0,
249 actions: 0,
250 };
251 let clamped = cmd.clamped();
252 assert!((clamped.move_x - 1.0).abs() < f32::EPSILON);
253 assert!((clamped.move_y - -1.0).abs() < f32::EPSILON);
254
255 let valid = InputCommand {
256 tick: 1,
257 move_x: 0.5,
258 move_y: -0.2,
259 actions: 0,
260 };
261 let clamped = valid.clamped();
262 assert!((clamped.move_x - 0.5).abs() < f32::EPSILON);
263 assert!((clamped.move_y - -0.2).abs() < f32::EPSILON);
264 }
265
266 #[test]
267 fn test_ship_stats_non_zero_default() {
268 let stats = ShipStats::default();
269 assert!(stats.max_hp > 0);
270 assert!(stats.max_shield > 0);
271 assert!(stats.max_energy > 0);
272 assert_eq!(stats.hp, stats.max_hp);
273 }
274}