banished_game/production/
fishing.rs1use crate::behaviour::character::{Action, MoveTo};
13use crate::pathfind::WALKABLE;
14use crate::town::Sim;
15use crate::work::jobs::{Work, WorkKind};
16
17#[derive(Debug, Clone, Default, PartialEq)]
21pub struct FishingTemplate {
22 pub output: usize,
25 pub mask: u32,
28 pub group: i32,
31}
32
33#[derive(Debug, Clone, Copy, Default, PartialEq)]
36pub struct Fishing {
37 pub registered: bool,
39 pub water: f32,
41}
42
43#[derive(Debug, Clone, Default, PartialEq)]
47pub struct FishingWork {
48 pub workers: Vec<Option<usize>>,
50 pub points: Vec<i32>,
52 pub misses: i32,
54}
55
56pub const LAYER: usize = 8;
58
59impl Sim<'_, '_> {
60 fn fishing_template(&self, e: usize) -> Option<(FishingTemplate, i32)> {
61 let t = &self.site_templates[self.sites.get(&e)?.template];
62 Some((t.fishing.clone()?, t.radius.unwrap_or(0)))
63 }
64
65 fn rect_centre(&self, e: usize) -> (i32, i32) {
67 let (x, y) = self.placed[e].cell;
68 let (w, h) = self.placed[e].footprint;
69 ((w >> 1) + x, (h >> 1) + y)
70 }
71
72 pub(crate) fn fishing_work(&self, e: usize) -> Work {
74 let t = &self.templates[self.placed[e].template];
75 let profession = self
76 .workforce
77 .workplaces
78 .get(&e)
79 .and_then(|w| w.profession)
80 .unwrap_or(self.professions.laborer);
81 Work::new(
82 e,
83 self.rect_cell(e),
84 profession,
85 WorkKind::Fishing(FishingWork {
86 workers: vec![None; t.use_points.len()],
87 points: vec![0; t.create_points.len()],
88 misses: 0,
89 }),
90 )
91 }
92
93 pub(crate) fn fishing_register(&mut self, e: usize) {
96 let Some(f) = self.sites.get(&e).and_then(|s| s.fishing) else {
97 return;
98 };
99 if f.registered {
100 return;
101 }
102 self.fishing_layer(e, 1);
103 self.fishing_water(e);
104 if let Some(f) = self.sites.get_mut(&e).and_then(|s| s.fishing.as_mut()) {
105 f.registered = true;
106 }
107 }
108
109 pub(crate) fn fishing_unregister(&mut self, e: usize) {
111 if !self
112 .sites
113 .get(&e)
114 .and_then(|s| s.fishing)
115 .is_some_and(|f| f.registered)
116 {
117 return;
118 }
119 self.fishing_layer(e, -1);
120 if let Some(f) = self.sites.get_mut(&e).and_then(|s| s.fishing.as_mut()) {
121 f.registered = false;
122 }
123 }
124
125 fn fishing_layer(&mut self, e: usize, delta: i32) {
127 let Some((_, radius)) = self.fishing_template(e) else {
128 return;
129 };
130 let (cx, cy) = self.rect_centre(e);
131 self.cells.add_layer(LAYER, cx, cy, radius, delta);
132 }
133
134 pub(crate) fn fishing_water(&mut self, e: usize) {
137 let Some((d, r)) = self.fishing_template(e) else {
138 return;
139 };
140 let (cx, cy) = self.rect_centre(e);
141 let n = self.size as i32;
142 let (mut cells, mut water) = (0, 0);
143 for dx in -r..r {
144 for dy in -r..r {
145 let (x, y) = (cx + dx, cy + dy);
146 if dx * dx + dy * dy > r * r || x < 0 || y < 0 || x >= n || y >= n {
147 continue;
148 }
149 cells += 1;
150 let ty = self.cells.types[(n * y + x) as usize].0;
151 if d.mask >> (ty & 0x1f) & 1 != 0 {
152 water += 1;
153 }
154 }
155 }
156 if let Some(f) = self.sites.get_mut(&e).and_then(|s| s.fishing.as_mut()) {
157 f.water = water as f32 / (cells as f32 * 0.5);
158 }
159 }
160
161 pub(crate) fn fishing_try(&mut self, work: &mut Work, c: usize) -> u8 {
163 let e = work.entity;
164 let Some((d, radius)) = self.fishing_template(e) else {
165 return 0;
166 };
167 let water = self.sites[&e].fishing.unwrap_or_default().water;
168 let (count, limit) = self.stock[d.group.clamp(0, 20) as usize];
169 self.set_icon(e, crate::work::professions::icon::LIMIT, limit <= count);
170 if limit <= count {
171 return 1;
172 }
173 let WorkKind::Fishing(fw) = &mut work.kind else {
174 return 0;
175 };
176 let Some(slot) = fw.workers.iter().position(Option::is_none) else {
177 return 1;
178 };
179 let mut free = None;
181 for i in 0..fw.points.len() {
182 let cell = self.create_cell(e, i);
183 if fw.points[i] == 2 && self.cells.objects[cell as usize].is_none() {
184 fw.points[i] = 0;
185 }
186 if free.is_none() && fw.points[i] == 0 {
187 free = Some((i, cell));
188 }
189 }
190 let Some((point, out_cell)) = free else {
191 let n = fw.points.len();
193 self.offers = (0..n)
194 .filter_map(|i| self.cells.objects[self.create_cell(e, i) as usize])
195 .filter_map(|o| self.piles.get(&o).map(|p| p.work))
196 .collect();
197 if self.offers.is_empty() {
198 return 1;
199 }
200 self.offers_again = true;
201 return crate::work::jobs::NESTED;
202 };
203 let (pos, yaw) = self.use_point(e, slot);
204 if !self.reachable(self.citizen_cell(c), self.cell_of(pos)) {
205 return 1;
206 }
207 let n = self.size as i32;
208 let r2 = radius * radius;
209 let (cx, cy) = (work.cell % n, work.cell / n);
210 for _ in 0..10 {
211 let (x, y) = loop {
212 let (dx, dy) = loop {
213 let dx = self.rng.rand_tol(radius);
214 let dy = self.rng.rand_tol(radius);
215 if dx * dx + dy * dy <= r2 {
216 break (dx, dy);
217 }
218 };
219 let (x, y) = (cx + dx, cy + dy);
221 if 0 <= y && y < n {
222 break (x, y);
223 }
224 };
225 let cell = n * y + x;
226 if cell < 0 || cell >= n * n {
229 continue;
230 }
231 let i = cell as usize;
232 let docks = self.cells.layers[i][LAYER];
233 if d.mask >> (self.cells.types[i].0 & 0x1f) & 1 == 0 || docks == 0 {
234 continue;
235 }
236 let factor = 1.0 / docks as f32 * water;
237 let param = ((point as i32 | 0x800) << 4) | slot as i32;
238 self.fishing_sequence(c, work, pos, yaw, d.output, factor, out_cell, param);
239 self.took_job(c, work);
240 if let WorkKind::Fishing(fw) = &mut work.kind {
241 fw.points[point] = 1;
242 fw.workers[slot] = Some(c);
243 fw.misses = 0;
244 }
245 return 2;
246 }
247 let misses = match &work.kind {
248 WorkKind::Fishing(fw) => fw.misses,
249 _ => 0,
250 };
251 if misses < 0xb {
252 self.wait_sequence(c, work, slot as i32);
253 self.took_job(c, work);
254 if let WorkKind::Fishing(fw) = &mut work.kind {
255 fw.workers[slot] = Some(c);
256 fw.misses += 1;
257 }
258 return 2;
259 }
260 if self.states[c].carried.used(self.materials) < 1 {
261 return 1;
262 }
263 self.drop_off(c);
264 2
265 }
266
267 #[allow(clippy::too_many_arguments)]
271 fn fishing_sequence(
272 &mut self,
273 c: usize,
274 work: &mut Work,
275 pos: [f32; 3],
276 yaw: f32,
277 output: usize,
278 factor: f32,
279 out_cell: i32,
280 param: i32,
281 ) {
282 self.clear_citizen(c, Some(work));
283 self.on_empty_think(c);
284 self.push(c, Action::ShowTool(-1));
285 self.push(
286 c,
287 Action::CompleteTask {
288 work: work.id,
289 param,
290 },
291 );
292 self.push(
293 c,
294 Action::SendMessage(crate::behaviour::character::Message {
295 component: crate::behaviour::character::Component::Fishing,
296 message: 0,
297 entity: Some(work.entity),
298 a: out_cell,
299 b: 0,
300 }),
301 );
302 let batch = self.states[c].carried.capacity;
303 self.push(
304 c,
305 Action::CreateRawMaterial {
306 template: output,
307 cell: out_cell,
308 batch,
309 workplace: Some(work.entity),
310 factor,
311 },
312 );
313 self.push(c, self.perform(c, yaw, 0xe, 30.0, true));
314 let walk = MoveTo::new(pos, 2, 0, WALKABLE, 0, -1.0, self.rng);
315 self.push(c, Action::move_to(walk));
316 self.push(c, Action::ShowTool(5));
317 }
318
319 pub(crate) fn fishing_complete(work: &mut Work, param: i32) {
322 let WorkKind::Fishing(fw) = &mut work.kind else {
323 return;
324 };
325 if let Some(w) = fw.workers.get_mut((param & 0xf) as usize) {
326 *w = None;
327 }
328 if param >> 15 & 1 != 0 {
329 if let Some(p) = fw.points.get_mut(((param >> 4) & 0xf) as usize) {
330 *p = 2;
331 }
332 }
333 }
334
335 pub(crate) fn fishing_refresh(&mut self, work: &mut Work) {
338 let id = work.id;
339 let WorkKind::Fishing(fw) = &mut work.kind else {
340 return;
341 };
342 let mut lost = false;
343 let mut held = Vec::new();
344 for w in fw.workers.iter_mut() {
345 let Some(c) = *w else { continue };
346 match self.characters[c].stack.holds_task(id) {
347 None => {
348 *w = None;
349 lost = true;
350 }
351 Some(param) => held.push((param >> 4) & 0xf),
352 }
353 }
354 if lost {
355 for (i, p) in fw.points.iter_mut().enumerate() {
356 if !held.contains(&(i as i32)) && *p == 1 {
357 *p = 2;
358 }
359 }
360 }
361 }
362}