use std::fmt;
use std::ops::Add;
use std::ops::Mul;
use std::ops::Sub;
const BW_TO_METERS: f32 = 30.0;
const SHIP_TO_METERS: f32 = 15.0;
const WORLD_TO_METERS: f32 = SHIP_TO_METERS;
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Meters(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct BigWorldDistance(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct WorldDistance(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct ShipModelDistance(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Km(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Millimeters(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct MetersPerSecond(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Degrees(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Radians(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Kilograms(f32);
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Seconds(f32);
impl From<f32> for Meters {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<i32> for Meters {
fn from(v: i32) -> Self {
Self(v as f32)
}
}
impl From<f32> for BigWorldDistance {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<i32> for BigWorldDistance {
fn from(v: i32) -> Self {
Self(v as f32)
}
}
impl From<f32> for ShipModelDistance {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<f32> for WorldDistance {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<f32> for Km {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<i32> for Km {
fn from(v: i32) -> Self {
Self(v as f32)
}
}
impl From<f32> for Millimeters {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<i32> for Millimeters {
fn from(v: i32) -> Self {
Self(v as f32)
}
}
impl From<f32> for MetersPerSecond {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<i32> for MetersPerSecond {
fn from(v: i32) -> Self {
Self(v as f32)
}
}
impl From<f32> for Degrees {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<f32> for Radians {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<f32> for Kilograms {
fn from(v: f32) -> Self {
Self(v)
}
}
impl From<f32> for Seconds {
fn from(v: f32) -> Self {
Self(v)
}
}
impl Meters {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_bigworld(self) -> BigWorldDistance {
BigWorldDistance(self.0 / BW_TO_METERS)
}
pub fn to_ship_model(self) -> ShipModelDistance {
ShipModelDistance(self.0 / SHIP_TO_METERS)
}
pub fn to_km(self) -> Km {
Km(self.0 / 1000.0)
}
pub fn to_mm(self) -> Millimeters {
Millimeters(self.0 * 1000.0)
}
pub fn to_world(self) -> WorldDistance {
WorldDistance(self.0 / WORLD_TO_METERS)
}
}
impl BigWorldDistance {
pub fn value(self) -> f32 {
self.0
}
pub fn to_meters(self) -> Meters {
Meters(self.0 * BW_TO_METERS)
}
pub fn to_km(self) -> Km {
self.to_meters().to_km()
}
}
impl ShipModelDistance {
pub const ZERO: ShipModelDistance = ShipModelDistance(0.0);
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn max_zero(self) -> Self {
Self(self.0.max(0.0))
}
pub fn to_meters(self) -> Meters {
Meters(self.0 * SHIP_TO_METERS)
}
pub fn to_bigworld(self) -> BigWorldDistance {
self.to_meters().to_bigworld()
}
}
impl WorldDistance {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_meters(self) -> Meters {
Meters(self.0 * WORLD_TO_METERS)
}
pub fn to_km(self) -> Km {
self.to_meters().to_km()
}
}
impl Km {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_meters(self) -> Meters {
Meters(self.0 * 1000.0)
}
pub fn to_bigworld(self) -> BigWorldDistance {
self.to_meters().to_bigworld()
}
}
impl Millimeters {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_meters(self) -> Meters {
Meters(self.0 / 1000.0)
}
pub fn to_bigworld(self) -> BigWorldDistance {
self.to_meters().to_bigworld()
}
}
impl MetersPerSecond {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn travel_over(self, duration: Seconds) -> Meters {
Meters(self.0 * duration.0)
}
}
impl Degrees {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_radians(self) -> Radians {
Radians(self.0.to_radians())
}
pub fn abs(self) -> Self {
Self(self.0.abs())
}
}
impl Radians {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
pub fn to_degrees(self) -> Degrees {
Degrees(self.0.to_degrees())
}
pub fn abs(self) -> Self {
Self(self.0.abs())
}
pub fn cos(self) -> f32 {
self.0.cos()
}
pub fn sin(self) -> f32 {
self.0.sin()
}
pub fn max_zero(self) -> Self {
Self(self.0.max(0.0))
}
}
impl Kilograms {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
}
impl Seconds {
pub const fn new(v: f32) -> Self {
Self(v)
}
pub fn value(self) -> f32 {
self.0
}
}
impl fmt::Display for Meters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.0} m", self.0)
}
}
impl fmt::Display for Km {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1} km", self.0)
}
}
impl fmt::Display for Millimeters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.0} mm", self.0)
}
}
impl fmt::Display for MetersPerSecond {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.0} m/s", self.0)
}
}
impl fmt::Display for Degrees {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1} deg", self.0)
}
}
impl fmt::Display for Kilograms {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.0} kg", self.0)
}
}
impl fmt::Display for Seconds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1} s", self.0)
}
}
impl Add for Degrees {
type Output = Degrees;
fn add(self, rhs: Degrees) -> Degrees {
Degrees(self.0 + rhs.0)
}
}
impl Sub for Degrees {
type Output = Degrees;
fn sub(self, rhs: Degrees) -> Degrees {
Degrees(self.0 - rhs.0)
}
}
impl Add for Radians {
type Output = Radians;
fn add(self, rhs: Radians) -> Radians {
Radians(self.0 + rhs.0)
}
}
impl Sub for Radians {
type Output = Radians;
fn sub(self, rhs: Radians) -> Radians {
Radians(self.0 - rhs.0)
}
}
impl Add for Seconds {
type Output = Seconds;
fn add(self, rhs: Seconds) -> Seconds {
Seconds(self.0 + rhs.0)
}
}
impl Sub for Seconds {
type Output = Seconds;
fn sub(self, rhs: Seconds) -> Seconds {
Seconds(self.0 - rhs.0)
}
}
impl Mul<f32> for Seconds {
type Output = Seconds;
fn mul(self, rhs: f32) -> Seconds {
Seconds(self.0 * rhs)
}
}
impl Mul<f32> for Meters {
type Output = Meters;
fn mul(self, rhs: f32) -> Meters {
Meters(self.0 * rhs)
}
}
impl Mul<f32> for BigWorldDistance {
type Output = BigWorldDistance;
fn mul(self, rhs: f32) -> BigWorldDistance {
BigWorldDistance(self.0 * rhs)
}
}
impl Mul<f32> for Km {
type Output = Km;
fn mul(self, rhs: f32) -> Km {
Km(self.0 * rhs)
}
}
impl Mul<f32> for Millimeters {
type Output = Millimeters;
fn mul(self, rhs: f32) -> Millimeters {
Millimeters(self.0 * rhs)
}
}
impl Add for Meters {
type Output = Meters;
fn add(self, rhs: Meters) -> Meters {
Meters(self.0 + rhs.0)
}
}
impl Sub for Meters {
type Output = Meters;
fn sub(self, rhs: Meters) -> Meters {
Meters(self.0 - rhs.0)
}
}
impl Add for BigWorldDistance {
type Output = BigWorldDistance;
fn add(self, rhs: BigWorldDistance) -> BigWorldDistance {
BigWorldDistance(self.0 + rhs.0)
}
}
impl Sub for BigWorldDistance {
type Output = BigWorldDistance;
fn sub(self, rhs: BigWorldDistance) -> BigWorldDistance {
BigWorldDistance(self.0 - rhs.0)
}
}
impl Add for ShipModelDistance {
type Output = ShipModelDistance;
fn add(self, rhs: ShipModelDistance) -> ShipModelDistance {
ShipModelDistance(self.0 + rhs.0)
}
}
impl Sub for ShipModelDistance {
type Output = ShipModelDistance;
fn sub(self, rhs: ShipModelDistance) -> ShipModelDistance {
ShipModelDistance(self.0 - rhs.0)
}
}
impl Add for Km {
type Output = Km;
fn add(self, rhs: Km) -> Km {
Km(self.0 + rhs.0)
}
}
impl Sub for Km {
type Output = Km;
fn sub(self, rhs: Km) -> Km {
Km(self.0 - rhs.0)
}
}
impl Add for Millimeters {
type Output = Millimeters;
fn add(self, rhs: Millimeters) -> Millimeters {
Millimeters(self.0 + rhs.0)
}
}
impl Sub for Millimeters {
type Output = Millimeters;
fn sub(self, rhs: Millimeters) -> Millimeters {
Millimeters(self.0 - rhs.0)
}
}
impl Add<BigWorldDistance> for Meters {
type Output = Meters;
fn add(self, rhs: BigWorldDistance) -> Meters {
Meters(self.0 + rhs.to_meters().0)
}
}
impl Sub<BigWorldDistance> for Meters {
type Output = Meters;
fn sub(self, rhs: BigWorldDistance) -> Meters {
Meters(self.0 - rhs.to_meters().0)
}
}
impl Add<Meters> for BigWorldDistance {
type Output = BigWorldDistance;
fn add(self, rhs: Meters) -> BigWorldDistance {
BigWorldDistance(self.0 + rhs.to_bigworld().0)
}
}
impl Sub<Meters> for BigWorldDistance {
type Output = BigWorldDistance;
fn sub(self, rhs: Meters) -> BigWorldDistance {
BigWorldDistance(self.0 - rhs.to_bigworld().0)
}
}
impl Add<Km> for Meters {
type Output = Meters;
fn add(self, rhs: Km) -> Meters {
Meters(self.0 + rhs.to_meters().0)
}
}
impl Sub<Km> for Meters {
type Output = Meters;
fn sub(self, rhs: Km) -> Meters {
Meters(self.0 - rhs.to_meters().0)
}
}
impl Add<Meters> for Km {
type Output = Km;
fn add(self, rhs: Meters) -> Km {
Km(self.0 + rhs.to_km().0)
}
}
impl Sub<Meters> for Km {
type Output = Km;
fn sub(self, rhs: Meters) -> Km {
Km(self.0 - rhs.to_km().0)
}
}
impl std::ops::Div<f32> for Meters {
type Output = Meters;
fn div(self, rhs: f32) -> Meters {
Meters(self.0 / rhs)
}
}
impl std::ops::Div<f32> for BigWorldDistance {
type Output = BigWorldDistance;
fn div(self, rhs: f32) -> BigWorldDistance {
BigWorldDistance(self.0 / rhs)
}
}
impl std::ops::Div<f32> for Km {
type Output = Km;
fn div(self, rhs: f32) -> Km {
Km(self.0 / rhs)
}
}
impl std::ops::Div<f32> for Millimeters {
type Output = Millimeters;
fn div(self, rhs: f32) -> Millimeters {
Millimeters(self.0 / rhs)
}
}
impl std::iter::Sum for Meters {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Meters(iter.map(|m| m.0).sum())
}
}
impl std::iter::Sum for BigWorldDistance {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
BigWorldDistance(iter.map(|d| d.0).sum())
}
}
impl std::iter::Sum for Km {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Km(iter.map(|k| k.0).sum())
}
}
impl std::iter::Sum for Millimeters {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
Millimeters(iter.map(|m| m.0).sum())
}
}
impl PartialEq<BigWorldDistance> for Meters {
fn eq(&self, other: &BigWorldDistance) -> bool {
self.0 == other.to_meters().0
}
}
impl PartialOrd<BigWorldDistance> for Meters {
fn partial_cmp(&self, other: &BigWorldDistance) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.to_meters().0)
}
}
impl PartialEq<Meters> for BigWorldDistance {
fn eq(&self, other: &Meters) -> bool {
self.0 == other.to_bigworld().0
}
}
impl PartialOrd<Meters> for BigWorldDistance {
fn partial_cmp(&self, other: &Meters) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.to_bigworld().0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn meters_display_rounds_to_whole() {
assert_eq!(Meters::from(740.6).to_string(), "741 m");
}
#[test]
fn km_display_one_decimal() {
assert_eq!(Km::from(10.5).to_string(), "10.5 km");
}
#[test]
fn millimeters_display_rounds_to_whole() {
assert_eq!(Millimeters::from(406.0).to_string(), "406 mm");
}
#[test]
fn meters_per_second_display_rounds_to_whole() {
assert_eq!(MetersPerSecond::from(820.0).to_string(), "820 m/s");
}
#[test]
fn ship_model_units_are_fifteen_meters() {
assert_eq!(ShipModelDistance::from(6.489).to_meters().value(), 97.335);
assert_eq!(Meters::from(97.335).to_ship_model().value(), 6.489);
}
#[test]
fn replay_world_units_are_fifteen_meters() {
assert_eq!(WorldDistance::from(8.0).to_meters().value(), 120.0);
assert_eq!(Meters::from(120.0).to_world().value(), 8.0);
}
#[test]
fn game_params_and_replay_spaces_are_not_the_same() {
let one = 1.0f32;
assert_eq!(BigWorldDistance::from(one).to_meters().value(), 30.0);
assert_eq!(WorldDistance::from(one).to_meters().value(), 15.0);
}
#[test]
fn angles_round_trip_between_units() {
assert!((Degrees::from(60.0).to_radians().value() - std::f32::consts::FRAC_PI_3).abs() < 1e-6);
assert!((Radians::from(std::f32::consts::FRAC_PI_3).to_degrees().value() - 60.0).abs() < 1e-4);
}
#[test]
fn travel_over_multiplies_speed_by_time() {
assert!((MetersPerSecond::from(224.0).travel_over(Seconds::from(0.033)).value() - 7.392).abs() < 1e-3);
}
#[test]
fn game_params_distances_match_the_published_consumable_ranges() {
assert!((BigWorldDistance::from(133.3333).to_km().value() - 4.0).abs() < 0.001);
assert_eq!(BigWorldDistance::from(250.0).to_km().value(), 7.5);
}
}