banished_game/production/
forester.rs1use crate::behaviour::character::{Action, MoveTo};
10use crate::nature::seeding::Seeder;
11use crate::pathfind::{nearest_neighbour, WALKABLE};
12use crate::production::herbalist::HerbalistWork;
13use crate::scheduler::ticks;
14use crate::town::Sim;
15use crate::work::jobs::{Work, WorkKind};
16
17#[derive(Debug, Clone, Default, PartialEq)]
19pub struct ForesterTemplate {
20 pub tree: usize,
23 pub group: i32,
26}
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
31pub struct Forester {
32 pub plant: bool,
34 pub cut: bool,
36}
37
38impl Sim<'_, '_> {
39 pub(crate) fn forester_work(&mut self, e: usize) -> Work {
42 if let Some(site) = self.sites.get_mut(&e) {
43 let m = &site.machine;
44 let state = if m.pending < 0 { m.state } else { m.pending };
45 if let Some(f) = &mut site.forester {
46 if state != 0xe && !f.plant && !f.cut {
47 *f = Forester {
48 plant: true,
49 cut: true,
50 };
51 }
52 }
53 }
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::Forester(HerbalistWork::default()),
65 )
66 }
67
68 pub(crate) fn forester_try(&mut self, work: &mut Work, c: usize) -> u8 {
70 let e = work.entity;
71 let Some(site) = self.sites.get(&e) else {
72 return 0;
73 };
74 let toggles = site.forester.unwrap_or_default();
75 let t = &self.site_templates[site.template];
76 let Some(desc) = t.forester.clone() else {
77 return 0;
78 };
79 let radius = t.radius.unwrap_or(0);
80 let group = desc.group.clamp(0, 20) as usize;
81 let (count, limit) = self.stock[group];
82 self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
83 let WorkKind::Forester(h) = &work.kind else {
84 return 0;
85 };
86 if self.work_max(e) < h.workers.len() as i32 {
87 return 1;
88 }
89 let n = self.size as i32;
90 let r2 = radius * radius;
91 let attempts = (r2 as f32 * std::f32::consts::PI).sqrt() as i32;
92 let (cx, cy) = (work.cell % n, work.cell / n);
93 let from = self.citizen_cell(c);
94 let entrance = self.entrance_cell(e);
95 let in_map = |cell: i32| 0 <= cell && cell < n * n;
96 for _ in 0..attempts {
97 let (x, y) = loop {
98 let (dx, dy) = loop {
99 let dx = self.rng.rand_tol(radius);
100 let dy = self.rng.rand_tol(radius);
101 if dx * dx + dy * dy <= r2 {
102 break (dx, dy);
103 }
104 };
105 let (x, y) = (cx + dx, cy + dy);
107 if 0 <= y && y < n {
108 break (x, y);
109 }
110 };
111 let cell = n * y + x;
112 if !in_map(cell) || !in_map(entrance) {
115 continue;
116 }
117 let (ci, ei) = (cell as usize, entrance as usize);
118 let bit = |ty: u8| 1u32 << (ty & 0x1f);
119 let (cb, eb) = (bit(self.cells.types[ci].0), bit(self.cells.types[ei].0));
120 if cb & 0x1e0 != 0 {
121 continue;
122 }
123 let (rc, re) = (self.regions[ci], self.regions[ei]);
125 let joined = (cb | eb) & 0x1e0 != 0 || (rc != 0 && re != 0 && rc == re);
126 if !joined || self.cells.occupants[ci].is_some() {
127 continue;
128 }
129 if self.geometry_rank(entrance, cell) > radius * 2 {
130 continue;
131 }
132 let object = self.cells.objects[ci];
133 if toggles.plant && object.is_none() {
134 if !self.reachable(cell, from) {
135 continue;
136 }
137 let admits = crate::world::placement::cell_admits(
138 self.cells,
139 self.terrain,
140 x,
141 y,
142 4,
143 0.97,
144 1000.0,
145 0.0,
146 false,
147 false,
148 |_| false,
149 );
150 if !admits || !self.seeder().density_ok(desc.tree, x, y) {
151 continue;
152 }
153 let point = self.ground_at(cell);
154 self.plant_sequence(c, work, desc.tree, point, cell, None, None, 0);
155 return self.forester_took(work, c);
156 }
157 let Some(o) = object else { continue };
158 if !toggles.cut {
159 continue;
160 }
161 let template = self.placed[o].template;
162 if self.templates[template].natural.is_none() {
163 continue;
164 }
165 let (count, limit) = self.stock[group];
166 if count >= limit {
167 continue;
168 }
169 let flags = self.templates[template]
170 .pile
171 .and_then(|p| self.templates[p].pile_material)
172 .map_or(0, |m| self.materials[m].flags);
173 if flags & 0x20 != 0 || flags & 0x2000 != 0 {
174 continue;
175 }
176 if self.placed[o].growth < 1.0 || self.claimed.contains(&o) {
177 continue;
178 }
179 let Some(nb) = nearest_neighbour(self.size, &self.cells.types, cell, from, 0xe07)
180 else {
181 continue;
182 };
183 if !self.reachable(nb, from) {
184 continue;
185 }
186 let point = self.ground_at(nb);
187 self.removal_sequence(c, work, o, point, true);
188 return self.forester_took(work, c);
189 }
190 let misses = match &work.kind {
191 WorkKind::Forester(h) => h.misses,
192 _ => 0,
193 };
194 if attempts < misses {
195 if self.states[c].carried.used(self.materials) < 1 {
196 return 0;
197 }
198 self.drop_off(c);
199 return 2;
200 }
201 self.wait_sequence(c, work, 0);
202 self.took_job(c, work);
203 if let WorkKind::Forester(h) = &mut work.kind {
204 h.workers.insert(c);
205 h.misses += 1;
206 }
207 2
208 }
209
210 fn forester_took(&mut self, work: &mut Work, c: usize) -> u8 {
211 self.took_job(c, work);
212 if let WorkKind::Forester(h) = &mut work.kind {
213 h.misses = 0;
214 h.workers.insert(c);
215 }
216 2
217 }
218
219 #[allow(clippy::too_many_arguments)]
223 pub(crate) fn plant_sequence(
224 &mut self,
225 c: usize,
226 work: &mut Work,
227 tree: usize,
228 point: [f32; 3],
229 cell: i32,
230 occupant: Option<usize>,
231 message: Option<crate::behaviour::character::Message>,
232 param: i32,
233 ) {
234 self.clear_citizen(c, Some(work));
235 self.on_empty_think(c);
236 self.push(
237 c,
238 Action::CompleteTask {
239 work: work.id,
240 param,
241 },
242 );
243 if let Some(m) = message {
244 self.push(c, Action::SendMessage(m));
245 }
246 self.push(c, Action::ShowTool(-1));
247 self.push(
248 c,
249 Action::CreateNaturalResource {
250 template: tree,
251 occupant,
252 cell,
253 },
254 );
255 self.push(c, self.perform(c, -1000.0, 0xc, 2.1, true));
256 let walk = MoveTo::new(point, 3, 0, WALKABLE, 0, -1.0, self.rng);
257 self.push(c, Action::move_to(walk));
258 self.push(c, Action::ShowTool(3));
259 }
260
261 pub(crate) fn wait_sequence(&mut self, c: usize, work: &mut Work, param: i32) {
263 self.clear_citizen(c, Some(work));
264 self.on_empty_think(c);
265 self.push(
266 c,
267 Action::CompleteTask {
268 work: work.id,
269 param,
270 },
271 );
272 self.push(
273 c,
274 Action::PlayAnimation {
275 anim: 0,
276 duration: ticks(0.25),
277 elapsed: 0,
278 },
279 );
280 }
281
282 pub(crate) fn seeder(&mut self) -> Seeder<'_> {
284 self.removed.clear();
285 Seeder {
286 templates: self.templates,
287 terrain: self.terrain,
288 cells: self.cells,
289 rng: self.rng,
290 placed: self.placed,
291 removed: &mut self.removed,
292 animation_events: &mut self.animation_events,
293 }
294 }
295
296 pub fn plant(&mut self, template: usize, cell: i32) {
298 self.seeder().plant(template, cell);
299 *self.models_dirty = true;
300 }
301}