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)]
36pub struct ComponentKind(pub u16);
37
38pub const INPUT_COMMAND_KIND: ComponentKind = ComponentKind(128);
41
42#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
44#[repr(C)]
45pub struct Transform {
46 pub x: f32,
48 pub y: f32,
50 pub z: f32,
52 pub rotation: f32,
54 pub entity_type: u16,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60#[repr(u8)]
61pub enum ShipClass {
62 Interceptor = 0,
63 Dreadnought = 1,
64 Hauler = 2,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69pub struct WeaponId(pub u8);
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
73pub struct SectorId(pub u64);
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[repr(u8)]
78pub enum OreType {
79 RawOre = 0,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84#[repr(u8)]
85pub enum ProjectileType {
86 PulseLaser = 0,
87 SeekerMissile = 1,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
92#[repr(u8)]
93pub enum AIState {
94 Patrol = 0,
95 Aggro = 1,
96 Combat = 2,
97 Return = 3,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
102pub enum RespawnLocation {
103 NearestSafeZone,
105 Station(u64),
107 Coordinate(f32, f32),
109}
110
111#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
113pub struct InputCommand {
114 pub tick: u64,
116 pub move_x: f32,
118 pub move_y: f32,
120 pub actions: u32,
122}
123
124impl InputCommand {
125 #[must_use]
127 pub fn clamped(mut self) -> Self {
128 self.move_x = self.move_x.clamp(-1.0, 1.0);
129 self.move_y = self.move_y.clamp(-1.0, 1.0);
130 self
131 }
132}
133
134#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
140pub struct ShipStats {
141 pub hp: u16,
142 pub max_hp: u16,
143 pub shield: u16,
144 pub max_shield: u16,
145 pub energy: u16,
146 pub max_energy: u16,
147 pub shield_regen_per_s: u16,
148 pub energy_regen_per_s: u16,
149}
150
151impl Default for ShipStats {
152 fn default() -> Self {
154 Self {
155 hp: 100,
156 max_hp: 100,
157 shield: 100,
158 max_shield: 100,
159 energy: 100,
160 max_energy: 100,
161 shield_regen_per_s: 0,
162 energy_regen_per_s: 0,
163 }
164 }
165}
166
167use std::sync::atomic::{AtomicU64, Ordering};
168use thiserror::Error;
169
170#[derive(Debug, Error, PartialEq, Eq)]
171pub enum AllocatorError {
172 #[error("NetworkId overflow (reached u64::MAX)")]
173 Overflow,
174 #[error("NetworkId allocator exhausted (reached limit)")]
175 Exhausted,
176}
177
178#[derive(Debug)]
183pub struct NetworkIdAllocator {
184 start_id: u64,
185 next: AtomicU64,
186}
187
188impl Default for NetworkIdAllocator {
189 fn default() -> Self {
190 Self::new(1)
191 }
192}
193
194impl NetworkIdAllocator {
195 #[must_use]
197 pub fn new(start_id: u64) -> Self {
198 Self {
199 start_id,
200 next: AtomicU64::new(start_id),
201 }
202 }
203
204 pub fn allocate(&self) -> Result<NetworkId, AllocatorError> {
209 let val = self
210 .next
211 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |curr| {
212 if curr == u64::MAX {
213 None
214 } else {
215 Some(curr + 1)
216 }
217 })
218 .map_err(|_| AllocatorError::Overflow)?;
219
220 if val == 0 {
221 return Err(AllocatorError::Exhausted);
222 }
223
224 Ok(NetworkId(val))
225 }
226
227 pub fn reset(&self) {
230 self.next.store(self.start_id, Ordering::Relaxed);
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_primitive_derives() {
240 let nid1 = NetworkId(42);
241 let nid2 = nid1;
242 assert_eq!(nid1, nid2);
243
244 let lid1 = LocalId(42);
245 let lid2 = LocalId(42);
246 assert_eq!(lid1, lid2);
247
248 let cid = ClientId(99);
249 assert_eq!(format!("{cid:?}"), "ClientId(99)");
250
251 let kind = ComponentKind(1);
252 assert_eq!(kind.0, 1);
253 }
254
255 #[test]
256 fn test_input_command_clamping() {
257 let cmd = InputCommand {
258 tick: 1,
259 move_x: 2.0,
260 move_y: -5.0,
261 actions: 0,
262 };
263 let clamped = cmd.clamped();
264 assert!((clamped.move_x - 1.0).abs() < f32::EPSILON);
265 assert!((clamped.move_y - -1.0).abs() < f32::EPSILON);
266
267 let valid = InputCommand {
268 tick: 1,
269 move_x: 0.5,
270 move_y: -0.2,
271 actions: 0,
272 };
273 let clamped = valid.clamped();
274 assert!((clamped.move_x - 0.5).abs() < f32::EPSILON);
275 assert!((clamped.move_y - -0.2).abs() < f32::EPSILON);
276 }
277
278 #[test]
279 fn test_ship_stats_non_zero_default() {
280 let stats = ShipStats::default();
281 assert!(stats.max_hp > 0);
282 assert!(stats.max_shield > 0);
283 assert!(stats.max_energy > 0);
284 assert_eq!(stats.hp, stats.max_hp);
285 }
286}