nil-core 0.5.5

Multiplayer strategy game
Documentation
// Copyright (C) Call of Nil contributors
// SPDX-License-Identifier: AGPL-3.0-only

use crate::error::AnyResult;
use crate::world::WorldOptions;
use bon::Builder;
use derive_more::Deref;
use nil_util::{ConstDeref, F64Math};
use serde::{Deserialize, Serialize};
use strum::EnumString;
use uuid::Uuid;

#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorldConfig {
  #[builder(start_fn, into)]
  name: WorldName,

  #[builder(skip)]
  id: WorldId,

  #[serde(default)]
  #[builder(default)]
  locale: Locale,

  #[serde(default)]
  #[builder(default)]
  allow_cheats: bool,

  #[serde(default)]
  #[builder(default, into)]
  speed: WorldSpeed,

  #[serde(default)]
  #[builder(default, into)]
  unit_speed: WorldUnitSpeed,

  #[serde(default)]
  #[builder(default, into)]
  bot_density: BotDensity,

  #[serde(default)]
  #[builder(default, into)]
  bot_advanced_start_ratio: BotAdvancedStartRatio,
}

impl WorldConfig {
  pub fn new(options: &WorldOptions) -> Self {
    Self {
      id: WorldId::new(),
      name: options.name.clone(),
      locale: options.locale,
      allow_cheats: options.allow_cheats,
      speed: options.speed,
      unit_speed: options.unit_speed,
      bot_density: options.bot_density,
      bot_advanced_start_ratio: options.bot_advanced_start_ratio,
    }
  }

  #[inline]
  pub fn id(&self) -> WorldId {
    self.id
  }

  #[inline]
  pub fn name(&self) -> WorldName {
    self.name.clone()
  }

  #[inline]
  pub fn locale(&self) -> Locale {
    self.locale
  }

  #[inline]
  pub fn are_cheats_allowed(&self) -> bool {
    self.allow_cheats
  }

  #[inline]
  pub fn speed(&self) -> WorldSpeed {
    self.speed
  }

  #[inline]
  pub fn unit_speed(&self) -> WorldUnitSpeed {
    self.unit_speed
  }

  #[inline]
  pub fn bot_density(&self) -> BotDensity {
    self.bot_density
  }

  #[inline]
  pub fn bot_advanced_start_ratio(&self) -> BotAdvancedStartRatio {
    self.bot_advanced_start_ratio
  }
}

#[derive(
  Clone,
  Copy,
  Debug,
  Deref,
  derive_more::Display,
  PartialEq,
  Eq,
  PartialOrd,
  Ord,
  Hash,
  Deserialize,
  Serialize,
)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorldId(Uuid);

impl WorldId {
  #[must_use]
  pub fn new() -> Self {
    Self(Uuid::now_v7())
  }
}

impl Default for WorldId {
  fn default() -> Self {
    Self::new()
  }
}

impl TryFrom<&str> for WorldId {
  type Error = anyhow::Error;

  fn try_from(value: &str) -> AnyResult<Self> {
    Ok(Self(Uuid::try_parse(value)?))
  }
}

#[derive(Clone, Debug, derive_more::Display, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorldName(Box<str>);

impl<T: AsRef<str>> From<T> for WorldName {
  fn from(value: T) -> Self {
    Self(Box::from(value.as_ref()))
  }
}

#[derive(Copy, Debug, strum::Display, EnumString, Deserialize, Serialize)]
#[derive_const(Clone, Default)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub enum Locale {
  #[default]
  #[serde(rename = "en-US")]
  #[strum(serialize = "en-US")]
  English,

  #[serde(rename = "pt-BR")]
  #[strum(serialize = "pt-BR")]
  Portuguese,
}

macro_rules! impl_f64_newtype {
  ($name:ident, min = $min:expr, max = $max:expr) => {
    impl $name {
      pub const MIN: Self = $name($min);
      pub const MAX: Self = $name($max);

      #[inline]
      pub const fn new(value: f64) -> Self {
        debug_assert!(value.is_finite());
        debug_assert!(!value.is_subnormal());
        Self(value.clamp(Self::MIN.0, Self::MAX.0))
      }
    }

    impl const From<f64> for $name {
      fn from(value: f64) -> Self {
        Self::new(value)
      }
    }

    impl const From<$name> for f64 {
      fn from(value: $name) -> Self {
        value.0
      }
    }
  };
  ($name:ident, min = $min:expr, max = $max:expr, default = $default:expr) => {
    impl_f64_newtype!($name, min = $min, max = $max);

    impl const Default for $name {
      fn default() -> Self {
        Self::new($default)
      }
    }
  };
}

#[derive(Copy, Debug, derive_more::Display, Deserialize, Serialize, ConstDeref, F64Math)]
#[derive_const(Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorldSpeed(f64);

impl_f64_newtype!(WorldSpeed, min = 0.1, max = 10.0, default = 1.0);

#[derive(Copy, Debug, derive_more::Display, Deserialize, Serialize, ConstDeref, F64Math)]
#[derive_const(Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct WorldUnitSpeed(f64);

impl_f64_newtype!(WorldUnitSpeed, min = 0.1, max = 10.0, default = 1.0);

#[derive(Copy, Debug, derive_more::Display, Deserialize, Serialize, ConstDeref, F64Math)]
#[derive_const(Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct BotDensity(f64);

impl_f64_newtype!(
  BotDensity,
  min = 0.0,
  max = 3.0,
  default = if cfg!(target_os = "android") { 1.0 } else { 2.0 }
);

/// Proportion of bots that will have an advanced start with higher level infrastructure.
#[derive(Copy, Debug, derive_more::Display, Deserialize, Serialize, ConstDeref, F64Math)]
#[derive_const(Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct BotAdvancedStartRatio(f64);

impl_f64_newtype!(BotAdvancedStartRatio, min = 0.0, max = 1.0, default = 0.2);