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