oort_api/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::f64::consts::TAU;
5
6#[doc(hidden)]
7pub mod panic;
8mod vec;
9
10#[allow(missing_docs)]
11#[derive(Copy, Clone)]
12pub enum SystemState {
13    Class,
14    Seed,
15    PositionX,
16    PositionY,
17    VelocityX,
18    VelocityY,
19    Heading,
20    AngularVelocity,
21
22    AccelerateX,
23    AccelerateY,
24    Torque,
25
26    Aim0,
27    Aim1,
28    Aim2,
29    Aim3,
30
31    Fire0,
32    Fire1,
33    Fire2,
34    Fire3,
35
36    Explode,
37
38    Radar0Heading,
39    Radar0Width,
40    Radar0MinDistance,
41    Radar0MaxDistance,
42    Radar0EcmMode,
43
44    Radar0ContactFound,
45    Radar0ContactClass,
46    Radar0ContactPositionX,
47    Radar0ContactPositionY,
48    Radar0ContactVelocityX,
49    Radar0ContactVelocityY,
50    Radar0ContactRssi,
51    Radar0ContactSnr,
52
53    Radar1Heading,
54    Radar1Width,
55    Radar1MinDistance,
56    Radar1MaxDistance,
57    Radar1EcmMode,
58
59    Radar1ContactFound,
60    Radar1ContactClass,
61    Radar1ContactPositionX,
62    Radar1ContactPositionY,
63    Radar1ContactVelocityX,
64    Radar1ContactVelocityY,
65    Radar1ContactRssi,
66    Radar1ContactSnr,
67
68    DebugTextPointer,
69    DebugTextLength,
70
71    MaxForwardAcceleration,
72    MaxLateralAcceleration,
73    MaxAngularAcceleration,
74
75    DebugLinesPointer,
76    DebugLinesLength,
77
78    CurrentTick,
79    MaxBackwardAcceleration,
80
81    ActivateAbility,
82
83    Radio0Channel, // TODO collapse into command word
84    Radio0Send,
85    Radio0Receive,
86    Radio0Data0,
87    Radio0Data1,
88    Radio0Data2,
89    Radio0Data3,
90
91    Radio1Channel,
92    Radio1Send,
93    Radio1Receive,
94    Radio1Data0,
95    Radio1Data1,
96    Radio1Data2,
97    Radio1Data3,
98
99    Radio2Channel,
100    Radio2Send,
101    Radio2Receive,
102    Radio2Data0,
103    Radio2Data1,
104    Radio2Data2,
105    Radio2Data3,
106
107    Radio3Channel,
108    Radio3Send,
109    Radio3Receive,
110    Radio3Data0,
111    Radio3Data1,
112    Radio3Data2,
113    Radio3Data3,
114
115    Radio4Channel,
116    Radio4Send,
117    Radio4Receive,
118    Radio4Data0,
119    Radio4Data1,
120    Radio4Data2,
121    Radio4Data3,
122
123    Radio5Channel,
124    Radio5Send,
125    Radio5Receive,
126    Radio5Data0,
127    Radio5Data1,
128    Radio5Data2,
129    Radio5Data3,
130
131    Radio6Channel,
132    Radio6Send,
133    Radio6Receive,
134    Radio6Data0,
135    Radio6Data1,
136    Radio6Data2,
137    Radio6Data3,
138
139    Radio7Channel,
140    Radio7Send,
141    Radio7Receive,
142    Radio7Data0,
143    Radio7Data1,
144    Radio7Data2,
145    Radio7Data3,
146
147    // TODO not part of interface
148    SelectedRadio,
149    SelectedRadar,
150
151    DrawnTextPointer,
152    DrawnTextLength,
153
154    Health,
155    Fuel,
156
157    ReloadTicks0,
158    ReloadTicks1,
159    ReloadTicks2,
160    ReloadTicks3,
161
162    Id,
163
164    Size,
165    MaxSize = 128,
166}
167
168#[allow(missing_docs)]
169pub const MAX_ENVIRONMENT_SIZE: usize = 1024;
170
171/// Identifiers for each class of ship.
172#[allow(missing_docs)]
173#[derive(Copy, Clone, PartialEq, Eq, Debug)]
174pub enum Class {
175    Fighter,
176    Frigate,
177    Cruiser,
178    Asteroid,
179    Target,
180    Missile,
181    Torpedo,
182    Unknown,
183}
184
185impl Class {
186    #[allow(missing_docs)]
187    pub fn from_f64(v: f64) -> Class {
188        match v as u32 {
189            0 => Class::Fighter,
190            1 => Class::Frigate,
191            2 => Class::Cruiser,
192            3 => Class::Asteroid,
193            4 => Class::Target,
194            5 => Class::Missile,
195            6 => Class::Torpedo,
196            _ => Class::Unknown,
197        }
198    }
199
200    /// Returns base stats for a class of ship
201    pub fn default_stats(&self) -> ClassStats {
202        match self {
203            Class::Fighter => ClassStats {
204                max_health: 100.0,
205                mass: 15000.0,
206                max_forward_acceleration: 60.0,
207                max_backward_acceleration: 30.0,
208                max_lateral_acceleration: 30.0,
209                max_angular_acceleration: TAU,
210            },
211            Class::Frigate => ClassStats {
212                max_health: 10000.0,
213                mass: 4e6,
214                max_forward_acceleration: 10.0,
215                max_backward_acceleration: 5.0,
216                max_lateral_acceleration: 5.0,
217                max_angular_acceleration: TAU / 8.0,
218            },
219            Class::Cruiser => ClassStats {
220                max_health: 20000.0,
221                mass: 9e6,
222                max_forward_acceleration: 5.0,
223                max_backward_acceleration: 2.5,
224                max_lateral_acceleration: 2.5,
225                max_angular_acceleration: TAU / 16.0,
226            },
227            Class::Asteroid => ClassStats {
228                max_health: 200.0,
229                mass: 20e6,
230                max_forward_acceleration: 0.0,
231                max_backward_acceleration: 0.0,
232                max_lateral_acceleration: 0.0,
233                max_angular_acceleration: 0.0,
234            },
235            Class::Target => ClassStats {
236                max_health: 1.0,
237                mass: 10.0,
238                max_forward_acceleration: 0.0,
239                max_backward_acceleration: 0.0,
240                max_lateral_acceleration: 0.0,
241                max_angular_acceleration: 0.0,
242            },
243            Class::Missile => ClassStats {
244                max_health: 20.0,
245                mass: 150.0,
246                max_forward_acceleration: 300.0,
247                max_backward_acceleration: 0.0,
248                max_lateral_acceleration: 100.0,
249                max_angular_acceleration: 4.0 * TAU,
250            },
251            Class::Torpedo => ClassStats {
252                max_health: 100.0,
253                mass: 500.0,
254                max_forward_acceleration: 70.0,
255                max_backward_acceleration: 0.0,
256                max_lateral_acceleration: 20.0,
257                max_angular_acceleration: 2.0 * TAU,
258            },
259            Class::Unknown => ClassStats {
260                max_health: 100.0,
261                mass: 1000.0,
262                max_forward_acceleration: 0.0,
263                max_backward_acceleration: 0.0,
264                max_lateral_acceleration: 0.0,
265                max_angular_acceleration: 0.0,
266            },
267        }
268    }
269}
270
271/// Stats for a class of ship
272#[derive(Debug, Clone)]
273#[allow(missing_docs)]
274pub struct ClassStats {
275    pub max_health: f64,
276    pub mass: f64,
277    pub max_forward_acceleration: f64,
278    pub max_backward_acceleration: f64,
279    pub max_lateral_acceleration: f64,
280    pub max_angular_acceleration: f64,
281}
282
283/// List of active abilities for an entity.
284#[repr(transparent)]
285pub struct ActiveAbilities(pub u64);
286
287impl ActiveAbilities {
288    /// Activate an ability.
289    pub fn set_ability(&mut self, ability: Ability) {
290        let mut mask = 0u64;
291        mask ^= 1u64 << (ability as u64);
292        self.0 |= mask;
293    }
294    /// Deactivate an ability.
295    pub fn unset_ability(&mut self, ability: Ability) {
296        let mut mask = 0u64;
297        mask ^= 1u64 << (ability as u64);
298        self.0 &= !mask;
299    }
300    /// Get whether an ability is active.
301    pub fn get_ability(&self, ability: Ability) -> bool {
302        (self.0 & 1 << (ability as u64)) >> (ability as u64) != 0
303    }
304    /// Unset all abilities
305    pub fn clear(&mut self) {
306        self.0 = 0
307    }
308    /// Invert set and unset abilities.
309    pub fn invert(&mut self) {
310        self.0 = !self.0
311    }
312    /// Iterator over active abilities
313    pub fn active_iter(&self) -> impl Iterator<Item = Ability> + core::fmt::Debug + Clone + '_ {
314        ABILITIES
315            .iter()
316            .flat_map(|&ability| self.get_ability(ability).then_some(ability))
317    }
318}
319
320/// Special abilities available to different ship classes.
321#[derive(Copy, Clone, PartialEq, Eq, Debug)]
322#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
323pub enum Ability {
324    /// No-op.
325    #[doc(hidden)]
326    None,
327    /// Fighter and missile only. Applies a 100 m/s² forward acceleration for 2s. Reloads in 10s.
328    Boost,
329    /// Deprecated
330    #[doc(hidden)]
331    ShapedCharge,
332    /// Torpedo only. Mimics the radar signature of a Cruiser for 0.5s. Reloads in 10s.
333    Decoy,
334    /// Cruiser only. Deflects projectiles for 1s. Reloads in 5s.
335    Shield,
336}
337
338/// Array of all ability types.
339pub const ABILITIES: &[Ability] = &[Ability::Boost, Ability::Decoy, Ability::Shield];
340
341/// Electronic Counter Measures (ECM) modes.
342#[derive(Copy, Clone, PartialEq, Eq, Debug)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
344pub enum EcmMode {
345    /// No ECM, radar will work normally.
346    None,
347    /// Affected enemy radars will have a lower signal-to-noise ratio, making
348    /// it harder to detect and track targets.
349    Noise,
350}
351
352impl From<f64> for EcmMode {
353    fn from(x: f64) -> Self {
354        match x as u32 {
355            0 => EcmMode::None,
356            1 => EcmMode::Noise,
357            _ => EcmMode::None,
358        }
359    }
360}
361
362#[doc(hidden)]
363#[derive(Default, Clone)]
364pub struct Line {
365    pub x0: f64,
366    pub y0: f64,
367    pub x1: f64,
368    pub y1: f64,
369    pub color: u32,
370}
371
372#[doc(hidden)]
373#[derive(Default, Clone, Debug)]
374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
375pub struct Text {
376    pub x: f64,
377    pub y: f64,
378    pub color: u32,
379    pub length: u8,
380    pub text: [u8; 11],
381}
382
383/// Message sent and received on the radio.
384pub type Message = [f64; 4];
385
386// Public for fuzzer.
387#[doc(hidden)]
388pub mod sys {
389    use super::SystemState;
390    use crate::MAX_ENVIRONMENT_SIZE;
391    use std::ptr;
392
393    // TODO crashes rust-analyzer
394    #[no_mangle]
395    pub static mut SYSTEM_STATE: [u64; SystemState::MaxSize as usize] =
396        [0; SystemState::MaxSize as usize];
397
398    pub fn read_system_state_u64(index: SystemState) -> u64 {
399        let system_state = unsafe { ptr::addr_of!(SYSTEM_STATE) };
400        unsafe { (*system_state)[index as usize] }
401    }
402
403    pub fn write_system_state_u64(index: SystemState, value: u64) {
404        let system_state = unsafe { ptr::addr_of_mut!(SYSTEM_STATE) };
405        unsafe { (*system_state)[index as usize] = value };
406    }
407
408    pub fn read_system_state(index: SystemState) -> f64 {
409        f64::from_bits(read_system_state_u64(index))
410    }
411
412    pub fn write_system_state(index: SystemState, value: f64) {
413        write_system_state_u64(index, value.to_bits())
414    }
415
416    #[no_mangle]
417    pub static mut ENVIRONMENT: [u8; MAX_ENVIRONMENT_SIZE] = [0; MAX_ENVIRONMENT_SIZE];
418
419    pub fn read_environment() -> &'static str {
420        // Format is key=value\nkey=value\n... ending with a null byte.
421        unsafe {
422            let environment = ptr::addr_of!(ENVIRONMENT);
423            let n = (*environment)
424                .iter()
425                .position(|&c| c == 0)
426                .unwrap_or((*environment).len());
427            std::str::from_utf8(&(*environment)[..n])
428                .expect("Failed to convert environment to string")
429        }
430    }
431
432    pub fn getenv(key: &str) -> Option<&'static str> {
433        let environment = read_environment();
434        for line in environment.lines() {
435            let mut parts = line.splitn(2, '=');
436            if let Some(k) = parts.next() {
437                if k == key {
438                    return parts.next();
439                }
440            }
441        }
442        None
443    }
444}
445
446mod math {
447    pub use std::f64::consts::{PI, TAU};
448
449    /// Returns the smallest rotation between angles `a` and `b`.
450    ///
451    /// A positive result is a counter-clockwise rotation and negative is clockwise.
452    pub fn angle_diff(a: f64, b: f64) -> f64 {
453        let c = (b - a).rem_euclid(TAU);
454        if c > PI {
455            c - TAU
456        } else {
457            c
458        }
459    }
460}
461
462mod rng {
463    fn rng() -> &'static mut oorandom::Rand64 {
464        let rng_state = unsafe { super::rng_state::get() };
465        &mut rng_state.rng
466    }
467
468    /// Returns a random number between `low` and `high`.
469    pub fn rand(low: f64, high: f64) -> f64 {
470        rng().rand_float() * (high - low) + low
471    }
472}
473
474#[doc(hidden)]
475pub mod rng_state {
476    #[derive(Clone)]
477    pub struct RngState {
478        pub rng: oorandom::Rand64,
479    }
480
481    static mut RNG_STATE: Option<RngState> = None;
482
483    impl RngState {
484        #[allow(clippy::new_without_default)]
485        pub fn new() -> Self {
486            Self {
487                rng: oorandom::Rand64::new(super::api::seed()),
488            }
489        }
490    }
491
492    pub unsafe fn get() -> &'static mut RngState {
493        RNG_STATE.as_mut().unwrap()
494    }
495
496    pub unsafe fn set(s: RngState) {
497        RNG_STATE = Some(s)
498    }
499}
500
501mod api {
502    use super::sys::{read_system_state, write_system_state};
503    use super::{Ability, Class, EcmMode, SystemState};
504    use crate::sys::{read_system_state_u64, write_system_state_u64};
505    use crate::{vec::*, ActiveAbilities, Message};
506
507    /// The time between each simulation tick.
508    pub const TICK_LENGTH: f64 = 1.0 / 60.0;
509
510    /// Returns a per-ship ID that is unique within a team.
511    pub fn id() -> u32 {
512        read_system_state(SystemState::Id) as u32
513    }
514
515    /// Returns the ship [`Class`] (Fighter, Cruiser, etc).
516    pub fn class() -> Class {
517        Class::from_f64(read_system_state(SystemState::Class))
518    }
519
520    /// Returns a random number useful for initializing a random number generator.
521    pub fn seed() -> u128 {
522        read_system_state(super::SystemState::Seed) as u128
523    }
524
525    /// Returns the scenario name.
526    pub fn scenario_name() -> &'static str {
527        super::sys::getenv("SCENARIO_NAME").unwrap_or("unknown")
528    }
529
530    /// Returns the world size in meters.
531    pub fn world_size() -> f64 {
532        super::sys::getenv("WORLD_SIZE")
533            .unwrap_or("0.0")
534            .parse()
535            .unwrap_or(0.0)
536    }
537
538    /// Returns the current position (in meters).
539    pub fn position() -> Vec2 {
540        vec2(
541            read_system_state(SystemState::PositionX),
542            read_system_state(SystemState::PositionY),
543        )
544    }
545
546    /// Returns the current velocity (in m/s).
547    pub fn velocity() -> Vec2 {
548        vec2(
549            read_system_state(SystemState::VelocityX),
550            read_system_state(SystemState::VelocityY),
551        )
552    }
553
554    /// Returns the current heading (in radians).
555    pub fn heading() -> f64 {
556        read_system_state(SystemState::Heading)
557    }
558
559    /// Returns the current angular velocity (in radians/s).
560    pub fn angular_velocity() -> f64 {
561        read_system_state(SystemState::AngularVelocity)
562    }
563
564    /// Sets the linear acceleration for the next tick (in m/s²).
565    pub fn accelerate(mut acceleration: Vec2) {
566        acceleration = acceleration.rotate(-heading());
567        if acceleration.x > max_forward_acceleration() {
568            acceleration *= max_forward_acceleration() / acceleration.x;
569        }
570        if acceleration.x < -max_backward_acceleration() {
571            acceleration *= max_backward_acceleration() / -acceleration.x;
572        }
573        if acceleration.y.abs() > max_lateral_acceleration() {
574            acceleration *= max_lateral_acceleration() / acceleration.y.abs();
575        }
576        write_system_state(SystemState::AccelerateX, acceleration.x);
577        write_system_state(SystemState::AccelerateY, acceleration.y);
578    }
579
580    /// Rotates the ship at the given speed (in radians/s).
581    ///
582    /// Internally this uses `torque()`. Reaching the commanded speed takes time.
583    pub fn turn(speed: f64) {
584        let max = max_angular_acceleration() * 0.2;
585        let dv = speed.clamp(-max, max) - angular_velocity();
586        if dv.abs() < max_angular_acceleration() * TICK_LENGTH {
587            torque(dv / TICK_LENGTH);
588        } else {
589            torque(dv.signum() * max_angular_acceleration());
590        }
591    }
592
593    /// Sets the angular acceleration for the next tick (in radians/s²).
594    ///
595    /// This is lower-level than turn() and can be used to turn faster.
596    pub fn torque(angular_acceleration: f64) {
597        write_system_state(SystemState::Torque, angular_acceleration);
598    }
599
600    /// Aims a turreted weapon.
601    ///
602    /// `index` selects the weapon.
603    /// `heading` is in radians.
604    pub fn aim(index: usize, heading: f64) {
605        let state_index = match index {
606            0 => SystemState::Aim0,
607            1 => SystemState::Aim1,
608            2 => SystemState::Aim2,
609            3 => SystemState::Aim3,
610            _ => return,
611        };
612        write_system_state(state_index, heading);
613    }
614
615    /// Fires a weapon.
616    ///
617    /// `index` selects the weapon.
618    pub fn fire(index: usize) {
619        let state_index = match index {
620            0 => SystemState::Fire0,
621            1 => SystemState::Fire1,
622            2 => SystemState::Fire2,
623            3 => SystemState::Fire3,
624            _ => return,
625        };
626        write_system_state(state_index, 1.0);
627    }
628
629    /// Returns the number of ticks until a weapon is ready to fire.
630    ///
631    /// `index` selects the weapon. Returns 0 if the weapon is ready.
632    pub fn reload_ticks(index: usize) -> u32 {
633        let state_index = match index {
634            0 => SystemState::ReloadTicks0,
635            1 => SystemState::ReloadTicks1,
636            2 => SystemState::ReloadTicks2,
637            3 => SystemState::ReloadTicks3,
638            _ => return 0,
639        };
640        read_system_state(state_index) as u32
641    }
642
643    /// Self-destructs, producing a damaging explosion.
644    ///
645    /// This is commonly used by missiles.
646    pub fn explode() {
647        write_system_state(SystemState::Explode, 1.0);
648    }
649
650    /// Returns the current health.
651    pub fn health() -> f64 {
652        read_system_state(SystemState::Health)
653    }
654
655    /// Returns the current fuel (delta-v).
656    pub fn fuel() -> f64 {
657        read_system_state(SystemState::Fuel)
658    }
659
660    #[doc(hidden)]
661    pub mod radar_internal {
662        use super::SystemState;
663
664        pub const MAX_RADARS: usize = 2;
665
666        pub struct RadarControlIndices {
667            pub heading: SystemState,
668            pub width: SystemState,
669            pub min_distance: SystemState,
670            pub max_distance: SystemState,
671            pub ecm_mode: SystemState,
672        }
673
674        pub fn radar_control_indices(sel: usize) -> RadarControlIndices {
675            assert!(sel < MAX_RADARS);
676            let stride = 13;
677            let offset = stride * sel;
678            let add_offset =
679                |x| unsafe { ::std::mem::transmute::<u8, SystemState>((x as u8) + offset as u8) };
680            RadarControlIndices {
681                heading: add_offset(SystemState::Radar0Heading),
682                width: add_offset(SystemState::Radar0Width),
683                min_distance: add_offset(SystemState::Radar0MinDistance),
684                max_distance: add_offset(SystemState::Radar0MaxDistance),
685                ecm_mode: add_offset(SystemState::Radar0EcmMode),
686            }
687        }
688
689        pub fn current_radar_control_indices() -> RadarControlIndices {
690            let sel = super::read_system_state(SystemState::SelectedRadar) as usize;
691            radar_control_indices(sel)
692        }
693
694        pub struct RadarContactIndices {
695            pub found: SystemState,
696            pub class: SystemState,
697            pub position: [SystemState; 2],
698            pub velocity: [SystemState; 2],
699            pub rssi: SystemState,
700            pub snr: SystemState,
701        }
702
703        pub fn radar_contact_indices(sel: usize) -> RadarContactIndices {
704            assert!(sel < MAX_RADARS);
705            let stride = 13;
706            let offset = stride * sel;
707            let add_offset =
708                |x| unsafe { ::std::mem::transmute::<u8, SystemState>((x as u8) + offset as u8) };
709            RadarContactIndices {
710                found: add_offset(SystemState::Radar0ContactFound),
711                class: add_offset(SystemState::Radar0ContactClass),
712                position: [
713                    add_offset(SystemState::Radar0ContactPositionX),
714                    add_offset(SystemState::Radar0ContactPositionY),
715                ],
716                velocity: [
717                    add_offset(SystemState::Radar0ContactVelocityX),
718                    add_offset(SystemState::Radar0ContactVelocityY),
719                ],
720                rssi: add_offset(SystemState::Radar0ContactRssi),
721                snr: add_offset(SystemState::Radar0ContactSnr),
722            }
723        }
724
725        pub fn current_radar_contact_indices() -> RadarContactIndices {
726            let sel = super::read_system_state(SystemState::SelectedRadar) as usize;
727            radar_contact_indices(sel)
728        }
729    }
730
731    /// Select the radar to control with subsequent API calls.
732    pub fn select_radar(index: usize) {
733        let index = index.clamp(0, radar_internal::MAX_RADARS - 1);
734        write_system_state(SystemState::SelectedRadar, index as f64);
735    }
736
737    /// Returns the heading the radar is pointed at.
738    pub fn radar_heading() -> f64 {
739        read_system_state(radar_internal::current_radar_control_indices().heading)
740    }
741
742    /// Sets the heading to point the radar at.
743    ///
744    /// It takes effect next tick.
745    pub fn set_radar_heading(heading: f64) {
746        write_system_state(
747            radar_internal::current_radar_control_indices().heading,
748            heading,
749        );
750    }
751
752    /// Returns the current radar width (in radians).
753    ///
754    /// This is the field of view of the radar.
755    pub fn radar_width() -> f64 {
756        read_system_state(radar_internal::current_radar_control_indices().width)
757    }
758
759    /// Sets the radar width (in radians).
760    ///
761    /// This is the field of view of the radar.
762    /// It takes effect next tick.
763    pub fn set_radar_width(width: f64) {
764        write_system_state(radar_internal::current_radar_control_indices().width, width);
765    }
766
767    /// Gets the current minimum distance filter of the radar (in meters).
768    pub fn radar_min_distance() -> f64 {
769        read_system_state(radar_internal::current_radar_control_indices().min_distance)
770    }
771
772    /// Sets the minimum distance filter of the radar (in meters).
773    ///
774    /// It takes effect next tick.
775    pub fn set_radar_min_distance(dist: f64) {
776        write_system_state(
777            radar_internal::current_radar_control_indices().min_distance,
778            dist,
779        );
780    }
781
782    /// Gets the current maximum distance filter of the radar (in meters).
783    pub fn radar_max_distance() -> f64 {
784        read_system_state(radar_internal::current_radar_control_indices().max_distance)
785    }
786
787    /// Sets the maximum distance filter of the radar (in meters).
788    ///
789    /// It takes effect next tick.
790    pub fn set_radar_max_distance(dist: f64) {
791        write_system_state(
792            radar_internal::current_radar_control_indices().max_distance,
793            dist,
794        );
795    }
796
797    /// Gets the Electronic Counter Measures (ECM) mode.
798    pub fn radar_ecm_mode() -> EcmMode {
799        read_system_state(radar_internal::current_radar_control_indices().ecm_mode).into()
800    }
801
802    /// Sets the Electronic Counter Measures (ECM) mode.
803    pub fn set_radar_ecm_mode(mode: EcmMode) {
804        write_system_state(
805            radar_internal::current_radar_control_indices().ecm_mode,
806            mode as u32 as f64,
807        );
808    }
809
810    /// A radar contact.
811    #[derive(Clone, Debug)]
812    pub struct ScanResult {
813        /// The contact's class.
814        pub class: Class,
815        /// The contact's approximate position.
816        pub position: Vec2,
817        /// The contact's approximate velocity.
818        pub velocity: Vec2,
819        /// The received signal strength measured in dBm.
820        pub rssi: f64,
821        /// The signal-to-noise ratio measured in dB.
822        pub snr: f64,
823    }
824
825    /// Returns the radar contact with the highest signal strength.
826    pub fn scan() -> Option<ScanResult> {
827        let indices = radar_internal::current_radar_contact_indices();
828        if read_system_state(indices.found) == 0.0 {
829            return None;
830        }
831        Some(ScanResult {
832            class: Class::from_f64(read_system_state(indices.class)),
833            position: vec2(
834                read_system_state(indices.position[0]),
835                read_system_state(indices.position[1]),
836            ),
837            velocity: vec2(
838                read_system_state(indices.velocity[0]),
839                read_system_state(indices.velocity[1]),
840            ),
841            rssi: read_system_state(indices.rssi),
842            snr: read_system_state(indices.snr),
843        })
844    }
845
846    #[doc(hidden)]
847    pub mod radio_internal {
848        use super::SystemState;
849
850        pub const MAX_RADIOS: usize = 8;
851
852        pub struct RadioIndices {
853            pub channel: SystemState,
854            pub send: SystemState,
855            pub receive: SystemState,
856            pub data: [SystemState; 4],
857        }
858
859        pub fn radio_indices(sel: usize) -> RadioIndices {
860            assert!(sel < MAX_RADIOS);
861            let stride = 7;
862            let offset = stride * sel;
863            let add_offset =
864                |x| unsafe { ::std::mem::transmute::<u8, SystemState>((x as u8) + offset as u8) };
865            RadioIndices {
866                channel: add_offset(SystemState::Radio0Channel),
867                send: add_offset(SystemState::Radio0Send),
868                receive: add_offset(SystemState::Radio0Receive),
869                data: [
870                    add_offset(SystemState::Radio0Data0),
871                    add_offset(SystemState::Radio0Data1),
872                    add_offset(SystemState::Radio0Data2),
873                    add_offset(SystemState::Radio0Data3),
874                ],
875            }
876        }
877    }
878
879    /// Select the radio to control with subsequent API calls.
880    pub fn select_radio(index: usize) {
881        let index = index.clamp(0, radio_internal::MAX_RADIOS - 1);
882        write_system_state(SystemState::SelectedRadio, index as f64);
883    }
884
885    /// Sets the channel to send and receive radio transmissions on.
886    ///
887    /// Takes effect next tick.
888    pub fn set_radio_channel(channel: usize) {
889        write_system_state(
890            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize)
891                .channel,
892            channel as f64,
893        );
894    }
895
896    /// Gets the current radio channel.
897    pub fn get_radio_channel() -> usize {
898        read_system_state(
899            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize)
900                .channel,
901        ) as usize
902    }
903
904    /// Sends a radio message.
905    ///
906    /// The message will be received on the next tick.
907    ///
908    /// If you want to send arbitrary data, consider using [`send_bytes`] instead.
909    pub fn send(msg: Message) {
910        let idxs =
911            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize);
912        write_system_state(idxs.send, 1.0);
913        write_system_state(idxs.data[0], msg[0]);
914        write_system_state(idxs.data[1], msg[1]);
915        write_system_state(idxs.data[2], msg[2]);
916        write_system_state(idxs.data[3], msg[3]);
917    }
918
919    /// Returns the received radio message.
920    pub fn receive() -> Option<Message> {
921        let idxs =
922            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize);
923        if read_system_state(idxs.receive) != 0.0 {
924            Some([
925                read_system_state(idxs.data[0]),
926                read_system_state(idxs.data[1]),
927                read_system_state(idxs.data[2]),
928                read_system_state(idxs.data[3]),
929            ])
930        } else {
931            None
932        }
933    }
934
935    /// Sends a radio message.
936    /// The message will be zero-filled or truncated to be 32 bytes long.
937    ///
938    /// The message will be received on the next tick.
939    ///
940    /// If you only want to send [`f64`]s consider using [`send`] instead.
941    pub fn send_bytes(msg: &[u8]) {
942        let mut bytes = [[0; 8]; 4];
943        bytes
944            .iter_mut()
945            .flatten()
946            .zip(msg)
947            .for_each(|(b, m)| *b = *m);
948
949        let idxs =
950            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize);
951        write_system_state(idxs.send, 1.0);
952        write_system_state_u64(idxs.data[0], u64::from_ne_bytes(bytes[0]));
953        write_system_state_u64(idxs.data[1], u64::from_ne_bytes(bytes[1]));
954        write_system_state_u64(idxs.data[2], u64::from_ne_bytes(bytes[2]));
955        write_system_state_u64(idxs.data[3], u64::from_ne_bytes(bytes[3]));
956    }
957
958    /// Returns the received radio message.
959    pub fn receive_bytes() -> Option<[u8; 32]> {
960        let idxs =
961            radio_internal::radio_indices(read_system_state(SystemState::SelectedRadio) as usize);
962        if read_system_state(idxs.receive) != 0.0 {
963            let mut bytes = [0; 32];
964            bytes[0..8].copy_from_slice(&read_system_state_u64(idxs.data[0]).to_ne_bytes());
965            bytes[8..16].copy_from_slice(&read_system_state_u64(idxs.data[1]).to_ne_bytes());
966            bytes[16..24].copy_from_slice(&read_system_state_u64(idxs.data[2]).to_ne_bytes());
967            bytes[24..32].copy_from_slice(&read_system_state_u64(idxs.data[3]).to_ne_bytes());
968            Some(bytes)
969        } else {
970            None
971        }
972    }
973
974    /// Returns the maximum linear acceleration (in m/s²).
975    #[deprecated]
976    pub fn max_acceleration() -> Vec2 {
977        vec2(
978            read_system_state(SystemState::MaxForwardAcceleration),
979            read_system_state(SystemState::MaxBackwardAcceleration),
980        )
981    }
982
983    /// Returns the maximum forward acceleration (in m/s²).
984    pub fn max_forward_acceleration() -> f64 {
985        read_system_state(SystemState::MaxForwardAcceleration)
986    }
987
988    /// Returns the maximum backward acceleration (in m/s²).
989    pub fn max_backward_acceleration() -> f64 {
990        read_system_state(SystemState::MaxBackwardAcceleration)
991    }
992
993    /// Returns the maximum lateral acceleration (in m/s²).
994    pub fn max_lateral_acceleration() -> f64 {
995        read_system_state(SystemState::MaxLateralAcceleration)
996    }
997
998    /// Returns the maximum angular acceleration (in radians/s²).
999    pub fn max_angular_acceleration() -> f64 {
1000        read_system_state(SystemState::MaxAngularAcceleration)
1001    }
1002
1003    /// Returns the number of ticks elapsed since the simulation began.
1004    pub fn current_tick() -> u32 {
1005        read_system_state(SystemState::CurrentTick) as u32
1006    }
1007
1008    /// Returns the number of seconds elapsed since the simulation began.
1009    pub fn current_time() -> f64 {
1010        read_system_state(SystemState::CurrentTick) * TICK_LENGTH
1011    }
1012
1013    /// Activates a special ability.
1014    pub fn activate_ability(ability: Ability) {
1015        let mut active_abilities =
1016            ActiveAbilities(read_system_state_u64(SystemState::ActivateAbility));
1017        active_abilities.set_ability(ability);
1018        write_system_state_u64(SystemState::ActivateAbility, active_abilities.0);
1019    }
1020
1021    /// Deactivates a special ability.
1022    pub fn deactivate_ability(ability: Ability) {
1023        let mut active_abilities =
1024            ActiveAbilities(read_system_state_u64(SystemState::ActivateAbility));
1025        active_abilities.unset_ability(ability);
1026        write_system_state_u64(SystemState::ActivateAbility, active_abilities.0);
1027    }
1028
1029    /// Get a copy of the active abilities. Useful for querying which abilities are currently active.
1030    pub fn active_abilities() -> ActiveAbilities {
1031        ActiveAbilities(read_system_state_u64(SystemState::ActivateAbility))
1032    }
1033
1034    /// Returns the position of the target set by the scenario.
1035    /// Only used in tutorials.
1036    pub fn target() -> Vec2 {
1037        vec2(
1038            read_system_state(radar_internal::current_radar_contact_indices().position[0]),
1039            read_system_state(radar_internal::current_radar_contact_indices().position[1]),
1040        )
1041    }
1042
1043    /// Returns the velocity of the target set by the scenario.
1044    /// Only used in tutorials.
1045    pub fn target_velocity() -> Vec2 {
1046        vec2(
1047            read_system_state(radar_internal::current_radar_contact_indices().velocity[0]),
1048            read_system_state(radar_internal::current_radar_contact_indices().velocity[1]),
1049        )
1050    }
1051}
1052
1053#[doc(hidden)]
1054#[macro_use]
1055pub mod dbg {
1056    use super::{Line, Text};
1057    use crate::sys::write_system_state;
1058    use crate::vec::*;
1059    use std::f64::consts::TAU;
1060    use std::ptr;
1061
1062    static mut TEXT_BUFFER: String = String::new();
1063    static mut LINE_BUFFER: Vec<Line> = Vec::new();
1064    static mut DRAWN_TEXT_BUFFER: Vec<Text> = Vec::new();
1065
1066    /// Adds text to be displayed when the ship is selected by clicking on it.
1067    ///
1068    /// Works just like [println!].
1069    #[macro_export]
1070    macro_rules! debug {
1071        ($($arg:tt)*) => {
1072            $crate::dbg::write(std::format_args!($($arg)*))
1073        };
1074    }
1075
1076    #[allow(unused)]
1077    #[doc(hidden)]
1078    pub fn write(args: std::fmt::Arguments) {
1079        use std::fmt::Write;
1080        unsafe {
1081            let buf = ptr::addr_of_mut!(TEXT_BUFFER);
1082            let _ = std::fmt::write(&mut *buf, args);
1083            (*buf).push('\n');
1084        }
1085    }
1086
1087    /// Creates a 24-bit RGB color from the arguments.
1088    pub fn rgb(r: u8, g: u8, b: u8) -> u32 {
1089        let r = r as u32;
1090        let g = g as u32;
1091        let b = b as u32;
1092        r << 16 | g << 8 | b
1093    }
1094
1095    /// Draws a line visible in debug mode.
1096    ///
1097    /// `a` and `b` are positions in world coordinates.
1098    /// `color` is 24-bit RGB.
1099    ///
1100    /// Up to 1024 lines can be drawn per ship, per tick. This quota is also consumed
1101    /// by the various shape drawing functions.
1102    pub fn draw_line(a: Vec2, b: Vec2, color: u32) {
1103        unsafe {
1104            let buf = ptr::addr_of_mut!(LINE_BUFFER);
1105            (*buf).push(Line {
1106                x0: a.x,
1107                y0: a.y,
1108                x1: b.x,
1109                y1: b.y,
1110                color,
1111            });
1112        }
1113    }
1114
1115    #[deprecated]
1116    #[doc(hidden)]
1117    pub fn debug_line(a: Vec2, b: Vec2, color: u32) {
1118        draw_line(a, b, color)
1119    }
1120
1121    /// Draws a regular polygon visible in debug mode.
1122    ///
1123    /// `center` is a position in world coordinates.
1124    /// `color` is 24-bit RGB.
1125    pub fn draw_polygon(center: Vec2, radius: f64, sides: i32, angle: f64, color: u32) {
1126        let delta_angle = TAU / sides as f64;
1127        let sin = delta_angle.sin();
1128        let cos = delta_angle.cos();
1129        let rotation: maths_rs::Mat2d = maths_rs::prelude::MatNew2::new(cos, -sin, sin, cos);
1130        let mut p = vec2(radius, 0.0).rotate(angle);
1131        for _ in 0..sides {
1132            let p2 = rotation * p;
1133            draw_line(center + p, center + p2, color);
1134            p = p2;
1135        }
1136    }
1137
1138    #[deprecated]
1139    #[doc(hidden)]
1140    pub fn debug_polygon(center: Vec2, radius: f64, sides: i32, angle: f64, color: u32) {
1141        draw_polygon(center, radius, sides, angle, color)
1142    }
1143
1144    /// Draws a triangle visible in debug mode.
1145    ///
1146    /// `center` is a position in world coordinates.
1147    /// `color` is 24-bit RGB.
1148    pub fn draw_triangle(center: Vec2, radius: f64, color: u32) {
1149        let x = f64::sqrt(3.0) * radius / 2.0;
1150        let y = radius / 2.0;
1151        let points = [
1152            center + vec2(0.0, radius),
1153            center + vec2(-x, -y),
1154            center + vec2(x, -y),
1155        ];
1156        for i in 0..3 {
1157            draw_line(points[i], points[(i + 1) % 3], color);
1158        }
1159    }
1160
1161    #[deprecated]
1162    #[doc(hidden)]
1163    pub fn debug_triangle(center: Vec2, radius: f64, color: u32) {
1164        draw_triangle(center, radius, color)
1165    }
1166
1167    /// Draws a square visible in debug mode.
1168    ///
1169    /// `center` is a position in world coordinates.
1170    /// `color` is 24-bit RGB.
1171    pub fn draw_square(center: Vec2, radius: f64, color: u32) {
1172        let offset = radius / f64::sqrt(2.0);
1173        let mut p = vec2(offset, offset);
1174        for _ in 0..4 {
1175            let p2 = vec2(-p.y, p.x);
1176            draw_line(center + p, center + p2, color);
1177            p = p2;
1178        }
1179    }
1180
1181    #[deprecated]
1182    #[doc(hidden)]
1183    pub fn debug_square(center: Vec2, radius: f64, color: u32) {
1184        draw_square(center, radius, color)
1185    }
1186
1187    /// Draws a diamond visible in debug mode.
1188    ///
1189    /// `center` is a position in world coordinates.
1190    /// `color` is 24-bit RGB.
1191    pub fn draw_diamond(center: Vec2, radius: f64, color: u32) {
1192        let mut p = vec2(radius, 0.0);
1193        for _ in 0..4 {
1194            let p2 = vec2(-p.y, p.x);
1195            draw_line(center + p, center + p2, color);
1196            p = p2;
1197        }
1198    }
1199
1200    #[deprecated]
1201    #[doc(hidden)]
1202    pub fn debug_diamond(center: Vec2, radius: f64, color: u32) {
1203        draw_diamond(center, radius, color)
1204    }
1205
1206    /// Adds text to be drawn in the world, visible in debug mode.
1207    ///
1208    /// Works like [println!]. Up to 128 strings can be drawn per ship, per tick.
1209    #[macro_export]
1210    macro_rules! draw_text {
1211        ($topleft:expr, $color:expr, $($arg:tt)*) => {
1212            $crate::dbg::draw_text_internal($topleft, $color, std::format_args!($($arg)*))
1213        };
1214    }
1215
1216    #[allow(unused)]
1217    #[doc(hidden)]
1218    pub fn draw_text_internal(topleft: Vec2, color: u32, args: std::fmt::Arguments) {
1219        use std::fmt::Write;
1220        let mut text = String::new();
1221        let _ = std::fmt::write(&mut text, args);
1222        let buf = unsafe { &mut *ptr::addr_of_mut!(DRAWN_TEXT_BUFFER) };
1223        // TODO handle longer text
1224        let mut text_buf = [0u8; 11];
1225        text_buf
1226            .iter_mut()
1227            .zip(text.bytes())
1228            .for_each(|(d, s)| *d = s);
1229        buf.push(Text {
1230            x: topleft.x,
1231            y: topleft.y,
1232            color,
1233            length: text.len().min(text_buf.len()) as u8,
1234            text: text_buf,
1235        });
1236    }
1237
1238    #[doc(hidden)]
1239    pub fn update() {
1240        {
1241            let slice = unsafe { &mut *ptr::addr_of_mut!(TEXT_BUFFER) }.as_bytes();
1242            write_system_state(
1243                super::SystemState::DebugTextPointer,
1244                slice.as_ptr() as u32 as f64,
1245            );
1246            write_system_state(
1247                super::SystemState::DebugTextLength,
1248                slice.len() as u32 as f64,
1249            );
1250        }
1251        {
1252            let slice = unsafe { &mut *ptr::addr_of_mut!(LINE_BUFFER) }.as_slice();
1253            write_system_state(
1254                super::SystemState::DebugLinesPointer,
1255                slice.as_ptr() as u32 as f64,
1256            );
1257            write_system_state(
1258                super::SystemState::DebugLinesLength,
1259                slice.len() as u32 as f64,
1260            );
1261        }
1262        {
1263            let slice = unsafe { &mut *ptr::addr_of_mut!(DRAWN_TEXT_BUFFER) }.as_slice();
1264            write_system_state(
1265                super::SystemState::DrawnTextPointer,
1266                slice.as_ptr() as u32 as f64,
1267            );
1268            write_system_state(
1269                super::SystemState::DrawnTextLength,
1270                slice.len() as u32 as f64,
1271            );
1272        }
1273    }
1274
1275    #[doc(hidden)]
1276    pub fn reset() {
1277        unsafe {
1278            TEXT_BUFFER.clear();
1279            LINE_BUFFER.clear();
1280            DRAWN_TEXT_BUFFER.clear();
1281        }
1282    }
1283}
1284
1285mod deprecated {
1286    use super::api::*;
1287    use super::sys::write_system_state;
1288    use super::SystemState;
1289
1290    /// TODO Remove this.
1291    #[deprecated]
1292    pub fn aim_gun(index: usize, heading: f64) {
1293        aim(index, heading);
1294    }
1295
1296    /// TODO Remove this.
1297    #[deprecated]
1298    pub fn fire_gun(index: usize) {
1299        fire(index);
1300    }
1301
1302    /// TODO Remove this.
1303    #[deprecated]
1304    pub fn launch_missile(index: usize, _unused: f64) {
1305        use super::Class::*;
1306        let state_index = match (class(), index) {
1307            (Fighter, 0) => SystemState::Fire1,
1308
1309            (Frigate, 0) => SystemState::Fire3,
1310
1311            (Cruiser, 0) => SystemState::Fire1,
1312            (Cruiser, 1) => SystemState::Fire2,
1313            (Cruiser, 2) => SystemState::Fire3,
1314
1315            _ => return,
1316        };
1317        write_system_state(state_index, 1.0);
1318    }
1319
1320    /// TODO Remove this.
1321    #[deprecated]
1322    pub fn orders() -> f64 {
1323        0.0
1324    }
1325}
1326
1327/// All APIs.
1328pub mod prelude {
1329    #[doc(inline)]
1330    pub use super::api::*;
1331    #[doc(inline)]
1332    pub use super::dbg::*;
1333    #[doc(hidden)]
1334    pub use super::deprecated::*;
1335    #[doc(inline)]
1336    pub use super::math::*;
1337    #[doc(inline)]
1338    pub use super::rng::*;
1339    #[doc(inline)]
1340    pub use super::vec::*;
1341    #[doc(inline)]
1342    pub use super::{Ability, Class, EcmMode, Message};
1343    #[doc(inline)]
1344    pub use crate::{debug, draw_text};
1345
1346    pub use byteorder;
1347    pub use maths_rs;
1348    pub use oorandom;
1349}