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