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