Skip to main content

nil_core/behavior/impl/
recruit.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::behavior::r#impl::idle::IdleBehavior;
5use crate::behavior::score::BehaviorScore;
6use crate::behavior::{Behavior, BehaviorProcessor};
7use crate::continent::coord::Coord;
8use crate::error::Result;
9use crate::ethic::EthicPowerAxis;
10use crate::infrastructure::building::r#impl::academy::recruit_queue::AcademyRecruitOrderRequest;
11use crate::infrastructure::building::r#impl::stable::recruit_queue::StableRecruitOrderRequest;
12use crate::infrastructure::building::r#impl::workshop::recruit_queue::WorkshopRecruitOrderRequest;
13use crate::military::unit::prelude::*;
14use crate::military::unit::{AcademyUnitId, StableUnitId, WorkshopUnitId};
15use crate::world::World;
16use bon::Builder;
17use nil_util::iter::IterExt;
18use rand::random_range;
19use std::fmt::Debug;
20use std::marker::PhantomData;
21use std::num::NonZeroU32;
22use std::ops::{Add, ControlFlow};
23use strum::IntoEnumIterator;
24
25#[derive(Builder, Debug)]
26pub struct RecruitBehavior {
27  coord: Coord,
28}
29
30impl RecruitBehavior {
31  pub const MAX_IN_QUEUE: u8 = 10;
32}
33
34impl Behavior for RecruitBehavior {
35  fn score(&self, world: &World) -> Result<BehaviorScore> {
36    let config = world.config();
37    let infrastructure = world.infrastructure(self.coord)?;
38    let max_in_queue = f64::from(Self::MAX_IN_QUEUE);
39
40    macro_rules! score {
41      ($building:ident) => {{
42        if let Some(in_queue) = infrastructure
43          .$building()
44          .turns_in_recruit_queue(&config)
45        {
46          BehaviorScore::new(1.0 - (in_queue / max_in_queue))
47        } else {
48          BehaviorScore::MIN
49        }
50      }};
51    }
52
53    let academy = score!(academy);
54    let stable = score!(stable);
55    let workshop = score!(workshop);
56
57    Ok(academy.max(stable).max(workshop))
58  }
59
60  fn behave(&self, world: &mut World) -> Result<ControlFlow<()>> {
61    let mut behaviors = vec![IdleBehavior.boxed()];
62
63    macro_rules! push {
64      ($unit:ident, $id:expr) => {{
65        let behavior = RecruitUnitBehavior::builder()
66          .marker(PhantomData::<$unit>)
67          .coord(self.coord)
68          .unit($id)
69          .build()
70          .boxed();
71
72        behaviors.push(behavior);
73      }};
74    }
75
76    for id in UnitId::iter() {
77      match id {
78        UnitId::Archer => push!(Archer, id),
79        UnitId::Axeman => push!(Axeman, id),
80        UnitId::HeavyCavalry => push!(HeavyCavalry, id),
81        UnitId::LightCavalry => push!(LightCavalry, id),
82        UnitId::Pikeman => push!(Pikeman, id),
83        UnitId::Ram => push!(Ram, id),
84        UnitId::Swordsman => push!(Swordsman, id),
85      }
86    }
87
88    BehaviorProcessor::new(world, behaviors)
89      .take(usize::from(Self::MAX_IN_QUEUE))
90      .try_each()?;
91
92    Ok(ControlFlow::Break(()))
93  }
94}
95
96#[derive(Builder, Debug)]
97pub struct RecruitUnitBehavior<T>
98where
99  T: Unit + Debug,
100{
101  coord: Coord,
102  unit: UnitId,
103  marker: PhantomData<T>,
104}
105
106impl<T> Behavior for RecruitUnitBehavior<T>
107where
108  T: Unit + Debug + 'static,
109{
110  fn score(&self, world: &World) -> Result<BehaviorScore> {
111    let unit_box = UnitBox::from(self.unit);
112    let infrastructure = world.infrastructure(self.coord)?;
113
114    if !infrastructure
115      .building(unit_box.building())
116      .is_enabled()
117    {
118      return Ok(BehaviorScore::MIN);
119    }
120
121    if !unit_box
122      .infrastructure_requirements()
123      .has_required_levels(infrastructure)
124    {
125      return Ok(BehaviorScore::MIN);
126    }
127
128    let chunk = unit_box.chunk();
129    let owner = world.continent().owner_of(self.coord)?;
130    let ruler_ref = world.ruler(owner)?;
131
132    if !ruler_ref.has_resources(chunk.resources()) {
133      return Ok(BehaviorScore::MIN);
134    }
135
136    if !world
137      .get_maintenance_balance(owner.clone())?
138      .add(chunk.maintenance() * 5u32)
139      .is_sustainable()
140    {
141      return Ok(BehaviorScore::MIN);
142    }
143
144    let mut score = BehaviorScore::new(random_range(0.8..=1.0));
145
146    if let Some(ethics) = ruler_ref.ethics() {
147      let power_ethics = ethics.power();
148
149      if unit_box.is_defensive() {
150        score *= match power_ethics {
151          EthicPowerAxis::Militarist => 0.75,
152          EthicPowerAxis::FanaticMilitarist => 0.5,
153          EthicPowerAxis::Pacifist => 1.25,
154          EthicPowerAxis::FanaticPacifist => 1.5,
155        }
156      } else {
157        if unit_box.is_workshop_unit() && power_ethics.is_pacifist_variant() {
158          return Ok(BehaviorScore::MIN);
159        }
160
161        score *= match power_ethics {
162          EthicPowerAxis::Militarist => 1.25,
163          EthicPowerAxis::FanaticMilitarist => 1.5,
164          EthicPowerAxis::Pacifist => 0.75,
165          EthicPowerAxis::FanaticPacifist => 0.5,
166        }
167      }
168    }
169
170    Ok(score)
171  }
172
173  fn behave(&self, world: &mut World) -> Result<ControlFlow<()>> {
174    if let Ok(id) = AcademyUnitId::try_from(self.unit) {
175      world.add_academy_recruit_order(&AcademyRecruitOrderRequest {
176        coord: self.coord,
177        unit: id,
178        chunks: NonZeroU32::MIN,
179      })?;
180    } else if let Ok(id) = StableUnitId::try_from(self.unit) {
181      world.add_stable_recruit_order(&StableRecruitOrderRequest {
182        coord: self.coord,
183        unit: id,
184        chunks: NonZeroU32::MIN,
185      })?;
186    } else if let Ok(id) = WorkshopUnitId::try_from(self.unit) {
187      world.add_workshop_recruit_order(&WorkshopRecruitOrderRequest {
188        coord: self.coord,
189        unit: id,
190        chunks: NonZeroU32::MIN,
191      })?;
192    }
193
194    Ok(ControlFlow::Continue(()))
195  }
196}