nil-core 0.5.3

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

use crate::continent::Coord;
use crate::error::{Error, Result};
use crate::infrastructure::building::{BuildingId, BuildingLevel, BuildingStatsTable};
use crate::infrastructure::queue::{InfrastructureQueue, InfrastructureQueueOrder};
use crate::resources::Resources;
use crate::resources::workforce::Workforce;
use bon::Builder;
use nil_num::mul_ceil::MulCeil;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use strum::EnumIs;
use uuid::Uuid;

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PrefectureBuildQueue {
  #[cfg_attr(feature = "typescript", ts(as = "Vec<PrefectureBuildOrder>"))]
  orders: VecDeque<PrefectureBuildOrder>,
}

impl PrefectureBuildQueue {
  pub(crate) fn build(
    &mut self,
    request: &PrefectureBuildOrderRequest,
    table: &BuildingStatsTable,
    current_level: BuildingLevel,
    current_resources: Option<&Resources>,
  ) -> Result<&PrefectureBuildOrder> {
    let id = table.id();
    let mut target_level = self.resolve_level(id, current_level);

    let kind = request.kind;
    if kind.is_demolition() && target_level <= table.min_level() {
      return Err(Error::CannotDecreaseBuildingLevel(id));
    } else if kind.is_construction() && target_level >= table.max_level() {
      return Err(Error::CannotIncreaseBuildingLevel(id));
    }

    target_level += match kind {
      PrefectureBuildOrderKind::Construction => 1i8,
      PrefectureBuildOrderKind::Demolition => -1i8,
    };

    let resources = table.get(target_level)?.resources.clone();
    if let PrefectureBuildOrderKind::Construction = kind
      && let Some(current_resources) = current_resources
      && current_resources
        .checked_sub(&resources)
        .is_none()
    {
      return Err(Error::InsufficientResources);
    }

    let mut workforce = table.get(target_level)?.workforce;
    kind.apply_modifier(&mut workforce);

    self.orders.push_back(PrefectureBuildOrder {
      id: PrefectureBuildOrderId::new(),
      kind,
      building: id,
      level: target_level,
      resources,
      workforce,
      state: PrefectureBuildOrderState::new(workforce),
    });

    let len = self.orders.len();
    Ok(unsafe {
      self
        .orders
        .get(len.unchecked_sub(1))
        .unwrap_unchecked()
    })
  }

  /// Cancels the last build order in the queue.
  #[must_use]
  pub(crate) fn cancel(&mut self) -> Option<PrefectureBuildOrder> {
    self.orders.pop_back()
  }

  pub fn resolve_level(&self, building: BuildingId, current_level: BuildingLevel) -> BuildingLevel {
    self
      .iter()
      .filter(|order| order.building() == building)
      .fold(current_level, |acc, order| {
        match order.kind() {
          PrefectureBuildOrderKind::Construction => acc + 1u8,
          PrefectureBuildOrderKind::Demolition => acc - 1u8,
        }
      })
  }
}

impl InfrastructureQueue<PrefectureBuildOrder> for PrefectureBuildQueue {
  fn queue(&self) -> &VecDeque<PrefectureBuildOrder> {
    &self.orders
  }

  fn queue_mut(&mut self) -> &mut VecDeque<PrefectureBuildOrder> {
    &mut self.orders
  }
}

#[must_use]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PrefectureBuildOrder {
  id: PrefectureBuildOrderId,
  kind: PrefectureBuildOrderKind,
  building: BuildingId,
  level: BuildingLevel,
  resources: Resources,
  workforce: Workforce,
  state: PrefectureBuildOrderState,
}

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

  #[inline]
  pub fn kind(&self) -> PrefectureBuildOrderKind {
    self.kind
  }

  #[inline]
  pub fn building(&self) -> BuildingId {
    self.building
  }

  #[inline]
  pub fn resources(&self) -> &Resources {
    &self.resources
  }
}

impl InfrastructureQueueOrder for PrefectureBuildOrder {
  fn is_done(&self) -> bool {
    self.state.is_done()
  }

  fn set_done(&mut self) {
    self.state = PrefectureBuildOrderState::Done;
  }

  fn pending_workforce(&self) -> Option<Workforce> {
    self.state.pending_workforce()
  }

  fn pending_workforce_mut(&mut self) -> Option<&mut Workforce> {
    self.state.pending_workforce_mut()
  }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PrefectureBuildOrderId(Uuid);

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

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

#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, EnumIs)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub enum PrefectureBuildOrderKind {
  Construction,
  Demolition,
}

impl PrefectureBuildOrderKind {
  fn apply_modifier(self, workforce: &mut Workforce) {
    if let Self::Demolition = self {
      *workforce = workforce.mul_ceil(0.5).into();
    }
  }
}

#[derive(Clone, Debug, EnumIs, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub enum PrefectureBuildOrderState {
  Pending { workforce: Workforce },
  Done,
}

impl PrefectureBuildOrderState {
  fn pending_workforce(&self) -> Option<Workforce> {
    if let Self::Pending { workforce } = self { Some(*workforce) } else { None }
  }

  fn pending_workforce_mut(&mut self) -> Option<&mut Workforce> {
    if let Self::Pending { workforce } = self { Some(workforce) } else { None }
  }
}

impl PrefectureBuildOrderState {
  fn new(workforce: Workforce) -> Self {
    Self::Pending { workforce }
  }
}

#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct PrefectureBuildOrderRequest {
  #[builder(into)]
  pub coord: Coord,
  #[builder(into)]
  pub building: BuildingId,
  pub kind: PrefectureBuildOrderKind,
}