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