1use 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::StorageId;
10use crate::infrastructure::building::prefecture::build_queue::{
11 PrefectureBuildOrderKind,
12 PrefectureBuildOrderRequest,
13};
14use crate::infrastructure::prelude::*;
15use crate::infrastructure::queue::InfrastructureQueue;
16use crate::military::maneuver::Maneuver;
17use crate::world::World;
18use bon::Builder;
19use nil_util::iter::IterExt;
20use rand::random_range;
21use serde::{Deserialize, Serialize};
22use std::fmt::Debug;
23use std::marker::PhantomData;
24use std::ops::ControlFlow;
25use std::sync::LazyLock;
26use strum::IntoEnumIterator;
27
28pub(crate) static BUILD_TEMPLATE: LazyLock<Vec<BuildStep>> = LazyLock::new(generate_template);
29
30#[derive(Builder, Debug)]
31pub struct BuildBehavior {
32 coord: Coord,
33}
34
35impl BuildBehavior {
36 const MAX_IN_QUEUE: u8 = 3;
37}
38
39impl Behavior for BuildBehavior {
40 fn score(&self, world: &World) -> Result<BehaviorScore> {
41 let infrastructure = world.infrastructure(self.coord)?;
42 let max_in_queue = f64::from(Self::MAX_IN_QUEUE);
43
44 if let Some(in_queue) = infrastructure
45 .prefecture()
46 .turns_in_build_queue()
47 {
48 Ok(BehaviorScore::new(1.0 - (in_queue / max_in_queue)))
49 } else {
50 Ok(BehaviorScore::MIN)
51 }
52 }
53
54 fn behave(&self, world: &mut World) -> Result<ControlFlow<()>> {
55 let mut behaviors = vec![IdleBehavior.boxed()];
56 macro_rules! push {
57 ($building:ident, $id:expr) => {{
58 let behavior = BuildBuildingBehavior::builder()
59 .marker(PhantomData::<$building>)
60 .coord(self.coord)
61 .building($id)
62 .build()
63 .boxed();
64
65 behaviors.push(behavior);
66 }};
67 }
68
69 for id in BuildingId::iter() {
70 match id {
71 BuildingId::Academy => push!(Academy, id),
72 BuildingId::Farm => push!(Farm, id),
73 BuildingId::IronMine => push!(IronMine, id),
74 BuildingId::Prefecture => push!(Prefecture, id),
75 BuildingId::Quarry => push!(Quarry, id),
76 BuildingId::Sawmill => push!(Sawmill, id),
77 BuildingId::Silo => push!(Silo, id),
78 BuildingId::Stable => push!(Stable, id),
79 BuildingId::Wall => push!(Wall, id),
80 BuildingId::Warehouse => push!(Warehouse, id),
81 BuildingId::Workshop => push!(Workshop, id),
82 }
83 }
84
85 BehaviorProcessor::new(world, behaviors)
86 .take(usize::from(Self::MAX_IN_QUEUE))
87 .try_each()?;
88
89 Ok(ControlFlow::Break(()))
90 }
91}
92
93#[derive(Builder, Debug)]
94pub struct BuildBuildingBehavior<T>
95where
96 T: Building + Debug,
97{
98 coord: Coord,
99 building: BuildingId,
100 marker: PhantomData<T>,
101}
102
103impl<T> BuildBuildingBehavior<T>
104where
105 T: Building + Debug,
106{
107 const STORAGE_CAPACITY_THRESHOLD: f64 = 0.8;
108}
109
110impl<T> Behavior for BuildBuildingBehavior<T>
111where
112 T: Building + Debug + 'static,
113{
114 #[allow(clippy::too_many_lines)]
115 fn score(&self, world: &World) -> Result<BehaviorScore> {
116 let infrastructure = world.infrastructure(self.coord)?;
117 let building = infrastructure.building(self.building);
118
119 if !building
120 .infrastructure_requirements()
121 .has_required_levels(infrastructure)
122 {
123 return Ok(BehaviorScore::MIN);
124 }
125
126 let level = infrastructure
127 .prefecture()
128 .resolve_level(self.building, building.level());
129
130 if level >= building.max_level() {
131 return Ok(BehaviorScore::MIN);
132 }
133
134 let stats = world.stats().infrastructure();
135 let owner = world.continent().owner_of(self.coord)?;
136 let ruler_ref = world.ruler(owner)?;
137
138 let required_resources = &stats
139 .building(self.building)?
140 .get(level + 1u8)?
141 .resources;
142
143 if !ruler_ref.has_resources(required_resources) {
144 return Ok(BehaviorScore::MIN);
145 }
146
147 if let BuildingId::Wall = self.building
151 && let Some(distance) = world
152 .military()
153 .maneuvers()
154 .filter(|maneuver| maneuver.destination() == self.coord)
155 .filter(|maneuver| maneuver.is_attack() && maneuver.is_going())
156 .filter_map(Maneuver::pending_distance)
157 .min()
158 {
159 let workforce = stats
160 .building(self.building)?
161 .get(level + 1u8)
162 .map(|it| f64::from(it.workforce))?;
163
164 if workforce <= f64::from(distance) {
165 return Ok(BehaviorScore::MAX);
166 }
167 }
168
169 if let BuildingId::Farm = self.building
170 && !world
171 .get_maintenance_balance(owner.clone())?
172 .is_sustainable()
173 {
174 return Ok(BehaviorScore::MAX);
175 }
176
177 if let Ok(id) = StorageId::try_from(self.building) {
179 let resources = ruler_ref.resources();
180 let capacity = world.get_storage_capacity(owner.clone())?;
181
182 let ratio = match id {
183 StorageId::Silo => f64::from(resources.food) / f64::from(capacity.silo),
184 StorageId::Warehouse => {
185 let capacity = f64::from(capacity.warehouse);
186 let iron_ratio = f64::from(resources.iron) / capacity;
187 let stone_ratio = f64::from(resources.stone) / capacity;
188 let wood_ratio = f64::from(resources.wood) / capacity;
189 iron_ratio.max(stone_ratio).max(wood_ratio)
190 }
191 };
192
193 if ratio >= Self::STORAGE_CAPACITY_THRESHOLD
194 && infrastructure
195 .prefecture()
196 .build_queue()
197 .iter()
198 .filter(|order| order.kind().is_construction())
199 .all(|order| order.building() != self.building)
200 {
201 return Ok(BehaviorScore::MAX);
202 }
203 }
204
205 let mut score = if BUILD_TEMPLATE
206 .iter()
207 .filter(|step| !step.is_done(infrastructure))
208 .take(3)
209 .any(|step| step.id == self.building)
210 {
211 BehaviorScore::new(random_range(0.8..=1.0))
212 } else {
213 BehaviorScore::MIN
214 };
215
216 if let Some(ethics) = ruler_ref.ethics() {
217 if self.building.is_civil() {
218 score *= match ethics.power() {
219 EthicPowerAxis::Militarist => 0.9,
220 EthicPowerAxis::FanaticMilitarist => 0.75,
221 EthicPowerAxis::Pacifist => 1.1,
222 EthicPowerAxis::FanaticPacifist => 1.25,
223 }
224 } else {
225 score *= match ethics.power() {
226 EthicPowerAxis::Militarist => 1.1,
227 EthicPowerAxis::FanaticMilitarist => 1.25,
228 EthicPowerAxis::Pacifist => 0.9,
229 EthicPowerAxis::FanaticPacifist => 0.75,
230 }
231 }
232 }
233
234 Ok(score)
235 }
236
237 fn behave(&self, world: &mut World) -> Result<ControlFlow<()>> {
238 let order = PrefectureBuildOrderRequest {
239 coord: self.coord,
240 building: self.building,
241 kind: PrefectureBuildOrderKind::Construction,
242 };
243
244 world.add_prefecture_build_order(&order)?;
245
246 Ok(ControlFlow::Continue(()))
247 }
248}
249
250#[derive(Clone, Debug, Deserialize, Serialize)]
251#[serde(rename_all = "camelCase")]
252pub struct BuildStep {
253 id: BuildingId,
254 level: BuildingLevel,
255}
256
257impl BuildStep {
258 fn new(id: BuildingId, level: BuildingLevel) -> Self {
259 Self { id, level }
260 }
261
262 pub fn is_done(&self, infrastructure: &Infrastructure) -> bool {
263 self.level <= infrastructure.building(self.id).level()
264 }
265}
266
267macro_rules! step {
268 ($id:ident, $level: expr) => {{ BuildStep::new(BuildingId::$id, BuildingLevel::new($level)) }};
269}
270
271fn generate_template() -> Vec<BuildStep> {
272 vec![
273 step!(Sawmill, 8),
274 step!(Quarry, 8),
275 step!(IronMine, 8),
276 step!(Prefecture, 2),
277 step!(Sawmill, 10),
278 step!(Quarry, 10),
279 step!(IronMine, 10),
280 step!(Prefecture, 3),
281 step!(Academy, 1),
282 step!(Farm, 2),
283 step!(Wall, 1),
284 step!(Warehouse, 2),
285 step!(Silo, 2),
286 step!(Sawmill, 12),
287 step!(Quarry, 12),
288 step!(IronMine, 12),
289 step!(Wall, 3),
290 step!(Academy, 3),
291 step!(Silo, 4),
292 step!(Farm, 4),
293 step!(Prefecture, 5),
294 step!(Sawmill, 15),
295 step!(Quarry, 15),
296 step!(IronMine, 15),
297 step!(Academy, 5),
298 step!(Warehouse, 11),
299 step!(Wall, 7),
300 step!(Prefecture, 10),
301 step!(Stable, 1),
302 step!(Sawmill, 18),
303 step!(Quarry, 18),
304 step!(IronMine, 18),
305 step!(Stable, 2),
306 step!(Wall, 10),
307 step!(Silo, 6),
308 step!(Farm, 6),
309 step!(Stable, 3),
310 step!(Warehouse, 15),
311 step!(Wall, 15),
312 step!(Sawmill, 20),
313 step!(Quarry, 20),
314 step!(IronMine, 20),
315 step!(Silo, 10),
316 step!(Farm, 10),
317 step!(Warehouse, 18),
318 step!(Wall, 20),
319 step!(Prefecture, 17),
320 step!(Stable, 6),
321 step!(Academy, 8),
322 step!(Sawmill, 25),
323 step!(Quarry, 25),
324 step!(IronMine, 25),
325 step!(Warehouse, 20),
326 step!(Silo, 15),
327 step!(Farm, 15),
328 step!(Stable, 13),
329 step!(Academy, 13),
330 step!(Warehouse, 23),
331 step!(Prefecture, 25),
332 step!(Academy, 20),
333 step!(Silo, 20),
334 step!(Farm, 20),
335 step!(Warehouse, 27),
336 step!(Sawmill, 30),
337 step!(Quarry, 30),
338 step!(IronMine, 30),
339 step!(Silo, 25),
340 step!(Farm, 25),
341 step!(Stable, 20),
342 step!(Academy, 25),
343 step!(Prefecture, 30),
344 step!(Warehouse, 30),
345 step!(Farm, 30),
346 step!(Silo, 30),
347 ]
348}