nil_core/behavior/impl/
recruit.rs1use 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}