banished_game/production/
herbalist.rs1use std::collections::BTreeSet;
14
15use crate::pathfind::nearest_neighbour;
16use crate::town::Sim;
17use crate::work::jobs::{Work, WorkKind};
18
19#[derive(Debug, Clone, Default, PartialEq)]
22pub struct HerbalistWork {
23 pub workers: BTreeSet<usize>,
25 pub misses: i32,
27}
28
29#[derive(Debug, Clone, Default, PartialEq)]
33pub struct AutoPickup {
34 pub enabled: bool,
36 pub works: Vec<i32>,
38}
39
40#[derive(Debug, Clone, Default, PartialEq)]
42pub struct HerbalistTemplate {
43 pub resources: Vec<usize>,
46 pub group: i32,
49}
50
51impl Sim<'_, '_> {
52 pub(crate) fn herbalist_work(&self, e: usize) -> Work {
54 let profession = self
55 .workforce
56 .workplaces
57 .get(&e)
58 .and_then(|w| w.profession)
59 .unwrap_or(self.professions.laborer);
60 Work::new(
61 e,
62 self.rect_cell(e),
63 profession,
64 WorkKind::Herbalist(HerbalistWork::default()),
65 )
66 }
67
68 pub(crate) fn auto_pickup_offers(&mut self, e: usize, exists: impl Fn(i32) -> bool) -> bool {
72 let Some(a) = self.sites.get_mut(&e).and_then(|s| s.auto_pickup.as_mut()) else {
73 return false;
74 };
75 if !a.enabled {
76 return false;
77 }
78 a.works.retain(|&id| exists(id));
79 if a.works.is_empty() {
80 return false;
81 }
82 self.offers = a.works.clone();
83 self.offers_again = true;
84 true
85 }
86
87 pub(crate) fn auto_pickup_add(&mut self, e: usize, work: i32) {
89 if let Some(a) = self.sites.get_mut(&e).and_then(|s| s.auto_pickup.as_mut()) {
90 if a.enabled {
91 a.works.push(work);
92 }
93 }
94 }
95
96 pub(crate) fn herbalist_try(&mut self, work: &mut Work, c: usize) -> u8 {
98 let e = work.entity;
99 let Some(site) = self.sites.get(&e) else {
100 return 0;
101 };
102 let t = &self.site_templates[site.template];
103 let Some(desc) = t.herbalist.clone() else {
104 return 0;
105 };
106 let radius = t.radius.unwrap_or(0);
107 let (count, limit) = self.stock[desc.group.clamp(0, 20) as usize];
109 let limited = limit <= count;
110 self.set_icon(e, crate::work::professions::icon::LIMIT, limited);
111 let WorkKind::Herbalist(h) = &work.kind else {
112 return 0;
113 };
114 if self.work_max(e) < h.workers.len() as i32 || limited {
115 return 1;
116 }
117 let n = self.size as i32;
118 let r2 = radius * radius;
119 let attempts = (r2 as f32 * std::f32::consts::PI).sqrt() as i32;
120 let (cx, cy) = (work.cell % n, work.cell / n);
121 let from = self.citizen_cell(c);
122 let entrance = self.entrance_cell(e);
123 let mut hit = None;
124 'draws: for _ in 0..attempts {
125 let (x, y) = loop {
126 let (dx, dy) = loop {
127 let dx = self.rng.rand_tol(radius);
128 let dy = self.rng.rand_tol(radius);
129 if dx * dx + dy * dy <= r2 {
130 break (dx, dy);
131 }
132 };
133 let (x, y) = (cx + dx, cy + dy);
136 if 0 <= y && y < n {
137 break (x, y);
138 }
139 };
140 let cell = n * y + x;
141 if cell < 0 || cell >= n * n {
144 continue;
145 }
146 let ty = self.cells.types[cell as usize].0;
147 if (1u32 << (ty & 0x1f)) & 0x1e0 != 0 {
148 continue;
149 }
150 if self.geometry_rank(entrance, cell) > radius * 2 {
151 continue;
152 }
153 let Some(o) = self.cells.objects[cell as usize] else {
154 continue;
155 };
156 let p = &self.placed[o];
157 if self.templates[p.template].natural.is_none() {
158 continue;
159 }
160 for &kind in &desc.resources {
161 if p.growth < 1.0 || p.template != kind || self.claimed.contains(&o) {
162 continue;
163 }
164 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, from, 0xe07)
165 else {
166 continue;
167 };
168 if !self.reachable(nb, from) {
169 continue;
170 }
171 hit = Some((o, nb));
172 break 'draws;
173 }
174 }
175 if let Some((o, nb)) = hit {
176 let point = self.ground_at(nb);
177 self.removal_sequence(c, work, o, point, true);
178 self.took_job(c, work);
179 if let WorkKind::Herbalist(h) = &mut work.kind {
180 h.misses = 0;
181 h.workers.insert(c);
182 }
183 return 2;
184 }
185 let misses = match &work.kind {
186 WorkKind::Herbalist(h) => h.misses,
187 _ => 0,
188 };
189 if attempts < misses {
190 if self.states[c].carried.used(self.materials) < 1 {
192 return 0;
193 }
194 self.drop_off(c);
195 return 2;
196 }
197 self.wait_sequence(c, work, 0);
198 self.took_job(c, work);
199 if let WorkKind::Herbalist(h) = &mut work.kind {
200 h.workers.insert(c);
201 h.misses += 1;
202 }
203 2
204 }
205
206 pub(crate) fn entrance_cell(&mut self, e: usize) -> i32 {
208 let town = crate::behaviour::activities::TownView {
209 size: self.size,
210 types: &self.cells.types,
211 regions: self.regions,
212 occupants: &self.cells.occupants,
213 objects: &self.cells.objects,
214 floors: &self.cells.floors,
215 entities: self.entities,
216 happiness: self.happiness,
217 };
218 town.entrance(e, self.rng)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 #[test]
225 fn attempts_follow_the_disc_area() {
226 let r2 = 20 * 20;
228 assert_eq!((r2 as f32 * std::f32::consts::PI).sqrt() as i32, 35);
229 }
230}