Skip to main content

nil_core/world/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod battle;
5mod chat;
6mod city;
7mod continent;
8mod event;
9mod infrastructure;
10mod market;
11mod military;
12mod npc;
13mod player;
14mod ranking;
15mod report;
16mod resources;
17mod round;
18mod savedata;
19
20pub mod cheat;
21pub mod config;
22pub mod stats;
23
24use crate::chat::Chat;
25use crate::continent::Continent;
26use crate::continent::size::ContinentSize;
27use crate::error::{Error, Result};
28use crate::event::Emitter;
29use crate::hooks::OnNextRound;
30use crate::market::Market;
31use crate::market::fee::MarketFee;
32use crate::military::Military;
33use crate::npc::bot::BotManager;
34use crate::npc::precursor::PrecursorManager;
35use crate::player::PlayerManager;
36use crate::ranking::Ranking;
37use crate::round::Round;
38use crate::ruler::{Ruler, RulerRef, RulerRefMut};
39use crate::savedata::{SaveHandle, Savedata};
40use crate::world::config::{WorldSpeed, WorldUnitSpeed};
41use bon::Builder;
42use config::{BotAdvancedStartRatio, BotDensity, Locale, WorldConfig, WorldId, WorldName};
43use serde::{Deserialize, Serialize};
44use stats::WorldStats;
45use std::sync::Arc;
46
47#[derive(Debug)]
48pub struct World {
49  round: Round,
50  continent: Continent,
51  player_manager: PlayerManager,
52  bot_manager: BotManager,
53  precursor_manager: PrecursorManager,
54  military: Military,
55  market: Market,
56  ranking: Ranking,
57  chat: Chat,
58
59  config: Arc<WorldConfig>,
60  stats: WorldStats,
61
62  // These are not included in the savedata.
63  emitter: Emitter,
64  save_handle: Option<SaveHandle>,
65  on_next_round: Option<OnNextRound>,
66}
67
68impl World {
69  pub fn new(mut options: WorldOptions) -> Result<Self> {
70    WorldOptions::clamp(&mut options);
71    options.name = options.name.trim().into();
72
73    let config = WorldConfig::new(&options);
74    let stats = WorldStats::new(&config);
75    let continent = Continent::new(options.size.unwrap_or_default());
76    let precursor_manager = PrecursorManager::new(continent.size());
77    let military = Military::new(continent.size());
78    let market = Market::new(options.market_fee.unwrap_or_default());
79
80    let mut world = Self {
81      round: Round::default(),
82      continent,
83      player_manager: PlayerManager::default(),
84      bot_manager: BotManager::default(),
85      precursor_manager,
86      military,
87      market,
88      ranking: Ranking::default(),
89      config: Arc::new(config),
90      stats,
91      chat: Chat::default(),
92
93      emitter: Emitter::default(),
94      save_handle: None,
95      on_next_round: None,
96    };
97
98    world.spawn_precursors()?;
99    world.spawn_bots()?;
100    world.update_ranking()?;
101
102    Ok(world)
103  }
104
105  #[inline]
106  pub fn with_savedata(savedata: Savedata) -> Self {
107    Self::from(savedata)
108  }
109
110  pub fn load(bytes: &[u8]) -> Result<Self> {
111    let savedata = Savedata::read(bytes)?;
112    Ok(Self::with_savedata(savedata))
113  }
114
115  #[inline]
116  pub fn id(&self) -> WorldId {
117    self.config.id()
118  }
119
120  #[inline]
121  pub fn config(&self) -> Arc<WorldConfig> {
122    Arc::clone(&self.config)
123  }
124
125  #[inline]
126  pub fn stats(&self) -> WorldStats {
127    self.stats.clone()
128  }
129
130  pub fn ruler(&self, ruler: &Ruler) -> Result<RulerRef<'_>> {
131    let ruler = match ruler {
132      Ruler::Bot { id } => RulerRef::Bot(self.bot(id)?),
133      Ruler::Player { id } => RulerRef::Player(self.player(id)?),
134      Ruler::Precursor { id } => RulerRef::Precursor(self.precursor(*id)),
135    };
136
137    Ok(ruler)
138  }
139
140  fn ruler_mut(&mut self, ruler: &Ruler) -> Result<RulerRefMut<'_>> {
141    let ruler = match ruler {
142      Ruler::Bot { id } => RulerRefMut::Bot(self.bot_mut(id)?),
143      Ruler::Player { id } => RulerRefMut::Player(self.player_mut(id)?),
144      Ruler::Precursor { id } => RulerRefMut::Precursor(self.precursor_mut(*id)),
145    };
146
147    Ok(ruler)
148  }
149
150  pub fn rulers(&self) -> impl Iterator<Item = RulerRef<'_>> {
151    self
152      .players()
153      .map(RulerRef::from)
154      .chain(self.bots().map(RulerRef::from))
155      .chain(self.precursors().map(RulerRef::from))
156  }
157
158  /// Schedules a save to be performed at the end of the current round.
159  /// If a save is already scheduled, it will be overwritten.
160  pub fn save<F>(&mut self, f: F)
161  where
162    F: FnOnce(Vec<u8>) + Send + Sync + 'static,
163  {
164    self.save_handle = Some(SaveHandle::new(f));
165  }
166
167  /// Registers a hook to be called once a new round is about to start.
168  pub fn on_next_round<F>(&mut self, f: F)
169  where
170    F: Fn(&mut World) + Send + Sync + 'static,
171  {
172    self.on_next_round = Some(OnNextRound::new(f));
173  }
174}
175
176impl Drop for World {
177  fn drop(&mut self) {
178    let _ = self.emit_drop();
179  }
180}
181
182impl TryFrom<WorldOptions> for World {
183  type Error = Error;
184
185  fn try_from(options: WorldOptions) -> Result<Self> {
186    Self::new(options)
187  }
188}
189
190#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
191#[serde(rename_all = "camelCase")]
192#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
193#[cfg_attr(feature = "typescript", ts(optional_fields = nullable))]
194pub struct WorldOptions {
195  #[builder(start_fn, into)]
196  pub name: WorldName,
197
198  #[serde(default)]
199  #[builder(into)]
200  pub size: Option<ContinentSize>,
201
202  #[serde(default)]
203  pub locale: Option<Locale>,
204
205  #[serde(default)]
206  pub allow_cheats: Option<bool>,
207
208  #[serde(default)]
209  #[builder(into)]
210  pub speed: Option<WorldSpeed>,
211
212  #[serde(default)]
213  #[builder(into)]
214  pub unit_speed: Option<WorldUnitSpeed>,
215
216  #[serde(default)]
217  #[builder(into)]
218  pub bot_density: Option<BotDensity>,
219
220  #[serde(default)]
221  #[builder(into)]
222  pub bot_advanced_start_ratio: Option<BotAdvancedStartRatio>,
223
224  #[serde(default)]
225  #[builder(into)]
226  pub market_fee: Option<MarketFee>,
227}
228
229impl WorldOptions {
230  pub fn clamp(&mut self) {
231    macro_rules! clamp {
232      ($($field_ty:ident => $field:ident),* $(,)?) => {
233        $(
234          if let Some(value) = self.$field.as_mut() {
235            $field_ty::clamp(value);
236          }
237        )*
238      };
239    }
240
241    clamp!(
242      ContinentSize => size,
243      WorldSpeed => speed,
244      WorldUnitSpeed => unit_speed,
245      BotDensity => bot_density,
246      BotAdvancedStartRatio => bot_advanced_start_ratio,
247      MarketFee => market_fee,
248    );
249  }
250}