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