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