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;
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  const MAX_IN_QUEUE: u8 = 5;
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    macro_rules! push {
63      ($unit:ident, $id:expr) => {{
64        let behavior = RecruitUnitBehavior::builder()
65          .marker(PhantomData::<$unit>)
66          .coord(self.coord)
67          .unit($id)
68          .build()
69          .boxed();
70
71        behaviors.push(behavior);
72      }};
73    }
74
75    for id in UnitId::iter() {
76      match id {
77        UnitId::Archer => push!(Archer, id),
78        UnitId::Axeman => push!(Axeman, id),
79        UnitId::HeavyCavalry => push!(HeavyCavalry, id),
80        UnitId::LightCavalry => push!(LightCavalry, id),
81        UnitId::Pikeman => push!(Pikeman, id),
82        UnitId::Ram => push!(Ram, id),
83        UnitId::Swordsman => push!(Swordsman, id),
84      }
85    }
86
87    BehaviorProcessor::new(world, behaviors)
88      .take(usize::from(Self::MAX_IN_QUEUE))
89      .try_each()?;
90
91    Ok(ControlFlow::Break(()))
92  }
93}
94
95#[derive(Builder, Debug)]
96pub struct RecruitUnitBehavior<T>
97where
98  T: Unit + Debug,
99{
100  coord: Coord,
101  unit: UnitId,
102  marker: PhantomData<T>,
103}
104
105impl<T> Behavior for RecruitUnitBehavior<T>
106where
107  T: Unit + Debug + 'static,
108{
109  fn score(&self, world: &World) -> Result<BehaviorScore> {
110    let unit_box = UnitBox::from(self.unit);
111    let infrastructure = world.infrastructure(self.coord)?;
112
113    if !infrastructure
114      .building(unit_box.building())
115      .is_enabled()
116    {
117      return Ok(BehaviorScore::MIN);
118    }
119
120    if !unit_box
121      .infrastructure_requirements()
122      .has_required_levels(infrastructure)
123    {
124      return Ok(BehaviorScore::MIN);
125    }
126
127    let chunk = unit_box.chunk();
128    let owner = world.continent().owner_of(self.coord)?;
129    let ruler_ref = world.ruler(owner)?;
130
131    if !ruler_ref.has_resources(&chunk.resources()) {
132      return Ok(BehaviorScore::MIN);
133    }
134
135    if !world
136      .get_maintenance_balance(owner.clone())?
137      .add(chunk.maintenance() * 5u32)
138      .is_sustainable()
139    {
140      return Ok(BehaviorScore::MIN);
141    }
142
143    let mut score = BehaviorScore::new(random_range(0.8..=1.0));
144
145    if let Some(ethics) = ruler_ref.ethics() {
146      let power_ethics = ethics.power();
147
148      if unit_box.is_defensive() {
149        score *= match power_ethics {
150          EthicPowerAxis::Militarist => 0.75,
151          EthicPowerAxis::FanaticMilitarist => 0.5,
152          EthicPowerAxis::Pacifist => 1.25,
153          EthicPowerAxis::FanaticPacifist => 1.5,
154        }
155      } else {
156        if unit_box.is_workshop_unit() && power_ethics.is_pacifist_variant() {
157          return Ok(BehaviorScore::MIN);
158        }
159
160        score *= match power_ethics {
161          EthicPowerAxis::Militarist => 1.25,
162          EthicPowerAxis::FanaticMilitarist => 1.5,
163          EthicPowerAxis::Pacifist => 0.75,
164          EthicPowerAxis::FanaticPacifist => 0.5,
165        }
166      }
167    }
168
169    Ok(score)
170  }
171
172  fn behave(&self, world: &mut World) -> Result<ControlFlow<()>> {
173    if let Ok(id) = AcademyUnitId::try_from(self.unit) {
174      world.add_academy_recruit_order(&AcademyRecruitOrderRequest {
175        coord: self.coord,
176        unit: id,
177        chunks: NonZeroU32::MIN,
178      })?;
179    } else if let Ok(id) = StableUnitId::try_from(self.unit) {
180      world.add_stable_recruit_order(&StableRecruitOrderRequest {
181        coord: self.coord,
182        unit: id,
183        chunks: NonZeroU32::MIN,
184      })?;
185    } else if let Ok(id) = WorkshopUnitId::try_from(self.unit) {
186      world.add_workshop_recruit_order(&WorkshopRecruitOrderRequest {
187        coord: self.coord,
188        unit: id,
189        chunks: NonZeroU32::MIN,
190      })?;
191    }
192
193    Ok(ControlFlow::Continue(()))
194  }
195}