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