Skip to main content

nil_core/npc/precursor/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod a;
5mod b;
6
7use crate::continent::{ContinentSize, Coord, Distance};
8use crate::ethic::Ethics;
9use crate::military::army::personnel::ArmyPersonnel;
10use crate::resources::Resources;
11use crate::resources::influence::Influence;
12use derive_more::Deref;
13use serde::{Deserialize, Serialize};
14use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
15
16pub use crate::npc::precursor::a::A;
17pub use crate::npc::precursor::b::B;
18
19pub const trait Precursor: Send + Sync {
20  fn id(&self) -> PrecursorId;
21  fn ethics(&self) -> &Ethics;
22  fn origin(&self) -> Coord;
23  fn resources(&self) -> &Resources;
24  fn resources_mut(&mut self) -> &mut Resources;
25  fn influence(&self) -> Influence;
26}
27
28#[derive(Clone, Debug, Deserialize, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct PrecursorManager {
31  a: A,
32  b: B,
33}
34
35impl PrecursorManager {
36  pub const fn new(size: ContinentSize) -> Self {
37    Self { a: A::new(size), b: B::new(size) }
38  }
39
40  pub const fn precursor(&self, id: PrecursorId) -> &dyn Precursor {
41    match id {
42      PrecursorId::A => &self.a,
43      PrecursorId::B => &self.b,
44    }
45  }
46
47  pub(crate) const fn precursor_mut(&mut self, id: PrecursorId) -> &mut dyn Precursor {
48    match id {
49      PrecursorId::A => &mut self.a,
50      PrecursorId::B => &mut self.b,
51    }
52  }
53
54  pub fn precursors(&self) -> impl Iterator<Item = &dyn Precursor> {
55    PrecursorId::iter().map(|id| self.precursor(id))
56  }
57
58  pub fn is_within_territory(&self, coord: Coord, size: ContinentSize) -> bool {
59    let distance = initial_territory_radius(size);
60    macro_rules! check {
61      ($($id:ident),+ $(,)?) => {
62        $(
63          let precursor = self.precursor(PrecursorId::$id);
64          if precursor.origin().is_within_distance(coord, distance) {
65            return true;
66          }
67        )+
68      };
69    }
70
71    check!(A, B);
72
73    false
74  }
75}
76
77#[derive(Deref)]
78pub struct PrecursorBox(Box<dyn Precursor>);
79
80impl PrecursorBox {
81  #[inline]
82  pub fn new(precursor: Box<dyn Precursor>) -> Self {
83    Self(precursor)
84  }
85
86  #[inline]
87  pub fn as_dyn(&self) -> &dyn Precursor {
88    &*self.0
89  }
90}
91
92impl<T> From<T> for PrecursorBox
93where
94  T: Precursor + 'static,
95{
96  fn from(value: T) -> Self {
97    Self::new(Box::new(value))
98  }
99}
100
101#[derive(Copy, Debug, Display, Hash, EnumIter, EnumString, Deserialize, Serialize)]
102#[derive_const(Clone, PartialEq, Eq)]
103#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
104pub enum PrecursorId {
105  #[serde(rename = "A")]
106  #[strum(serialize = "A")]
107  A,
108
109  #[serde(rename = "B")]
110  #[strum(serialize = "B")]
111  B,
112}
113
114#[derive(Clone, Debug, Deserialize, Serialize)]
115#[serde(rename_all = "camelCase")]
116#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
117pub struct PublicPrecursor {
118  id: PrecursorId,
119  origin: Coord,
120}
121
122impl PublicPrecursor {
123  pub const fn new<T>(precursor: &T) -> Self
124  where
125    T: [const] Precursor + ?Sized,
126  {
127    Self {
128      id: precursor.id(),
129      origin: precursor.origin(),
130    }
131  }
132}
133
134impl From<&dyn Precursor> for PublicPrecursor {
135  fn from(precursor: &dyn Precursor) -> Self {
136    Self::new(precursor)
137  }
138}
139
140impl<T> const From<&T> for PublicPrecursor
141where
142  T: [const] Precursor,
143{
144  fn from(precursor: &T) -> Self {
145    Self::new(precursor)
146  }
147}
148
149/// Precursors start with an initial territory equal to one-tenth of the continent size.
150/// In other words, they would begin with a 10×10 territory on a 100×100 continent.
151#[inline]
152pub const fn initial_territory_radius(size: ContinentSize) -> Distance {
153  Distance::new(size.get().div_ceil(20).next_multiple_of(2))
154}
155
156#[inline]
157pub const fn initial_city_amount(size: ContinentSize) -> u8 {
158  size.get().div_ceil(10).saturating_mul(2)
159}
160
161pub fn initial_offensive_personnel() -> ArmyPersonnel {
162  ArmyPersonnel::builder()
163    .axeman(5000)
164    .light_cavalry(2500)
165    .ram(300)
166    .build()
167}
168
169pub fn initial_defensive_personnel() -> ArmyPersonnel {
170  ArmyPersonnel::builder()
171    .archer(3000)
172    .pikeman(5000)
173    .swordsman(5000)
174    .heavy_cavalry(1000)
175    .build()
176}