banished_game/production/
pasture.rs1use crate::behaviour::character::{Action, Component, Message, MoveTo};
13use crate::nature::wildlife::{Wild, HUNTED};
14use crate::pathfind::{nearest_neighbour, WALKABLE};
15use crate::production::herbalist::HerbalistWork;
16use crate::scheduler::EntityScheduler;
17use crate::terrain::CellType;
18use crate::town::Sim;
19use crate::work::jobs::{Jobs, Work, WorkKind, NESTED};
20
21impl Sim<'_, '_> {
22 pub(crate) fn with_wild<R>(&mut self, f: impl FnOnce(&mut Wild) -> R) -> R {
25 let mut scheduler = EntityScheduler::default();
26 let mut wild = Wild {
27 wildlife: &mut *self.wildlife,
28 placed: &mut *self.placed,
29 templates: self.templates,
30 cells: &*self.cells,
31 terrain: self.terrain,
32 rng: &mut *self.rng,
33 scheduler: &mut scheduler,
34 };
35 f(&mut wild)
36 }
37
38 pub(crate) fn pasture_enter2(&mut self, e: usize) {
41 self.with_wild(|w| w.fill(e));
42 self.wildlife.pasture_list.push(e);
43 self.set_pasture_workplace(e, false);
44 }
45
46 pub(crate) fn pasture_update2(&mut self, jobs: &mut Jobs, e: usize) {
50 self.with_wild(|w| {
51 w.fill(e);
52 w.births(e);
53 });
54 let Some(kind) = self.wildlife.pastures.get(&e).and_then(|p| p.kind) else {
55 return;
56 };
57 let n = self.size as i32;
59 let area = self.wildlife.pastures[&e].area;
60 for y in area[1] as i32..(area[1] + area[3]) as i32 {
61 for x in area[0] as i32..(area[0] + area[2]) as i32 {
62 self.cells.previous_template[(y * n + x) as usize] = Some(kind);
63 }
64 }
65 let Some(lk) = self.wildlife.livestock_kinds.get(&kind).cloned() else {
66 return;
67 };
68 let Some(product) = lk.product else { return };
69 let Some(material) = self.templates[product].pile_material else {
70 return;
71 };
72 let amount = self.materials[material].pile_amount;
73 let (count, limit) = self.stock[lk.group.clamp(0, 20) as usize];
74 while 0 < amount && amount < self.wildlife.pastures[&e].produced {
76 self.wildlife.pastures.get_mut(&e).unwrap().produced -= amount;
77 if limit <= count {
78 continue;
79 }
80 for cell in self.pasture_points(e) {
81 let i = cell as usize;
82 if let Some(o) = self.cells.objects[i] {
83 if self.piles.contains_key(&o) {
84 continue;
85 }
86 self.cells.set_type(i, CellType(2));
88 self.cells.objects[i] = None;
89 }
90 let yaw = self.rng.rand_tol(2) as f32 * 90.0;
91 let pile = self.spawn_pile(product, (cell % n, cell / n), amount, yaw, None);
92 self.count_made(Some(e), true, material, amount);
95 let auto = self
96 .sites
97 .get(&e)
98 .and_then(|s| s.auto_pickup.as_ref())
99 .is_some();
100 if auto {
101 if let Some(p) = self.piles.get_mut(&pile) {
102 p.auto_pickup = Some(e);
103 }
104 }
105 self.activate_piles(jobs);
107 break;
108 }
109 }
110 }
111
112 pub(crate) fn pasture_leave(&mut self, e: usize) {
115 self.with_wild(|w| w.release(e));
116 }
117
118 pub(crate) fn pasture_built(&mut self, e: usize) {
121 for cell in self.pasture_points(e) {
122 let i = cell as usize;
123 self.cells.set_type(i, CellType(2));
124 self.cells.objects[i] = None;
125 }
126 }
127
128 pub(crate) fn pasture_points(&self, e: usize) -> Vec<i32> {
131 let mut cells = Vec::new();
132 let parts = self
133 .sites
134 .get(&e)
135 .map(|s| s.parts.clone())
136 .unwrap_or_default();
137 for x in std::iter::once(e).chain(parts) {
138 let count = self.templates[self.placed[x].template].create_points.len();
139 cells.extend((0..count).map(|i| self.create_cell(x, i)));
140 }
141 cells
142 }
143
144 fn set_pasture_workplace(&mut self, e: usize, on: bool) {
146 self.enable_workplace(e, on);
147 }
148
149 pub fn pasture_message(&mut self, e: usize, message: i32) {
153 if message == 0 {
154 self.with_wild(|w| w.mark_breeder(e));
155 }
156 }
157
158 pub fn set_pasture_kind(&mut self, jobs: &mut Jobs, e: usize, kind: Option<usize>) {
164 let Some(old) = self.wildlife.pastures.get(&e).map(|p| p.kind) else {
165 return;
166 };
167 if old.is_none() && kind.is_some() {
168 self.wildlife.pastures.get_mut(&e).unwrap().kind = kind;
169 self.pasture_action(jobs, e, 2);
170 }
171 self.wildlife.pastures.get_mut(&e).unwrap().kind = kind;
172 let cap = self.wildlife.capacity(e);
173 self.wildlife.pastures.get_mut(&e).unwrap().limit = cap;
174 }
175
176 pub fn set_pasture_limit(&mut self, e: usize, limit: i32) {
181 if let Some(p) = self.wildlife.pastures.get_mut(&e) {
182 p.limit = limit;
183 }
184 }
185
186 pub fn pasture_query(&self, e: usize, query: i32) -> u8 {
193 let Some(p) = self.wildlife.pastures.get(&e) else {
194 return 0;
195 };
196 let mut free = 0;
197 for &o in &self.wildlife.pasture_list {
198 if o != e
199 && self
200 .wildlife
201 .pastures
202 .get(&o)
203 .is_some_and(|x| x.kind == p.kind)
204 {
205 free +=
206 self.wildlife.capacity(o) - self.wildlife.pastures[&o].livestock.len() as i32;
207 }
208 }
209 let has = u8::from(p.kind.is_some());
210 let count = p.livestock.len() as i32;
211 match query {
212 0 if 0 < count && count <= free => has,
213 1 if 9 < count && 4 < free => has,
214 2 => {
215 let on = self.workforce.workplaces.get(&e).is_some_and(|w| w.enabled);
216 if on {
217 2 | has
218 } else {
219 has
220 }
221 }
222 _ => 0,
223 }
224 }
225
226 pub fn pasture_action(&mut self, jobs: &mut Jobs, e: usize, action: i32) {
232 if self.pasture_query(e, action) & 1 == 0 {
233 return;
234 }
235 if action == 2 {
236 let on = self.workforce.workplaces.get(&e).is_some_and(|w| w.enabled);
237 self.set_pasture_workplace(e, !on);
238 if on {
239 self.remove_work(jobs, e);
240 self.set_pasture_workplace(e, false);
241 } else {
242 let work = self.pasture_work(e);
243 self.set_work(jobs, e, work, 1, true);
244 self.set_pasture_workplace(e, true);
245 }
246 return;
247 }
248 let count = self.wildlife.pastures[&e].livestock.len() as i32;
249 let keep = if action == 0 { 0 } else { count - (count >> 1) };
250 let kind = self.wildlife.pastures[&e].kind;
251 let room = |w: &crate::nature::wildlife::Wildlife, o: usize| {
252 w.capacity(o) - w.pastures[&o].livestock.len() as i32
253 };
254 let others: Vec<usize> = self
255 .wildlife
256 .pasture_list
257 .iter()
258 .copied()
259 .filter(|&o| {
260 o != e
261 && self
262 .wildlife
263 .pastures
264 .get(&o)
265 .is_some_and(|x| x.kind == kind)
266 })
267 .collect();
268 let mut best = None;
269 let mut most = 0;
270 for &o in &others {
271 let r = room(self.wildlife, o);
272 if most < r {
273 most = r;
274 best = Some(o);
275 }
276 }
277 let send = |sim: &mut Self, o: usize| {
278 while keep < sim.wildlife.pastures[&e].livestock.len() as i32
279 && 0 < room(sim.wildlife, o)
280 {
281 let a = sim.wildlife.pastures[&e].livestock[0];
282 sim.with_wild(|w| w.assign(a, Some(o)));
283 }
284 };
285 if let Some(b) = best {
286 send(self, b);
287 }
288 for o in others {
289 if Some(o) != best {
290 send(self, o);
291 }
292 }
293 }
294
295 pub(crate) fn pasture_work(&self, e: usize) -> Work {
297 let profession = self
298 .workforce
299 .workplaces
300 .get(&e)
301 .and_then(|w| w.profession)
302 .unwrap_or(self.professions.laborer);
303 Work::new(
304 e,
305 self.rect_cell(e),
306 profession,
307 WorkKind::Pasture(HerbalistWork::default()),
308 )
309 }
310
311 pub(crate) fn pasture_try(&mut self, work: &mut Work, c: usize) -> u8 {
315 if std::mem::take(&mut self.pasture_stage) {
316 return self.pasture_walk(work, c);
317 }
318 let e = work.entity;
319 let WorkKind::Pasture(h) = &work.kind else {
320 return 0;
321 };
322 if self.work_max(e) < h.workers.len() as i32 {
323 return 1;
324 }
325 let group = self
326 .sites
327 .get(&e)
328 .and_then(|s| self.site_templates[s.template].pasture)
329 .unwrap_or(5);
330 let (count, limit) = self.stock[group.clamp(0, 20) as usize];
331 self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
332 if limit <= count {
333 return 1;
334 }
335 let Some(p) = self.wildlife.pastures.get(&e) else {
336 return 0;
337 };
338 let here = self.citizen_cell(c);
339 if p.limit < p.livestock.len() as i32 {
340 let Some(a) = self.cull_choice(e) else {
341 return 1;
342 };
343 let animal = &self.wildlife.animals[&a];
344 if animal.flags & HUNTED != 0 {
345 return 1;
346 }
347 let at = self.cell_of(animal.character.position);
348 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, at, here, WALKABLE)
349 else {
350 return 1;
351 };
352 if !self.reachable(nb, here) {
353 return 1;
354 }
355 let point = self.ground_at(nb);
356 self.cull_sequence(c, work, a, point, at);
357 self.took_job(c, work);
358 return 2;
359 }
360 let n = self.size as i32;
361 let offers: Vec<i32> = self
362 .pasture_points(e)
363 .into_iter()
364 .filter(|&cell| 0 <= cell && cell < n * n)
365 .filter_map(|cell| self.cells.objects[cell as usize])
366 .filter_map(|o| self.piles.get(&o).map(|p| p.work))
367 .collect();
368 if !offers.is_empty() {
369 self.offers = offers;
370 self.offers_again = true;
371 self.pasture_stage = true;
372 return NESTED;
373 }
374 self.pasture_walk(work, c)
375 }
376
377 fn pasture_walk(&mut self, work: &mut Work, c: usize) -> u8 {
380 let e = work.entity;
381 let Some(area) = self.wildlife.pastures.get(&e).map(|p| p.area) else {
382 return 0;
383 };
384 let n = self.size as i32;
385 let ry = self.rng.rand_below(area[3] as u32) as i32;
386 let rx = self.rng.rand_below(area[2] as u32) as i32;
387 let cell = (area[1] as i32 + ry) * n + rx + area[0] as i32;
388 let here = self.citizen_cell(c);
389 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, here, WALKABLE) else {
390 return 0;
391 };
392 if !self.reachable(here, nb) {
393 return 0;
394 }
395 let at = self.ground_at(cell);
396 let stand = self.ground_at(nb);
397 let message = Message {
398 component: Component::Pasture,
399 message: 0,
400 entity: Some(e),
401 a: 0,
402 b: 0,
403 };
404 self.tend_sequence(c, work, stand, at, message);
405 self.took_job(c, work);
406 if let WorkKind::Pasture(h) = &mut work.kind {
407 h.workers.insert(c);
408 }
409 2
410 }
411
412 fn cull_choice(&self, e: usize) -> Option<usize> {
414 let mut best = None;
415 let mut oldest = 0.0f32;
416 for &a in &self.wildlife.pastures.get(&e)?.livestock {
417 let animal = &self.wildlife.animals[&a];
418 if oldest < animal.age && animal.flags & HUNTED == 0 {
419 let adult = self.wildlife.animal_kinds[&animal.template].desc.adult;
420 if adult < animal.age {
421 oldest = animal.age;
422 best = Some(a);
423 }
424 }
425 }
426 best
427 }
428
429 fn cull_sequence(&mut self, c: usize, work: &mut Work, a: usize, point: [f32; 3], at: i32) {
432 self.clear_citizen(c, Some(work));
433 self.on_empty_think(c);
434 self.push(
435 c,
436 Action::CompleteTask {
437 work: work.id,
438 param: a as i32,
439 },
440 );
441 let template = self.wildlife.animals[&a].template;
442 let materials = self
443 .wildlife
444 .animal_kinds
445 .get(&template)
446 .map(|k| k.materials.clone())
447 .unwrap_or_default();
448 let batch = self.states[c].carried.capacity;
449 let workplace = self.states[c].workplace;
450 for m in materials {
451 self.push(
452 c,
453 Action::CreateRawMaterial {
454 template: m,
455 cell: at,
456 batch,
457 workplace,
458 factor: 1.0,
459 },
460 );
461 }
462 self.push(c, Action::RemoveAnimal { entity: a });
463 let perform = self.perform(c, -1000.0, 0xb, 4.0, true);
464 self.push(c, perform);
465 let walk = MoveTo::new(point, 1, 0, WALKABLE, 0, -1.0, self.rng);
466 self.push(c, Action::move_to(walk));
467 self.with_wild(|w| w.hunted(a));
468 }
469
470 pub(crate) fn pasture_fail(&mut self, work: &mut Work, c: usize, param: i32) {
473 if let WorkKind::Pasture(h) = &mut work.kind {
474 h.workers.remove(&c);
475 }
476 if let Some(animal) = usize::try_from(param)
477 .ok()
478 .and_then(|a| self.wildlife.animals.get_mut(&a))
479 {
480 animal.flags &= !HUNTED;
481 }
482 }
483}