nil-core 0.5.5

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

mod behavior;
mod maneuver;

use crate::error::Result;
use crate::player::{Player, PlayerId};
use crate::resources::prelude::*;
use crate::round::Round;
use crate::ruler::Ruler;
use crate::world::World;
use std::collections::HashMap;

impl World {
  #[inline]
  pub fn round(&self) -> &Round {
    &self.round
  }

  pub fn start_round(&mut self) -> Result<()> {
    let ids = self
      .player_manager
      .active_players()
      .map(Player::id);

    self.round.start(ids)?;
    self.emit_round_updated()?;

    Ok(())
  }

  pub fn set_player_ready(&mut self, player: &PlayerId, is_ready: bool) -> Result<()> {
    self.round.set_ready(player, is_ready);

    if self.round.is_done() {
      self.next_round(true)?;
    } else {
      self.emit_round_updated()?;
    }

    Ok(())
  }

  /// Forcefully ends the current round.
  pub fn dangerously_end_round(&mut self, emit: bool) -> Result<()> {
    self.round.dangerously_set_done();
    self.next_round(emit)?;
    Ok(())
  }

  pub(super) fn next_round(&mut self, emit: bool) -> Result<()> {
    let ids = self
      .player_manager
      .active_players()
      .map(Player::id);

    self.round.next(ids)?;
    self.prepare_next_round()?;
    self.consume_pending_save()?;

    if emit {
      self.emit_round_updated()?;
    }

    if let Some(on_next_round) = self.on_next_round.clone() {
      on_next_round.call(self);
    }

    Ok(())
  }

  fn prepare_next_round(&mut self) -> Result<()> {
    self.update_resources()?;
    self.process_city_queues();
    self.collapse_armies();
    self.process_maneuvers()?;
    self.update_ranking()?;
    self.process_npc_behavior()?;
    self.report_manager.prune();
    Ok(())
  }

  /// Updates all rulers' resources by increasing them with the amount generated
  /// in the current round and then deducting all maintenance-related costs.
  fn update_resources(&mut self) -> Result<()> {
    let stats = self.stats.infrastructure.as_ref();
    let mut diff: HashMap<Ruler, ResourcesDiff> = HashMap::new();

    for city in self.continent.cities() {
      let owner = city.owner().clone();
      let resources = diff.entry(owner).or_default();
      *resources += city.round_production(stats)?;
      resources.food -= city.maintenance(stats)?;
    }

    for (ruler, mut resources) in diff {
      resources.food -= self.military.maintenance_of(ruler.clone());
      let capacity = self.get_storage_capacity(ruler.clone())?;
      self
        .ruler_mut(&ruler)?
        .resources_mut()
        .add_within_capacity(&resources, &capacity);
    }

    Ok(())
  }

  /// Processes the build and recruitment queues for all cities.
  fn process_city_queues(&mut self) {
    let config = self.config();
    for city in self.continent.cities_mut() {
      let coord = city.coord();
      let owner = city.owner().clone();
      let infrastructure = city.infrastructure_mut();

      infrastructure.process_prefecture_build_queue(&config);

      macro_rules! process_recruit_queue {
        ($building:ident) => {
          paste::paste! {
            if let Some(personnel) = infrastructure.[<process_ $building:snake _recruit_queue>](&config) {
              self.military.spawn(coord, owner.clone(), personnel);
            }
          }
        };
      }

      process_recruit_queue!(Academy);
      process_recruit_queue!(Stable);
      process_recruit_queue!(Workshop);
    }
  }
}