1use crate::pathfind::{WALKABLE, WATER};
7use crate::random::Random;
8use crate::sectors::SectorGraph;
9use crate::terrain::CellType;
10use crate::work::stock::StorageSearchMap;
11
12pub type MapRect = (i32, i32, i32, i32);
15
16#[derive(Debug, Clone, Default)]
18pub struct EntityView {
19 pub alive: bool,
21 pub rect: Option<MapRect>,
23 pub use_points: Vec<[f32; 3]>,
25 pub has_interact: bool,
27 pub position: [f32; 3],
29 pub has_model: bool,
31 pub happiness: Option<i32>,
35}
36
37pub struct StorageGeometry<'a, 'b> {
39 pub town: &'a TownView<'b>,
41 pub sectors: &'a mut SectorGraph,
43 pub ground: &'a dyn Fn(i32) -> [f32; 3],
45}
46
47impl StorageSearchMap for StorageGeometry<'_, '_> {
48 fn entrance(&mut self, entity: usize, rng: &mut Random) -> i32 {
49 self.town.entrance(entity, rng)
50 }
51
52 fn rank(&mut self, a: i32, b: i32) -> i32 {
54 let n = self.town.size as i32;
55 self.sectors.rebuild(self.town.types);
56 let d = self.sectors.distance(
57 a,
58 b,
59 self.town.reachable(WALKABLE, a, b),
60 true,
61 self.town.types,
62 );
63 if d >= 0 {
64 d
65 } else {
66 let (dx, dy) = ((a % n - b % n) as f32, (a / n - b / n) as f32);
67 n + (dx * dx + dy * dy).sqrt() as i32
68 }
69 }
70
71 fn market_position(
73 &mut self,
74 entity: usize,
75 from: i32,
76 rng: &mut Random,
77 ) -> Option<(i32, [f32; 3])> {
78 let points = &self.town.entities[entity].use_points;
79 let k = rng.rand_below(points.len() as u32) as usize;
80 let p = *points.get(k)?;
81 let cell = self.town.cell_of(p);
82 self.town
83 .reachable(WALKABLE, from, cell)
84 .then(|| (self.rank(from, cell), p))
85 }
86
87 fn storage_position(&mut self, entity: usize, from: i32) -> Option<(i32, [f32; 3])> {
90 let e = &self.town.entities[entity];
91 let mut best = i32::MAX;
92 let mut target = None;
93 if e.has_interact {
94 for p in &e.use_points {
95 let cell = self.town.cell_of(*p);
96 if self.town.reachable(WALKABLE, from, cell) {
97 let d = self.rank(from, cell);
98 if d >= 0 && d < best {
99 best = d;
100 target = Some(*p);
101 }
102 }
103 }
104 } else if let Some((x, y, w, h)) = e.rect {
105 let n = self.town.size as i32;
106 for cy in y - 1..=y + h + 1 {
107 for cx in x - 1..x + w + 1 {
108 if cx < 0 || cy < 0 || cx >= n || cy >= n {
109 continue;
110 }
111 let cell = cy * n + cx;
112 if (1u32 << (self.town.types[cell as usize].0 & 31)) & 0xe07 != 0
113 && self.town.reachable(WALKABLE, from, cell)
114 {
115 let d = self.rank(cell, from);
116 if d < best {
117 best = d;
118 target = Some((self.ground)(cell));
119 }
120 }
121 }
122 }
123 }
124 target.map(|p| (best, p))
125 }
126}
127
128pub struct TownView<'a> {
130 pub size: usize,
132 pub types: &'a [CellType],
134 pub regions: &'a [u16],
136 pub occupants: &'a [Option<usize>],
139 pub objects: &'a [Option<usize>],
141 pub floors: &'a crate::world::floor::Floors,
144 pub entities: &'a [EntityView],
146 pub happiness: &'a [usize],
149}
150
151impl TownView<'_> {
152 pub fn cell_of(&self, p: [f32; 3]) -> i32 {
154 let n = self.size as i32;
155 let half = (n >> 1) as f32;
156 let x = ((p[0] + half) as i32).clamp(0, n - 1);
157 let y = ((p[1] + half) as i32).clamp(0, n - 1);
158 n * y + x
159 }
160
161 pub fn reachable(&self, mask: u32, a: i32, b: i32) -> bool {
165 let (ba, bb) = (
166 1u32 << (self.types[a as usize].0 & 0x1f),
167 1u32 << (self.types[b as usize].0 & 0x1f),
168 );
169 let mask = mask | ba | bb;
170 if mask & ba == 0 || mask & bb == 0 {
171 return false;
172 }
173 if mask & WATER != 0 {
174 return true;
175 }
176 let (ra, rb) = (self.regions[a as usize], self.regions[b as usize]);
177 ra != 0 && rb != 0 && ra == rb
178 }
179
180 pub fn entrance(&self, entity: usize, rng: &mut Random) -> i32 {
186 let e = &self.entities[entity];
187 if !e.use_points.is_empty() {
188 if e.use_points.len() > 1 {
189 rng.rand_below(e.use_points.len() as u32);
190 }
191 return self.cell_of(e.use_points[0]);
192 }
193 match e.rect {
194 Some((x, y, w, h)) => (y + (h >> 1)) * self.size as i32 + (w >> 1) + x,
195 None => -1,
196 }
197 }
198
199 pub fn point_cell(&self, entity: usize) -> i32 {
202 let e = &self.entities[entity];
203 if let Some(p) = e.use_points.first() {
204 return self.cell_of(*p);
205 }
206 match e.rect {
207 Some((x, y, w, h)) => (y + (h >> 1)) * self.size as i32 + (w >> 1) + x,
208 None => -1,
209 }
210 }
211
212 pub fn can_reach(&self, cell: i32, entity: usize, rng: &mut Random) -> bool {
216 let e = self.entrance(entity, rng);
217 e >= 0 && self.reachable(WALKABLE, cell, e)
218 }
219
220 fn object(&self, cell: usize) -> Option<usize> {
222 self.occupants[cell].or(self.objects[cell])
223 }
224}
225
226pub struct TownSearch<'a, 'b> {
228 pub town: &'a TownView<'b>,
230 pub cell: &'a dyn Fn(usize) -> i32,
232 pub anywhere: &'a dyn Fn(usize) -> bool,
234 pub priority: &'a dyn Fn(usize) -> i32,
236 pub rng: &'a mut Random,
238}
239
240impl crate::work::professions::Search for TownSearch<'_, '_> {
241 fn cell(&self, c: usize) -> i32 {
242 (self.cell)(c)
243 }
244 fn anywhere(&self, profession: usize) -> bool {
245 (self.anywhere)(profession)
246 }
247 fn priority(&self, workplace: usize) -> i32 {
248 (self.priority)(workplace)
249 }
250 fn reach(&mut self, cell: i32, workplace: usize) -> bool {
251 self.town.can_reach(cell, workplace, self.rng)
252 }
253 fn rng(&mut self) -> &mut Random {
254 self.rng
255 }
256}
257
258#[derive(Debug, Clone, PartialEq)]
262pub struct Loiter {
263 pub target: [f32; 3],
265 pub duration: f32,
267 pub happiness: Option<usize>,
269}
270
271pub fn loiter(
278 town: &TownView,
279 cell: i32,
280 home: Option<usize>,
281 range: i32,
282 rng: &mut Random,
283 ground: impl Fn(i32) -> [f32; 3],
284 rank: &mut dyn FnMut(i32, i32) -> i32,
285) -> Option<Loiter> {
286 let n = town.size as i32;
287 let home_cell = home.map_or(-1, |h| town.entrance(h, rng));
288 let mut chosen = town.occupants[cell as usize];
289 if rng.rand_float() < 0.25 {
290 chosen = None;
291 }
292 chosen = chosen.filter(|&e| {
295 let v = &town.entities[e];
296 v.happiness.is_some()
297 && v.use_points
298 .first()
299 .is_some_and(|&p| town.reachable(WALKABLE, cell, town.cell_of(p)))
300 });
301 if chosen.is_none() {
302 chosen = nearest_happiness(town, cell, range, rng, rank);
303 if chosen.is_none() && home_cell >= 0 {
304 chosen = nearest_happiness(town, home_cell, range, rng, rank);
305 }
306 }
307 let mut pad = chosen.and_then(|e| town.entities[e].happiness).unwrap_or(6);
308 if chosen.is_none() {
309 let mut set = std::collections::BTreeSet::new();
313 let (cx, cy) = (cell % n, cell / n);
314 for y in cy - 6..=cy + 6 {
315 for x in cx - 6..=cx + 6 {
316 if x < 0 || x >= n || y < 0 || y >= n {
317 continue;
318 }
319 let Some(e) = town.object((n * y + x) as usize) else {
320 continue;
321 };
322 if town.entities[e].rect.is_some() && town.can_reach(cell, e, rng) {
323 set.insert(e);
324 }
325 }
326 }
327 pad = 6;
328 if let Some(h) = home {
329 if town.can_reach(cell, h, rng) {
330 set.insert(h);
331 }
332 }
333 if !set.is_empty() {
334 let k = rng.rand_below(set.len() as u32) as usize;
335 chosen = set.iter().nth(k).copied();
336 }
337 }
338 let (happy, (x0, y0, w, h)) = match chosen {
339 None => (None, (cell % n, cell / n, 1, 1)),
340 Some(e) => {
341 let v = &town.entities[e];
342 (
343 v.happiness.map(|_| e),
344 v.rect.unwrap_or((cell % n, cell / n, 1, 1)),
345 )
346 }
347 };
348 let mut cells = std::collections::BTreeSet::new();
351 for y in y0 - pad..y0 - pad + h + 2 * pad {
352 for x in x0 - pad..x0 - pad + w + 2 * pad {
353 if x < 0 || x >= n || y < 0 || y >= n {
354 continue;
355 }
356 let c = n * y + x;
357 if town.types[c as usize].0 < 3 && town.reachable(WALKABLE, cell, c) {
358 cells.insert(c);
359 }
360 }
361 }
362 if cells.is_empty() {
363 return None;
364 }
365 let k = rng.rand_below(cells.len() as u32) as usize;
366 let spot = *cells.iter().nth(k).unwrap();
367 let base = ground(spot);
368 let dy = rng.rand_float_tol() * 0.33;
370 let dx = rng.rand_float_tol() * 0.33;
371 let duration = rng.rand_float_tol() * 5.0 + 10.0;
373 Some(Loiter {
374 target: [base[0] + dx, base[1] + dy, base[2]],
375 duration,
376 happiness: happy,
377 })
378}
379
380fn nearest_happiness(
384 town: &TownView,
385 cell: i32,
386 range: i32,
387 rng: &mut Random,
388 rank: &mut dyn FnMut(i32, i32) -> i32,
389) -> Option<usize> {
390 let mut found = Vec::new();
391 for &e in town.happiness {
392 let point = town.point_cell(e);
393 if point < 0 {
394 continue;
395 }
396 if rank(cell, point) < range * 2
397 && town.reachable(WALKABLE, cell, point)
398 && town.entities[e].happiness.is_some()
399 {
400 found.push(e);
401 }
402 }
403 if found.is_empty() {
404 return None;
405 }
406 Some(found[rng.rand_below(found.len() as u32) as usize])
407}