Skip to main content

nil_core/behavior/
recruit.rs

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