1use std::collections::BTreeMap;
8
9use crate::behaviour::character::{Action, Component, Message, MoveTo};
10use crate::pathfind::{nearest_neighbour, WALKABLE};
11use crate::production::farm::AreaWork;
12use crate::scheduler::{seconds, ticks};
13use crate::town::Sim;
14use crate::work::construction::Callbacks;
15use crate::work::jobs::{Jobs, Work, WorkKind};
16
17#[derive(Debug, Clone, Copy, Default, PartialEq)]
23pub struct OrchardTemplate {
24 pub spacing: (i32, i32),
26 pub tend: f32,
28 pub group: i32,
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
35pub struct TreeSlot {
36 pub cell: i32,
39 pub state: i32,
42 pub entity: Option<usize>,
45 pub picked: bool,
48}
49
50#[derive(Debug, Clone, Default, PartialEq)]
53pub struct Orchard {
54 pub enabled: bool,
57 pub year: u32,
60 pub trees: Vec<TreeSlot>,
62 pub tree: Option<usize>,
65}
66
67#[derive(Debug, Clone, Copy, Default, PartialEq)]
70pub struct Fruit {
71 pub ripeness: f32,
73 pub picked: bool,
75}
76
77#[derive(Debug, Clone, Copy, Default, PartialEq)]
83pub struct FruitTemplate {
84 pub months: f32,
86 pub best: f32,
88 pub range: f32,
90 pub pile: Option<usize>,
92}
93
94pub fn ripen(f: &mut Fruit, d: &FruitTemplate, growth: f32, dt: i64, calendar: f32, t: f32) {
100 let rate = 1.0 / (d.months * 300.0);
101 let month = (calendar / 3600.0 * 12.0) as i32;
102 if f.picked && 4 < (month - 7) as u32 {
103 f.picked = false;
104 }
105 let distance = (t - d.best).abs() / d.range;
106 let weight = if !f.picked && 1.0 <= growth {
107 if !(0.0..=1.0).contains(&distance) {
108 if t <= 32.0 {
109 -10.0
110 } else {
111 0.0
112 }
113 } else {
114 1.0 - distance * distance
115 }
116 } else {
117 0.0
118 };
119 let g = growth.min(1.0);
120 f.ripeness += seconds(dt) * g * rate * weight;
121 if f.ripeness < 0.0 {
122 f.ripeness = 0.0;
123 }
124 if 1.0 < f.ripeness {
125 f.ripeness = 1.0;
126 }
127}
128
129impl Sim<'_, '_> {
130 fn orchard(&self, e: usize) -> Option<&Orchard> {
131 self.sites.get(&e)?.orchard.as_ref()
132 }
133
134 fn orchard_mut(&mut self, e: usize) -> Option<&mut Orchard> {
135 self.sites.get_mut(&e)?.orchard.as_mut()
136 }
137
138 fn orchard_desc(&self, e: usize) -> OrchardTemplate {
139 self.site_templates[self.sites[&e].template]
140 .orchard
141 .unwrap_or_default()
142 }
143
144 fn orchard_refresh(&mut self, e: usize) {
146 let objects = &self.cells.objects;
147 let placed = &self.placed;
148 let templates = self.templates;
149 let Some(o) = self.sites.get_mut(&e).and_then(|s| s.orchard.as_mut()) else {
150 return;
151 };
152 for t in o.trees.iter_mut().filter(|t| t.state == 1) {
153 let here = objects[t.cell as usize];
154 let alive = here.is_some_and(|x| {
155 templates[placed[x].template].natural.is_some() && Some(x) == t.entity
156 });
157 if !alive {
158 t.state = 2;
159 t.entity = None;
160 }
161 }
162 }
163
164 fn orchard_count(&mut self, e: usize, state: i32) -> usize {
165 self.orchard_refresh(e);
166 self.orchard(e)
167 .map_or(0, |o| o.trees.iter().filter(|t| t.state == state).count())
168 }
169
170 pub(crate) fn orchard_enter2(&mut self, e: usize) {
172 self.enable_workplace(e, false);
173 }
174
175 pub(crate) fn orchard_update2(&mut self, e: usize) {
177 if self.orchard(e).is_some_and(|o| o.enabled) {
178 self.machine(e).request(9);
179 }
180 }
181
182 pub(crate) fn orchard_enter9(&mut self, jobs: &mut Jobs, e: usize) {
185 let on = self.orchard(e).is_some_and(|o| o.enabled);
186 self.enable_workplace(e, on);
187 let d = self.orchard_desc(e);
188 let (sx, sy) = (d.spacing.0.max(1), d.spacing.1.max(1));
189 let n = self.size as i32;
190 let (x0, y0) = self.placed[e].cell;
191 let (w, h) = self.placed[e].footprint;
192 let cols = (w - 1 + sx) / sx;
193 let rows = (h - 1 + sy) / sy;
194 let mut trees = Vec::new();
195 for r in 0..rows {
196 for c in 0..cols {
197 trees.push(TreeSlot {
198 cell: (y0 + r * sy) * n + x0 + c * sx,
199 ..Default::default()
200 });
201 }
202 }
203 if let Some(o) = self.orchard_mut(e) {
204 o.trees = trees;
205 }
206 self.unstamp(e);
207 self.orchard_plant_work(jobs, e);
208 }
209
210 fn orchard_plant_work(&mut self, jobs: &mut Jobs, e: usize) {
212 self.orchard_refresh(e);
213 let count = self.orchard(e).map_or(0, |o| o.trees.len());
214 let tree = self.orchard(e).and_then(|o| o.tree);
215 let profession = self
216 .workforce
217 .workplaces
218 .get(&e)
219 .and_then(|w| w.profession)
220 .unwrap_or(self.professions.laborer);
221 let work = Work::new(
222 e,
223 self.rect_cell(e),
224 profession,
225 WorkKind::PlantOrchard(AreaWork {
226 claims: BTreeMap::new(),
227 need: vec![false; count],
228 crop: tree,
229 }),
230 );
231 self.set_work(jobs, e, work, 1, false);
232 }
233
234 pub(crate) fn orchard_update9(&mut self, jobs: &mut Jobs, e: usize) {
236 let total = self.orchard(e).map_or(0, |o| o.trees.len());
237 if self.orchard_count(e, 1) == total {
238 self.remove_work(jobs, e);
239 self.machine(e).request(10);
240 }
241 }
242
243 pub fn orchard_clear_now(&mut self, e: usize) {
247 self.machine(e).request(0x14);
248 }
249
250 pub fn orchard_harvest_now(&mut self, e: usize) {
254 self.machine(e).request(0xb);
255 let year = self.year;
256 if let Some(o) = self.orchard_mut(e) {
257 for t in &mut o.trees {
258 t.picked = false;
259 }
260 o.year = year;
261 }
262 }
263
264 fn orchard_ripeness(&self, e: usize) -> f32 {
266 self.orchard(e)
267 .map_or(0.0, |o| ripeness(o, &self.cells.objects, self.fruits))
268 }
269
270 fn orchard_harvest_due(&mut self, e: usize) -> bool {
273 let Some(o) = self.orchard(e) else {
274 return false;
275 };
276 if !o.enabled || self.year == o.year {
277 return false;
278 }
279 let month = (self.calendar / 3600.0 * 12.0) as i32;
280 let ripe = self.orchard_ripeness(e);
281 let autumn = self.temperature >= 33.0 && month >= 0 && ((month - 7) as u32) < 5;
282 if (autumn && 0.0 < ripe) || 0.99 < ripe {
283 self.orchard_harvest_now(e);
284 return true;
285 }
286 false
287 }
288
289 pub(crate) fn orchard_enter10(&mut self, jobs: &mut Jobs, e: usize) {
291 if self.orchard_count(e, 2) > 0 {
292 self.machine(e).request(0xc);
293 return;
294 }
295 self.orchard_tend_work(jobs, e);
296 }
297
298 fn orchard_tend_work(&mut self, jobs: &mut Jobs, e: usize) {
299 let profession = self
300 .workforce
301 .workplaces
302 .get(&e)
303 .and_then(|w| w.profession)
304 .unwrap_or(self.professions.laborer);
305 let work = Work::new(
306 e,
307 self.rect_cell(e),
308 profession,
309 WorkKind::TendArea(crate::production::herbalist::HerbalistWork::default()),
310 );
311 self.set_work(jobs, e, work, 1, false);
312 }
313
314 pub(crate) fn orchard_exit_work(&mut self, jobs: &mut Jobs, e: usize) {
316 let id = self.sites[&e].work_id;
317 if id >= 0 && jobs.works.contains_key(&id) {
318 self.remove_work(jobs, e);
319 }
320 }
321
322 pub(crate) fn orchard_update10(&mut self, jobs: &mut Jobs, e: usize) {
325 self.orchard_refresh(e);
326 if self.orchard_harvest_due(e) {
327 return;
328 }
329 if self.orchard_count(e, 2) > 0 {
330 self.machine(e).request(0xc);
331 }
332 let month = (self.calendar / 3600.0 * 12.0) as i32;
333 let season = month >= 0 && ((month - 1) as u32) < 0xb && 2 < (month - 9) as u32;
334 let id = self.sites[&e].work_id;
335 let registered = id >= 0 && jobs.works.contains_key(&id);
336 if registered && !season {
337 self.remove_work(jobs, e);
338 } else if !registered && season {
339 self.orchard_tend_work(jobs, e);
340 }
341 }
342
343 pub(crate) fn orchard_enter12(&mut self, jobs: &mut Jobs, e: usize) {
345 self.orchard_plant_work(jobs, e);
346 }
347
348 pub(crate) fn orchard_update12(&mut self, e: usize) {
350 self.orchard_refresh(e);
351 if self.orchard_harvest_due(e) {
352 return;
353 }
354 if self.orchard_count(e, 2) == 0 {
355 self.machine(e).request(10);
356 }
357 }
358
359 pub(crate) fn orchard_enter11(&mut self, jobs: &mut Jobs, e: usize) {
362 let need: Vec<bool> = self
363 .orchard(e)
364 .map(|o| o.trees.iter().map(|t| t.entity.is_some()).collect())
365 .unwrap_or_default();
366 let profession = self
367 .workforce
368 .workplaces
369 .get(&e)
370 .and_then(|w| w.profession)
371 .unwrap_or(self.professions.laborer);
372 let work = Work::new(
373 e,
374 self.rect_cell(e),
375 profession,
376 WorkKind::PickFruit(AreaWork {
377 claims: BTreeMap::new(),
378 need,
379 crop: None,
380 }),
381 );
382 self.set_work(jobs, e, work, 1, false);
383 let tree = self.orchard(e).and_then(|o| o.tree);
384 let n = self.size as i32;
385 let (x0, y0) = self.placed[e].cell;
386 let (w, h) = self.placed[e].footprint;
387 for y in y0..y0 + h {
388 for x in x0..x0 + w {
389 self.cells.previous_template[(y * n + x) as usize] = tree;
390 }
391 }
392 }
393
394 pub(crate) fn orchard_update11(&mut self, jobs: &mut Jobs, e: usize) {
396 let Some(o) = self.orchard(e) else { return };
397 let standing = o.trees.iter().filter(|t| t.state == 1).count();
398 let picked = o.trees.iter().filter(|t| t.picked).count();
399 if standing <= picked {
400 self.remove_work(jobs, e);
401 self.machine(e).request(10);
402 }
403 }
404
405 pub(crate) fn orchard_enter14(&mut self, jobs: &mut Jobs, e: usize) {
408 let id = self.sites[&e].work_id;
409 if id >= 0 && jobs.works.contains_key(&id) {
410 self.remove_work(jobs, e);
411 }
412 let filter = self.site_templates[self.sites[&e].template]
413 .clear
414 .as_ref()
415 .and_then(|c| c.reference.clone());
416 let work = Work::new(
417 e,
418 self.rect_cell(e),
419 self.professions.laborer,
420 WorkKind::Clear(crate::work::construction::ClearWork {
421 filter,
422 clear_resources: true,
423 clear_cells: false,
424 clear_piles: true,
425 ..Default::default()
426 }),
427 );
428 self.set_work(jobs, e, work, 0x401, false);
429 }
430
431 pub(crate) fn orchard_exit14(&mut self, jobs: &mut Jobs, e: usize) {
433 self.remove_work(jobs, e);
434 }
435
436 pub(crate) fn orchard_update14(&mut self, e: usize) {
438 if self.is_clear(e) {
439 self.machine(e).request(9);
440 }
441 }
442
443 pub fn orchard_message(&mut self, e: usize, message: i32, index: i32) {
449 let Some(slot) = self
450 .orchard(e)
451 .and_then(|o| o.trees.get(index as usize))
452 .copied()
453 else {
454 return;
455 };
456 let here = self.cells.objects[slot.cell as usize];
457 match message {
458 0 => {
459 let fruit = here.is_some_and(|x| {
460 let t = &self.templates[self.placed[x].template];
461 t.natural.is_some() && t.fruit.is_some()
462 });
463 if let Some(t) = self
464 .orchard_mut(e)
465 .and_then(|o| o.trees.get_mut(index as usize))
466 {
467 if fruit {
468 t.state = 1;
469 t.entity = here;
470 } else {
471 t.state = 2;
472 }
473 }
474 }
475 1 => {
476 self.orchard_refresh(e);
477 let Some(t) = self
478 .orchard_mut(e)
479 .and_then(|o| o.trees.get_mut(index as usize))
480 else {
481 return;
482 };
483 t.picked = true;
484 if let Some(x) = t.entity {
485 let f = self.fruits.entry(x).or_default();
486 f.ripeness = 0.0;
487 f.picked = true;
488 }
489 }
490 2 => {
491 self.orchard_refresh(e);
492 let Some(x) = here else { return };
493 let Some(fd) = self.templates[self.placed[x].template].fruit else {
494 return;
495 };
496 let tend = self.orchard_desc(e).tend;
497 let dt = ticks(fd.months * 300.0 * tend);
498 let growth = self.placed[x].growth;
499 let (calendar, t) = (self.calendar, self.temperature);
500 ripen(
501 self.fruits.entry(x).or_default(),
502 &fd,
503 growth,
504 dt,
505 calendar,
506 t,
507 );
508 }
509 _ => {}
510 }
511 }
512
513 pub fn orchard_action(&mut self, e: usize, action: i32) {
518 let Some(state) = self.sites.get(&e).map(|s| s.machine.state) else {
519 return;
520 };
521 match action {
522 0 if state == 2 => self.machine(e).request(9),
523 1 if state == 0xa || state == 0xc => self.orchard_harvest_now(e),
524 2 => {
525 let on = match self.orchard_mut(e) {
526 Some(o) => {
527 o.enabled = !o.enabled;
528 o.enabled
529 }
530 None => return,
531 };
532 self.enable_workplace(e, on);
533 }
534 3 if matches!(state, 9..=0xc) => self.orchard_clear_now(e),
535 _ => {}
536 }
537 }
538
539 pub fn set_orchard_tree(&mut self, e: usize, tree: Option<usize>) {
542 let Some(old) = self.orchard(e).map(|o| o.tree) else {
543 return;
544 };
545 if old.is_none() && tree.is_some() {
546 self.orchard_action(e, 2);
547 }
548 if let Some(o) = self.orchard_mut(e) {
549 o.tree = tree;
550 }
551 }
552
553 pub(crate) fn plant_orchard_try(&mut self, work: &mut Work, c: usize) -> u8 {
556 let e = work.entity;
557 self.orchard_refresh(e);
558 let WorkKind::PlantOrchard(pa) = &work.kind else {
559 return 0;
560 };
561 if self.work_max(e) < pa.claims.len() as i32 {
562 return 1;
563 }
564 let Some(o) = self.orchard(e).cloned() else {
565 return 0;
566 };
567 let from = self.citizen_cell(c);
568 for (i, slot) in o.trees.iter().enumerate() {
569 let WorkKind::PlantOrchard(pa) = &work.kind else {
570 return 0;
571 };
572 if pa.need.get(i).copied().unwrap_or(true) || pa.claims.contains_key(&(i as i32)) {
573 continue;
574 }
575 if slot.entity.is_some() {
576 continue;
577 }
578 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, slot.cell, from, 0xe0f)
579 else {
580 continue;
581 };
582 if !self.reachable(from, nb) {
583 continue;
584 }
585 let Some(tree) = pa.crop else { return 1 };
586 let point = self.ground_at(nb);
587 let message = Message {
588 component: Component::Orchard,
589 message: 0,
590 entity: Some(e),
591 a: i as i32,
592 b: 0,
593 };
594 self.plant_sequence(
595 c,
596 work,
597 tree,
598 point,
599 slot.cell,
600 Some(e),
601 Some(message),
602 i as i32,
603 );
604 self.took_job(c, work);
605 if let WorkKind::PlantOrchard(pa) = &mut work.kind {
606 pa.claims.insert(i as i32, c);
607 pa.need[i] = true;
608 }
609 return 2;
610 }
611 1
612 }
613
614 pub(crate) fn plant_orchard_refresh(&mut self, work: &mut Work) {
616 let id = work.id;
617 let WorkKind::PlantOrchard(pa) = &mut work.kind else {
618 return;
619 };
620 let lost: Vec<i32> = pa
621 .claims
622 .iter()
623 .filter(|(_, &c)| self.characters[c].stack.holds_task(id).is_none())
624 .map(|(&i, _)| i)
625 .collect();
626 for i in lost {
627 pa.claims.remove(&i);
628 if let Some(n) = pa.need.get_mut(i as usize) {
629 *n = false;
630 }
631 }
632 }
633
634 pub(crate) fn plant_orchard_done(work: &mut Work, index: i32) {
636 if let WorkKind::PlantOrchard(pa) = &mut work.kind {
637 pa.claims.remove(&index);
638 if let Some(n) = pa.need.get_mut(index as usize) {
639 *n = false;
640 }
641 }
642 }
643
644 pub(crate) fn tend_orchard_try(&mut self, work: &mut Work, c: usize) -> u8 {
646 let e = work.entity;
647 let WorkKind::TendArea(t) = &work.kind else {
648 return 0;
649 };
650 if self.work_max(e) < t.workers.len() as i32 {
651 return 1;
652 }
653 let from = self.citizen_cell(c);
654 let count = self.orchard(e).map_or(0, |o| o.trees.len()) as u32;
655 let index = self.rng.rand_below(count) as i32;
656 let Some(cell) = self
657 .orchard(e)
658 .and_then(|o| o.trees.get(index as usize))
659 .map(|t| t.cell)
660 else {
661 return 0;
662 };
663 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, from, WALKABLE) else {
664 return 0;
665 };
666 if !self.reachable(from, nb) {
667 return 0;
668 }
669 let at = self.ground_at(cell);
670 let stand = self.ground_at(nb);
671 let message = Message {
672 component: Component::Orchard,
673 message: 2,
674 entity: Some(e),
675 a: index,
676 b: 0,
677 };
678 self.tend_sequence(c, work, stand, at, message);
679 self.took_job(c, work);
680 if let WorkKind::TendArea(t) = &mut work.kind {
681 t.workers.insert(c);
682 }
683 2
684 }
685
686 pub(crate) fn pick_fruit_try(&mut self, work: &mut Work, c: usize) -> u8 {
688 let e = work.entity;
689 let WorkKind::PickFruit(pf) = &work.kind else {
690 return 0;
691 };
692 if self.work_max(e) < pf.claims.len() as i32 {
693 return 1;
694 }
695 let group = self.orchard_desc(e).group;
696 let (count, limit) = self.stock[group.clamp(0, 20) as usize];
697 self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
698 if limit <= count {
699 return 1;
700 }
701 let Some(o) = self.orchard(e).cloned() else {
702 return 0;
703 };
704 let from = self.citizen_cell(c);
705 for (i, slot) in o.trees.iter().enumerate() {
706 let WorkKind::PickFruit(pf) = &work.kind else {
707 return 0;
708 };
709 if !pf.need[i] || pf.claims.contains_key(&(i as i32)) {
710 continue;
711 }
712 let Some(tree) = self.cells.objects[slot.cell as usize] else {
713 if let WorkKind::PickFruit(pf) = &mut work.kind {
714 pf.need[i] = false;
715 }
716 continue;
717 };
718 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, slot.cell, from, 0xe0f)
719 else {
720 continue;
721 };
722 if !self.reachable(from, nb) {
723 continue;
724 }
725 if self.templates[self.placed[tree].template].natural.is_none() {
726 if let WorkKind::PickFruit(pf) = &mut work.kind {
727 pf.need[i] = false;
728 }
729 continue;
730 }
731 if self.claimed.contains(&tree) {
732 continue;
733 }
734 let fd = self.templates[self.placed[tree].template]
735 .fruit
736 .unwrap_or_default();
737 let amount = self.fruits.get(&tree).map_or(0.0, |f| f.ripeness);
738 let point = self.ground_at(nb);
739 self.pick_sequence(c, work, e, point, fd.pile, i % 2 == 0, amount, nb, i as i32);
740 self.took_job(c, work);
741 if let WorkKind::PickFruit(pf) = &mut work.kind {
742 pf.claims.insert(i as i32, c);
743 pf.need[i] = false;
744 }
745 return 2;
746 }
747 1
748 }
749
750 #[allow(clippy::too_many_arguments)]
753 fn pick_sequence(
754 &mut self,
755 c: usize,
756 work: &mut Work,
757 orchard: usize,
758 point: [f32; 3],
759 fruit: Option<usize>,
760 leave: bool,
761 amount: f32,
762 cell: i32,
763 index: i32,
764 ) {
765 self.clear_citizen(c, Some(work));
766 self.on_empty_think(c);
767 self.push(
768 c,
769 Action::CompleteTask {
770 work: work.id,
771 param: index,
772 },
773 );
774 self.push(
775 c,
776 Action::SendMessage(Message {
777 component: Component::Orchard,
778 message: 1,
779 entity: Some(orchard),
780 a: index,
781 b: 0,
782 }),
783 );
784 if leave {
785 if let Some(t) = fruit {
786 let batch = self.states[c].carried.capacity;
787 self.push(
788 c,
789 Action::CreateRawMaterial {
790 template: t,
791 cell,
792 batch,
793 workplace: self.states[c].workplace,
794 factor: amount,
795 },
796 );
797 }
798 }
799 self.push(c, self.perform(c, -1000.0, 8, 1.0, true));
800 let walk = MoveTo::new(point, 1, 0, WALKABLE, 0, -1.0, self.rng);
801 self.push(c, Action::move_to(walk));
802 }
803
804 pub(crate) fn pick_done(work: &mut Work, index: i32, failed: bool) {
806 if let WorkKind::PickFruit(pf) = &mut work.kind {
807 pf.claims.remove(&index);
808 if failed {
809 if let Some(n) = pf.need.get_mut(index as usize) {
810 *n = true;
811 }
812 }
813 }
814 }
815}
816
817pub fn ripeness(
819 o: &Orchard,
820 objects: &[Option<usize>],
821 fruits: &std::collections::BTreeMap<usize, Fruit>,
822) -> f32 {
823 if o.trees.is_empty() {
824 return 1.0;
825 }
826 let sum: f32 = o
827 .trees
828 .iter()
829 .filter(|t| t.state == 1 && t.entity.is_some())
830 .filter(|t| objects[t.cell as usize] == t.entity)
831 .map(|t| {
832 let x = t.entity.unwrap();
833 fruits.get(&x).map_or(0.0, |f| f.ripeness.min(1.0))
834 })
835 .sum();
836 sum / o.trees.len() as f32
837}