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