#![allow(clippy::upper_case_acronyms)] use std::borrow::Cow; use std::collections::HashSet; use std::fmt::Display; use std::str::FromStr; use crate::record::Precision; use crate::ParseError; #[derive(Debug, Clone, PartialEq)] pub enum Property { /// Object Coordinates. T(Coords), /// The object name should use the most common notation for each object. It is strongly /// recommended to use ICAO or NATO names like: C172 or F/A-18C. This will help Tacview to /// associate each object with the corresponding entry in its database. Type and Name are the /// only properties which *CANNOT* be predefined in Tacview database. Name(String), /// Object types are built using tags. This makes object management much more powerful and /// transparent than with the previous exclusive types. Type and Name are the only properties /// which *CANNOT* be predefined in Tacview database. Type(HashSet), /// Parent object id. Useful to associate for example a missile (child object) and /// its launcher aircraft (parent object). Parent(u64), /// ID of the following object. Typically used to link waypoints together. Next(u64), /// The call sign will be displayed in priority over the object name and sometimes pilot name, /// especially in the 3D view and selection boxes. This is handy for mission debriefings where /// call signs are more informative than aircraft names. CallSign(String), /// Aircraft registration (aka tail number) Registration(String), /// Current transponder code. Any code is possible, there is no limitation like with the old 4 /// digit transponders. Squawk(String), /// Mode S equipped aircraft uniquely assigned ICAO 24-bit address. ICAO24(String), /// Aircraft pilot in command name. Pilot(String), /// Group the object belongs to. Used to group objects together. For example, a formation of /// F-16 flying a CAP together. Group(String), /// ISO 3166-1 alpha-2 country code. Country(String), /// Coalition. Coalition(String), /// Color of the object. Color(Color), /// Filename of the 3D model which will be used to represent the object in the 3D view. 3D /// models must be in Wavefront .obj file format and stored in either /// %ProgramData%\Tacview\Data\Meshes\ or %APPDATA%\Tacview\Data\Meshes\. Shape(String), /// Debug text visible in the 3D view when Tacview is launched with the /Debug:on command line /// argument. Debug(String), /// Free real-time text displayable in the 3D view and telemetry windows (to provide /// miscellaneous info to the end-user) Label(String), /// Target currently focused by the object (typically used to designate laser beam target /// object, can also be used to show what the pilot is currently focused on) FocusedTarget(u64), /// Primary target id (could be locked using any device, like radar, IR, NVG, ...) LockedTarget(u64), /// The higher the ratio, the more important is the object is (e.g. locally simulated aircraft /// could be 1.0 importance factor). /// Unit: ratio Importance(f64), /// Plane position in its Group (the lowest is the leader). Slot(u64), /// Specifies that an object is disabled (typically out-of-combat) without being destroyed yet. /// This is particularly useful for combat training and shotlogs. Disabled(bool), /// This flag is useful to hide specific objects from the 3D view. Can be used for a fog-of-war /// effect, or to prevent virtual objects from being displayed. Visible(bool), /// Use this attribute to record the current health status of an object. The ratio is equal to /// 1.0 when the object is brand new, and 0.0 whenever the object is out of /// combat/dead/destroyed. This attribute as currently no effect on the events, you still need /// to remove the object manually whenever it is destroyed. /// Unit: ratio Health(f64), /// Object length. Especially useful when displaying buildings. /// Unit: m Length(f64), /// Object width. Especially useful when displaying buildings. /// Unit: m Width(f64), /// Object height. Especially useful when displaying buildings. /// Unit: m Height(f64), /// Object bounding sphere radius. Object bounding sphere radius. Can be used to define custom /// explosion, smoke/grenade radius. Can be animated. /// Unit: m Radius(f64), /// Indicated airspeed. /// Unit: m/s IAS(f64), /// Calibrated airspeed. /// Unit: m/s CAS(f64), /// True airspeed. /// Unit: m/s TAS(f64), /// Mach number. /// Unit: ratio Mach(f64), /// Angle of attack. /// Unit: deg AOA(f64), /// Sideslip angle, also called angle of sideslip. /// Unit: deg AOS(f64), /// Object altitude above ground level. /// Unit: m AGL(f64), /// Aircraft heading. When there is no roll and pitch data available, this property can be used /// to specify the yaw while keeping full rotation emulation in the 3D view. /// Unit: deg HDG(f64), /// Aircraft magnetic heading. Heading relative to local magnetic north. /// Unit: deg HDM(f64), /// Main/engine #1 throttle handle position (could be >1 for Afterburner and <0 for reverse). /// Unit: ratio Throttle(f64), /// Engine #2 throttle handle position (could be >1 for Afterburner and <0 for reverse). /// Unit: ratio Throttle2(f64), /// Main/engine #1 afterburner status. /// Unit: ratio Afterburner(f64), /// Air brakes status. /// Unit: ratio AirBrakes(f64), /// Flaps position. /// Unit: ratio Flaps(f64), /// Landing gear status. /// Unit: ratio LandingGear(f64), /// Landing gear handle position. /// Unit: ratio LandingGearHandle(f64), /// Arresting hook status. /// Unit: ratio Tailhook(f64), /// Parachute status (not to be mistaken for DragChute). /// Unit: ratio Parachute(f64), /// Drogue/Drag Parachute status. /// Unit: ratio DragChute(f64), /// Fuel quantity currently available in each tanks (up to 10 tanks supported). /// Unit: kg FuelWeight(u8, f64), /// Fuel quantity currently available in each tanks (up to 10 tanks supported). /// Unit: l FuelVolume(u8, f64), /// Fuel flow for each engine (up to 8 engines supported). /// Unit: kg/hour FuelFlowWeight(u8, f64), /// Fuel flow for each engine (up to 8 engines supported). /// Unit: l/hour FuelFlowVolume(u8, f64), /// Radar mode (0 = off) RadarMode(f64), /// Radar azimuth (heading) relative to aircraft orientation. /// Unit: deg RadarAzimuth(f64), /// Radar elevation relative to aircraft orientation. /// Unit: deg RadarElevation(f64), /// Radar roll angle relative to aircraft orientation. /// Unit: deg RadarRoll(f64), /// Radar scan range. /// Unit: m RadarRange(f64), /// Radar beamwidth in azimuth. /// Unit: deg RadarHorizontalBeamwidth(f64), /// Radar beamwidth in elevation. /// Unit: deg RadarVerticalBeamwidth(f64), /// Primary target lock mode (0 = no lock/no target). LockedTargetMode(f64), /// Primary target azimuth (heading) relative to aircraft orientation. /// Unit: deg LockedTargetAzimuth(f64), /// Primary target elevation relative to aircraft orientation. /// Unit: deg LockedTargetElevation(f64), /// Primary target distance to aircraft. /// Unit: m LockedTargetRange(f64), /// Enable/disable engagement range (such as when a SAM site turns off its radar) (0 = off). EngagementMode(f64), /// Enable/disable engagement range (such as when a SAM site turns off its radar) (0 = off). EngagementMode2(f64), /// Engagement range for anti-aircraft units. This is the radius of the sphere which will be /// displayed in the 3D view. Typically used for SAM and AAA units, but this can be also /// relevant to warships. /// Unit: m EngagementRange(f64), /// Engagement range for anti-aircraft units. This is the radius of the sphere which will be /// displayed in the 3D view. Typically used for SAM and AAA units, but this can be also /// relevant to warships. /// Unit: m EngagementRange2(f64), /// Engagement range for anti-aircraft units. This is the radius of the sphere which will be /// displayed in the 3D view. Typically used for SAM and AAA units, but this can be also /// relevant to warships. /// Unit: m VerticalEngagementRange(f64), /// Engagement range for anti-aircraft units. This is the radius of the sphere which will be /// displayed in the 3D view. Typically used for SAM and AAA units, but this can be also /// relevant to warships. /// Unit: m VerticalEngagementRange2(f64), /// Raw player HOTAS/Yoke position in real-life (flight sim input device). /// Unit: ratio RollControlInput(f64), /// Raw player HOTAS/Yoke position in real-life (flight sim input device). /// Unit: ratio PitchControlInput(f64), /// Raw player HOTAS/Yoke position in real-life (flight sim input device). /// Unit: ratio YawControlInput(f64), /// HOTAS/Yoke position in simulated (with response curves) or real-life cockpit. /// Unit: ratio RollControlPosition(f64), /// HOTAS/Yoke position in simulated (with response curves) or real-life cockpit. /// Unit: ratio PitchControlPosition(f64), /// HOTAS/Yoke position in simulated (with response curves) or real-life cockpit. /// Unit: ratio YawControlPosition(f64), /// Trim position for each axis. /// Unit: ratio RollTrimTab(f64), /// Trim position for each axis. /// Unit: ratio PitchTrimTab(f64), /// Trim position for each axis. /// Unit: ratio YawTrimTab(f64), /// Control surfaces position on the aircraft. /// Unit: ratio AileronLeft(f64), /// Control surfaces position on the aircraft. /// Unit: ratio AileronRight(f64), /// Control surfaces position on the aircraft. /// Unit: ratio Elevator(f64), /// Control surfaces position on the aircraft. /// Unit: ratio Rudder(f64), /// Pilot head orientation in the cockpit relative to the aircraft orientation /// Unit: ratio PilotHeadRoll(f64), /// Pilot head orientation in the cockpit relative to the aircraft orientation /// Unit: ratio PilotHeadPitch(f64), /// Pilot head orientation in the cockpit relative to the aircraft orientation /// Unit: ratio PilotHeadYaw(f64), /// Gravitational force equivalent of the acceleration in each axis relative to the aircraft /// orientation /// Unit: g VerticalGForce(f64), /// Gravitational force equivalent of the acceleration in each axis relative to the aircraft /// orientation /// Unit: g LongitudinalGForce(f64), /// Gravitational force equivalent of the acceleration in each axis relative to the aircraft /// orientation /// Unit: g LateralGForce(f64), /// Ratio between 0 and 1 describing the current Environmental Noise Level measured by the /// flight recorder. Typically used by gliders to detect engine use. This is the equivalent of /// the ENL field which can be found in IGC files. /// Unit: ratio ENL(f64), /// Unknown property. This only exists for forward compatibility and using it is not recommended /// as the property you are using could be move to the known properties in a future release. Unknown(String, String), } #[derive(Debug, Default, Clone, PartialEq)] pub struct Coords { /// Unit: deg pub longitude: Option, /// Unit: deg pub latitude: Option, /// Unit: m pub altitude: Option, /// Native x coordinate from a flat world. pub u: Option, /// Native y coordinate from a flat world. pub v: Option, /// Positive when rolling the aircraft to the right. pub roll: Option, /// Positive when taking off. pub pitch: Option, /// Clockwise relative to true north. pub yaw: Option, /// Yaw relative to true north of the flat world. pub heading: Option, } impl Coords { pub fn update(&mut self, other: &Coords, reference_latitude: f64, reference_longitude: f64) { if let Some(longitude) = other.longitude { self.longitude = Some(longitude + reference_longitude); } if let Some(latitude) = other.latitude { self.latitude = Some(latitude + reference_latitude); } if let Some(altitude) = other.altitude { self.altitude = Some(altitude); } if let Some(u) = other.u { self.u = Some(u); } if let Some(v) = other.v { self.v = Some(v); } if let Some(roll) = other.roll { self.roll = Some(roll); } if let Some(pitch) = other.pitch { self.pitch = Some(pitch); } if let Some(yaw) = other.yaw { self.yaw = Some(yaw); } if let Some(heading) = other.heading { self.heading = Some(heading); } } pub fn position(mut self, lat: f64, lon: f64, alt: f64) -> Self { self.latitude = Some(lat); self.longitude = Some(lon); self.altitude = Some(alt); self } pub fn uv(mut self, u: f64, v: f64) -> Self { self.u = Some(u); self.v = Some(v); self } pub fn orientation(mut self, yaw: f64, pitch: f64, roll: f64) -> Self { self.yaw = Some(yaw); self.pitch = Some(pitch); self.roll = Some(roll); self } pub fn heading(mut self, v: f64) -> Self { self.heading = Some(v); self } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Color { Red, Orange, Green, Blue, Violet, Grey, Unknown(String), } #[derive(Debug, Hash, Clone, PartialEq, Eq)] pub enum Tag { // Class Air, Ground, Sea, Weapon, Sensor, Navaid, Misc, // Attributes Static, Heavy, Medium, Light, Minor, // Basic Types FixedWing, Rotorcraft, Armor, AntiAircraft, Vehicle, Watercraft, Human, Biologic, Missile, Rocket, Bomb, Torpedo, Projectile, Beam, Decoy, Building, Bullseye, Waypoint, // Specific Types Tank, Warship, AircraftCarrier, Submarine, Infantry, Parachutist, Shell, Bullet, Flare, Chaff, SmokeGrenade, Aerodrome, Container, Shrapnel, Unknown(String), } impl FromStr for Property { type Err = ParseError; fn from_str(s: &str) -> Result { let (name, value) = s.split_once('=').ok_or(ParseError::MissingDelimiter('='))?; Ok(match name { "T" => Property::T(Coords::from_str(value)?), "Name" => Property::Name(value.to_string()), "Type" => Property::Type(value.split('+').map(Tag::from).collect()), "Parent" => Property::Parent(u64::from_str_radix(value, 16)?), "Next" => Property::Next(u64::from_str_radix(value, 16)?), "CallSign" => Property::CallSign(value.to_string()), "Registration" => Property::Registration(value.to_string()), "Squawk" => Property::Squawk(value.to_string()), "ICAO24" => Property::ICAO24(value.to_string()), "Pilot" => Property::Pilot(value.to_string()), "Group" => Property::Group(value.to_string()), "Country" => Property::Country(value.to_string()), "Coalition" => Property::Coalition(value.to_string()), "Color" => Property::Color(Color::from(value)), "Shape" => Property::Shape(value.to_string()), "Debug" => Property::Debug(value.to_string()), "Label" => Property::Label(value.to_string()), "FocusedTarget" => Property::FocusedTarget(u64::from_str_radix(value, 16)?), "LockedTarget" => Property::LockedTarget(u64::from_str_radix(value, 16)?), "Importance" => Property::Importance(FromStr::from_str(value)?), "Slot" => Property::Slot(FromStr::from_str(value)?), "Disabled" => Property::Disabled(i64::from_str(value)? != 0), "Visible" => Property::Visible(i64::from_str(value)? != 0), "Health" => Property::Health(FromStr::from_str(value)?), "Length" => Property::Length(FromStr::from_str(value)?), "Width" => Property::Width(FromStr::from_str(value)?), "Height" => Property::Height(FromStr::from_str(value)?), "Radius" => Property::Radius(FromStr::from_str(value)?), "IAS" => Property::IAS(FromStr::from_str(value)?), "CAS" => Property::CAS(FromStr::from_str(value)?), "TAS" => Property::TAS(FromStr::from_str(value)?), "Mach" => Property::Mach(FromStr::from_str(value)?), "AOA" => Property::AOA(FromStr::from_str(value)?), "AOS" => Property::AOS(FromStr::from_str(value)?), "AGL" => Property::AGL(FromStr::from_str(value)?), "HDG" => Property::HDG(FromStr::from_str(value)?), "HDM" => Property::HDM(FromStr::from_str(value)?), "Throttle" => Property::Throttle(FromStr::from_str(value)?), "Throttle2" => Property::Throttle2(FromStr::from_str(value)?), "Afterburner" => Property::Afterburner(FromStr::from_str(value)?), "AirBrakes" => Property::AirBrakes(FromStr::from_str(value)?), "Flaps" => Property::Flaps(FromStr::from_str(value)?), "LandingGear" => Property::LandingGear(FromStr::from_str(value)?), "LandingGearHandle" => Property::LandingGearHandle(FromStr::from_str(value)?), "Tailhook" => Property::Tailhook(FromStr::from_str(value)?), "Parachute" => Property::Parachute(FromStr::from_str(value)?), "DragChute" => Property::DragChute(FromStr::from_str(value)?), "FuelWeight" => Property::FuelWeight(0, FromStr::from_str(value)?), "FuelWeight2" => Property::FuelWeight(1, FromStr::from_str(value)?), "FuelWeight3" => Property::FuelWeight(2, FromStr::from_str(value)?), "FuelWeight4" => Property::FuelWeight(3, FromStr::from_str(value)?), "FuelWeight5" => Property::FuelWeight(4, FromStr::from_str(value)?), "FuelWeight6" => Property::FuelWeight(5, FromStr::from_str(value)?), "FuelWeight7" => Property::FuelWeight(6, FromStr::from_str(value)?), "FuelWeight8" => Property::FuelWeight(7, FromStr::from_str(value)?), "FuelWeight9" => Property::FuelWeight(8, FromStr::from_str(value)?), "FuelVolume" => Property::FuelVolume(0, FromStr::from_str(value)?), "FuelVolume1" => Property::FuelVolume(1, FromStr::from_str(value)?), "FuelVolume2" => Property::FuelVolume(2, FromStr::from_str(value)?), "FuelVolume3" => Property::FuelVolume(3, FromStr::from_str(value)?), "FuelVolume4" => Property::FuelVolume(4, FromStr::from_str(value)?), "FuelVolume5" => Property::FuelVolume(5, FromStr::from_str(value)?), "FuelVolume6" => Property::FuelVolume(6, FromStr::from_str(value)?), "FuelVolume7" => Property::FuelVolume(7, FromStr::from_str(value)?), "FuelVolume8" => Property::FuelVolume(8, FromStr::from_str(value)?), "FuelVolume9" => Property::FuelVolume(9, FromStr::from_str(value)?), "FuelFlowWeight" => Property::FuelFlowWeight(0, FromStr::from_str(value)?), "FuelFlowWeight2" => Property::FuelFlowWeight(1, FromStr::from_str(value)?), "FuelFlowWeight3" => Property::FuelFlowWeight(2, FromStr::from_str(value)?), "FuelFlowWeight4" => Property::FuelFlowWeight(3, FromStr::from_str(value)?), "FuelFlowWeight5" => Property::FuelFlowWeight(4, FromStr::from_str(value)?), "FuelFlowWeight6" => Property::FuelFlowWeight(5, FromStr::from_str(value)?), "FuelFlowWeight7" => Property::FuelFlowWeight(6, FromStr::from_str(value)?), "FuelFlowWeight8" => Property::FuelFlowWeight(7, FromStr::from_str(value)?), "FuelFlowVolume" => Property::FuelFlowVolume(0, FromStr::from_str(value)?), "FuelFlowVolume2" => Property::FuelFlowVolume(1, FromStr::from_str(value)?), "FuelFlowVolume3" => Property::FuelFlowVolume(2, FromStr::from_str(value)?), "FuelFlowVolume4" => Property::FuelFlowVolume(3, FromStr::from_str(value)?), "FuelFlowVolume5" => Property::FuelFlowVolume(4, FromStr::from_str(value)?), "FuelFlowVolume6" => Property::FuelFlowVolume(5, FromStr::from_str(value)?), "FuelFlowVolume7" => Property::FuelFlowVolume(6, FromStr::from_str(value)?), "FuelFlowVolume8" => Property::FuelFlowVolume(7, FromStr::from_str(value)?), "RadarMode" => Property::RadarMode(FromStr::from_str(value)?), "RadarAzimuth" => Property::RadarAzimuth(FromStr::from_str(value)?), "RadarElevation" => Property::RadarElevation(FromStr::from_str(value)?), "RadarRoll" => Property::RadarRoll(FromStr::from_str(value)?), "RadarRange" => Property::RadarRange(FromStr::from_str(value)?), "RadarHorizontalBeamwidth" => { Property::RadarHorizontalBeamwidth(FromStr::from_str(value)?) } "RadarVerticalBeamwidth" => Property::RadarVerticalBeamwidth(FromStr::from_str(value)?), "LockedTargetMode" => Property::LockedTargetMode(FromStr::from_str(value)?), "LockedTargetAzimuth" => Property::LockedTargetAzimuth(FromStr::from_str(value)?), "LockedTargetElevation" => Property::LockedTargetElevation(FromStr::from_str(value)?), "LockedTargetRange" => Property::LockedTargetRange(FromStr::from_str(value)?), "EngagementMode" => Property::EngagementMode(FromStr::from_str(value)?), "EngagementMode2" => Property::EngagementMode2(FromStr::from_str(value)?), "EngagementRange" => Property::EngagementRange(FromStr::from_str(value)?), "EngagementRange2" => Property::EngagementRange2(FromStr::from_str(value)?), "VerticalEngagementRange" => { Property::VerticalEngagementRange(FromStr::from_str(value)?) } "VerticalEngagementRange2" => { Property::VerticalEngagementRange2(FromStr::from_str(value)?) } "RollControlInput" => Property::RollControlInput(FromStr::from_str(value)?), "PitchControlInput" => Property::PitchControlInput(FromStr::from_str(value)?), "YawControlInput" => Property::YawControlInput(FromStr::from_str(value)?), "RollControlPosition" => Property::RollControlPosition(FromStr::from_str(value)?), "PitchControlPosition" => Property::PitchControlPosition(FromStr::from_str(value)?), "YawControlPosition" => Property::YawControlPosition(FromStr::from_str(value)?), "RollTrimTab" => Property::RollTrimTab(FromStr::from_str(value)?), "PitchTrimTab" => Property::PitchTrimTab(FromStr::from_str(value)?), "YawTrimTab" => Property::YawTrimTab(FromStr::from_str(value)?), "AileronLeft" => Property::AileronLeft(FromStr::from_str(value)?), "AileronRight" => Property::AileronRight(FromStr::from_str(value)?), "Elevator" => Property::Elevator(FromStr::from_str(value)?), "Rudder" => Property::Rudder(FromStr::from_str(value)?), "PilotHeadRoll" => Property::PilotHeadRoll(FromStr::from_str(value)?), "PilotHeadPitch" => Property::PilotHeadPitch(FromStr::from_str(value)?), "PilotHeadYaw" => Property::PilotHeadYaw(FromStr::from_str(value)?), "VerticalGForce" => Property::VerticalGForce(FromStr::from_str(value)?), "LongitudinalGForce" => Property::LongitudinalGForce(FromStr::from_str(value)?), "LateralGForce" => Property::LateralGForce(FromStr::from_str(value)?), "ENL" => Property::ENL(FromStr::from_str(value)?), name => Self::Unknown(name.to_string(), value.to_string()), }) } } impl Display for Property { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use Property::*; match self { T(v) => write!(f, "T={v}"), Name(v) => write!(f, "Name={v}"), Type(v) => write!(f, "Type={}", join(v.iter().map(|v| v.as_str()), "+")), Parent(v) => write!(f, "Parent={v:x}"), Next(v) => write!(f, "Next={v:x}"), CallSign(v) => write!(f, "CallSign={v}"), Registration(v) => write!(f, "Registration={v}"), Squawk(v) => write!(f, "Squawk={v}"), ICAO24(v) => write!(f, "ICAO24={v}"), Pilot(v) => write!(f, "Pilot={v}"), Group(v) => write!(f, "Group={v}"), Country(v) => write!(f, "Country={v}"), Coalition(v) => write!(f, "Coalition={v}"), Color(v) => write!(f, "Color={}", v.as_str()), Shape(v) => write!(f, "Shape={v}"), Debug(v) => write!(f, "Debug={v}"), Label(v) => write!(f, "Label={v}"), FocusedTarget(v) => write!(f, "FocusedTarget={v:x}"), LockedTarget(v) => write!(f, "LockedTarget={v:x}"), Importance(v) => write!(f, "Importance={v}"), Slot(v) => write!(f, "Slot={v}"), Disabled(v) => write!(f, "Disabled={}", *v as i32), Visible(v) => write!(f, "Visible={}", *v as i32), Health(v) => write!(f, "Health={v}"), Length(v) => write!(f, "Length={v}"), Width(v) => write!(f, "Width={v}"), Height(v) => write!(f, "Height={v}"), Radius(v) => write!(f, "Radius={v}"), IAS(v) => write!(f, "IAS={v}"), CAS(v) => write!(f, "CAS={v}"), TAS(v) => write!(f, "TAS={v}"), Mach(v) => write!(f, "Mach={v}"), AOA(v) => write!(f, "AOA={}", v.max_precision(2)), AOS(v) => write!(f, "AOS={v}"), AGL(v) => write!(f, "AGL={v}"), HDG(v) => write!(f, "HDG={v}"), HDM(v) => write!(f, "HDM={v}"), Throttle(v) => write!(f, "Throttle={v}"), Throttle2(v) => write!(f, "Throttle2={v}"), Afterburner(v) => write!(f, "Afterburner={v}"), AirBrakes(v) => write!(f, "AirBrakes={v}"), Flaps(v) => write!(f, "Flaps={v}"), LandingGear(v) => write!(f, "LandingGear={v}"), LandingGearHandle(v) => write!(f, "LandingGearHandle={v}"), Tailhook(v) => write!(f, "Tailhook={v}"), Parachute(v) => write!(f, "Parachute={v}"), DragChute(v) => write!(f, "DragChute={v}"), FuelWeight(i, v) => write!(f, "FuelWeight{}={}", to_index(*i), v), FuelVolume(i, v) => write!(f, "FuelVolume{}={}", to_index(*i), v), FuelFlowWeight(i, v) => write!(f, "FuelFlowWeight{}={}", to_index(*i), v), FuelFlowVolume(i, v) => write!(f, "FuelFlowVolume{}={}", to_index(*i), v), RadarMode(v) => write!(f, "RadarMode={v}"), RadarAzimuth(v) => write!(f, "RadarAzimuth={v}"), RadarElevation(v) => write!(f, "RadarElevation={v}"), RadarRoll(v) => write!(f, "RadarRoll={v}"), RadarRange(v) => write!(f, "RadarRange={v}"), RadarHorizontalBeamwidth(v) => write!(f, "RadarHorizontalBeamwidth={v}"), RadarVerticalBeamwidth(v) => write!(f, "RadarVerticalBeamwidth={v}"), LockedTargetMode(v) => write!(f, "LockedTargetMode={v}"), LockedTargetAzimuth(v) => write!(f, "LockedTargetAzimuth={v}"), LockedTargetElevation(v) => write!(f, "LockedTargetElevation={v}"), LockedTargetRange(v) => write!(f, "LockedTargetRange={v}"), EngagementMode(v) => write!(f, "EngagementMode={v}"), EngagementMode2(v) => write!(f, "EngagementMode2={v}"), EngagementRange(v) => write!(f, "EngagementRange={v}"), EngagementRange2(v) => write!(f, "EngagementRange2={v}"), VerticalEngagementRange(v) => write!(f, "VerticalEngagementRange={v}"), VerticalEngagementRange2(v) => write!(f, "VerticalEngagementRange2={v}"), RollControlInput(v) => write!(f, "RollControlInput={v}"), PitchControlInput(v) => write!(f, "PitchControlInput={v}"), YawControlInput(v) => write!(f, "YawControlInput={v}"), RollControlPosition(v) => write!(f, "RollControlPosition={v}"), PitchControlPosition(v) => write!(f, "PitchControlPosition={v}"), YawControlPosition(v) => write!(f, "YawControlPosition={v}"), RollTrimTab(v) => write!(f, "RollTrimTab={v}"), PitchTrimTab(v) => write!(f, "PitchTrimTab={v}"), YawTrimTab(v) => write!(f, "YawTrimTab={v}"), AileronLeft(v) => write!(f, "AileronLeft={v}"), AileronRight(v) => write!(f, "AileronRight={v}"), Elevator(v) => write!(f, "Elevator={v}"), Rudder(v) => write!(f, "Rudder={v}"), PilotHeadRoll(v) => write!(f, "PilotHeadRoll={v}"), PilotHeadPitch(v) => write!(f, "PilotHeadPitch={v}"), PilotHeadYaw(v) => write!(f, "PilotHeadYaw={v}"), VerticalGForce(v) => write!(f, "VerticalGForce={v}"), LongitudinalGForce(v) => write!(f, "LongitudinalGForce={v}"), LateralGForce(v) => write!(f, "LateralGForce={v}"), ENL(v) => write!(f, "ENL={v}"), Unknown(k, v) => write!(f, "{k}={v}"), } } } impl<'a> From<&'a str> for Color { fn from(s: &str) -> Self { match s { "Red" => Self::Red, "Orange" => Self::Orange, "Green" => Self::Green, "Blue" => Self::Blue, "Violet" => Self::Violet, color => Self::Unknown(color.to_string()), } } } impl Color { fn as_str(&self) -> &str { use Color::*; match self { Red => "Red", Orange => "Orange", Green => "Green", Blue => "Blue", Violet => "Violet", Grey => "Grey", Unknown(color) => color, } } } impl<'a> From<&'a str> for Tag { fn from(s: &str) -> Self { match s { "Air" => Self::Air, "Ground" => Self::Ground, "Sea" => Self::Sea, "Weapon" => Self::Weapon, "Sensor" => Self::Sensor, "Navaid" => Self::Navaid, "Misc" => Self::Misc, "Static" => Self::Static, "Heavy" => Self::Heavy, "Medium" => Self::Medium, "Light" => Self::Light, "Minor" => Self::Minor, "FixedWing" => Self::FixedWing, "Rotorcraft" => Self::Rotorcraft, "Armor" => Self::Armor, "AntiAircraft" => Self::AntiAircraft, "Vehicle" => Self::Vehicle, "Watercraft" => Self::Watercraft, "Human" => Self::Human, "Biologic" => Self::Biologic, "Missile" => Self::Missile, "Rocket" => Self::Rocket, "Bomb" => Self::Bomb, "Torpedo" => Self::Torpedo, "Projectile" => Self::Projectile, "Beam" => Self::Beam, "Decoy" => Self::Decoy, "Building" => Self::Building, "Bullseye" => Self::Bullseye, "Waypoint" => Self::Waypoint, "Tank" => Self::Tank, "Warship" => Self::Warship, "AircraftCarrier" => Self::AircraftCarrier, "Submarine" => Self::Submarine, "Infantry" => Self::Infantry, "Parachutist" => Self::Parachutist, "Shell" => Self::Shell, "Bullet" => Self::Bullet, "Flare" => Self::Flare, "Chaff" => Self::Chaff, "SmokeGrenade" => Self::SmokeGrenade, "Aerodrome" => Self::Aerodrome, "Container" => Self::Container, "Shrapnel" => Self::Shrapnel, tag => Self::Unknown(tag.to_string()), } } } impl Tag { fn as_str(&self) -> &str { use Tag::*; match self { Air => "Air", Ground => "Ground", Sea => "Sea", Weapon => "Weapon", Sensor => "Sensor", Navaid => "Navaid", Misc => "Misc", Static => "Static", Heavy => "Heavy", Medium => "Medium", Light => "Light", Minor => "Minor", FixedWing => "FixedWing", Rotorcraft => "Rotorcraft", Armor => "Armor", AntiAircraft => "AntiAircraft", Vehicle => "Vehicle", Watercraft => "Watercraft", Human => "Human", Biologic => "Biologic", Missile => "Missile", Rocket => "Rocket", Bomb => "Bomb", Torpedo => "Torpedo", Projectile => "Projectile", Beam => "Beam", Decoy => "Decoy", Building => "Building", Bullseye => "Bullseye", Waypoint => "Waypoint", Tank => "Tank", Warship => "Warship", AircraftCarrier => "AircraftCarrier", Submarine => "Submarine", Infantry => "Infantry", Parachutist => "Parachutist", Shell => "Shell", Bullet => "Bullet", Flare => "Flare", Chaff => "Chaff", SmokeGrenade => "SmokeGrenade", Aerodrome => "Aerodrome", Container => "Container", Shrapnel => "Shrapnel", Unknown(tag) => tag, } } } impl FromStr for Coords { type Err = ParseError; fn from_str(s: &str) -> Result { let parts = s.split('|').collect::>(); let mut coords = Coords::default(); match &parts[..] { [longitude, latitude, altitude] => { if !longitude.is_empty() { coords.longitude = Some(f64::from_str(longitude)?); } if !latitude.is_empty() { coords.latitude = Some(f64::from_str(latitude)?); } if !altitude.is_empty() { coords.altitude = Some(f64::from_str(altitude)?); } } [longitude, latitude, altitude, u, v] => { if !longitude.is_empty() { coords.longitude = Some(f64::from_str(longitude)?); } if !latitude.is_empty() { coords.latitude = Some(f64::from_str(latitude)?); } if !altitude.is_empty() { coords.altitude = Some(f64::from_str(altitude)?); } if !u.is_empty() { coords.u = Some(f64::from_str(u)?); } if !v.is_empty() { coords.v = Some(f64::from_str(v)?); } } [longitude, latitude, altitude, roll, pitch, yaw] => { if !longitude.is_empty() { coords.longitude = Some(f64::from_str(longitude)?); } if !latitude.is_empty() { coords.latitude = Some(f64::from_str(latitude)?); } if !altitude.is_empty() { coords.altitude = Some(f64::from_str(altitude)?); } if !roll.is_empty() { coords.roll = Some(f64::from_str(roll)?); } if !pitch.is_empty() { coords.pitch = Some(f64::from_str(pitch)?); } if !yaw.is_empty() { coords.yaw = Some(f64::from_str(yaw)?); } } [longitude, latitude, altitude, roll, pitch, yaw, u, v, heading] => { if !longitude.is_empty() { coords.longitude = Some(f64::from_str(longitude)?); } if !latitude.is_empty() { coords.latitude = Some(f64::from_str(latitude)?); } if !altitude.is_empty() { coords.altitude = Some(f64::from_str(altitude)?); } if !roll.is_empty() { coords.roll = Some(f64::from_str(roll)?); } if !pitch.is_empty() { coords.pitch = Some(f64::from_str(pitch)?); } if !yaw.is_empty() { coords.yaw = Some(f64::from_str(yaw)?); } if !u.is_empty() { coords.u = Some(f64::from_str(u)?); } if !v.is_empty() { coords.v = Some(f64::from_str(v)?); } if !heading.is_empty() { coords.heading = Some(f64::from_str(heading)?); } } _ => return Err(ParseError::InvalidCoordinateFormat), } Ok(coords) } } impl Display for Coords { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let has_orientation = self.yaw.is_some() || self.pitch.is_some() || self.roll.is_some(); let has_uv = self.u.is_some() || self.v.is_some(); if self.heading.is_some() || (has_orientation && has_uv) { write!( f, "{}|{}|{}|{}|{}|{}|{}|{}|{}", NoneAsEmpty(self.longitude.max_precision(7)), NoneAsEmpty(self.latitude.max_precision(7)), NoneAsEmpty(self.altitude.max_precision(2)), NoneAsEmpty(self.roll.max_precision(1)), NoneAsEmpty(self.pitch.max_precision(1)), NoneAsEmpty(self.yaw.max_precision(1)), NoneAsEmpty(self.u.max_precision(2)), NoneAsEmpty(self.v.max_precision(2)), NoneAsEmpty(self.heading.max_precision(1)) ) } else if has_orientation { write!( f, "{}|{}|{}|{}|{}|{}", NoneAsEmpty(self.longitude.max_precision(7)), NoneAsEmpty(self.latitude.max_precision(7)), NoneAsEmpty(self.altitude.max_precision(2)), NoneAsEmpty(self.roll.max_precision(1)), NoneAsEmpty(self.pitch.max_precision(1)), NoneAsEmpty(self.yaw.max_precision(1)), ) } else if has_uv { write!( f, "{}|{}|{}|{}|{}", NoneAsEmpty(self.longitude.max_precision(7)), NoneAsEmpty(self.latitude.max_precision(7)), NoneAsEmpty(self.altitude.max_precision(2)), NoneAsEmpty(self.u.max_precision(2)), NoneAsEmpty(self.v.max_precision(2)), ) } else { write!( f, "{}|{}|{}", NoneAsEmpty(self.longitude.max_precision(7)), NoneAsEmpty(self.latitude.max_precision(7)), NoneAsEmpty(self.altitude.max_precision(2)), ) } } } fn join<'a>(iter: impl Iterator, sep: &'a str) -> String { iter.fold(String::new(), |mut acc, v| { if !acc.is_empty() { acc += sep; } acc + v }) } struct NoneAsEmpty(Option); impl Display for NoneAsEmpty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(v) = &self.0 { v.fmt(f) } else { Ok(()) } } } fn to_index(i: u8) -> Cow<'static, str> { match i { 0 => Cow::Borrowed(""), 1 => Cow::Borrowed("2"), 2 => Cow::Borrowed("3"), 3 => Cow::Borrowed("4"), 4 => Cow::Borrowed("5"), 5 => Cow::Borrowed("6"), 6 => Cow::Borrowed("7"), 7 => Cow::Borrowed("8"), 8 => Cow::Borrowed("9"), i => Cow::Owned((i + 1).to_string()), } }