1use crate::behaviour::character::Character;
15use crate::pathfind::Pathfinder;
16use crate::town::Sim;
17
18const NEAR: f32 = 3.0;
20const AHEAD: f32 = 6.0;
21const SLOW: f32 = 0.125;
22const SLOWEST: f32 = 0.33;
23const DOCKED: f32 = 0.2;
24const APPROACH: f32 = 64.0;
26pub const DOCKED_MESSAGE: i32 = 9;
28pub const GONE_MESSAGE: i32 = 0xb;
30
31#[derive(Debug, Clone, Copy)]
33pub struct BoatKind {
34 pub character: banished_formats::game::CharacterDescription,
36}
37
38pub struct Boat {
43 pub template: usize,
45 pub character: Character,
47 pub state: i8,
49 pub post: Option<usize>,
51 pub dock: [f32; 3],
53 pub dock_yaw: f32,
55 pub target: [f32; 3],
57 pub route: Vec<[f32; 3]>,
59 pub index: i32,
61}
62
63fn ahead(route: &[[f32; 3]], index: i32, p: [f32; 3], d: f32) -> Option<i32> {
66 let mut i = index;
67 loop {
68 i += 1;
69 if route.len() as i32 <= i {
70 return None;
71 }
72 let q = route[i as usize];
73 let (dx, dy, dz) = (p[0] - q[0], p[1] - q[1], p[2]);
74 if d * d <= dx * dx + dy * dy + dz * dz {
75 return Some(i);
76 }
77 }
78}
79
80fn length(v: [f32; 3]) -> f32 {
82 crate::math::length3(v)
83}
84
85fn normalize(v: [f32; 3]) -> [f32; 3] {
87 crate::math::normalize3(v)
88}
89
90fn heading(v: [f32; 3]) -> f32 {
92 crate::behaviour::character::heading([v[0], v[1]])
93}
94
95pub fn route(
102 size: usize,
103 terrain: &crate::terrain::HeightGrid,
104 floors: &crate::world::floor::Floors,
105 pathfinder: &mut Pathfinder,
106 river: &[[f32; 2]],
107 dock: [f32; 3],
108 dock_yaw: f32,
109) -> (Vec<[f32; 3]>, f32) {
110 let n = size as i32;
111 let half = (n >> 1) as f32;
112 let inside = |p: [f32; 2]| {
113 let (x, y) = ((p[0] + half) as i32, (p[1] + half) as i32);
114 0 <= x && x < n && 0 <= y && y < n
115 };
116 let cell = |p: [f32; 2]| {
117 let x = ((p[0] + half) as i32).clamp(0, n - 1);
118 let y = ((p[1] + half) as i32).clamp(0, n - 1);
119 y * n + x
120 };
121 let mut route = Vec::new();
122 let mut i = 0;
123 let mut entry = [0.0; 2];
124 while i < river.len() {
125 let p = river[i];
126 i += 1;
127 if inside(p) {
128 entry = p;
129 break;
130 }
131 route.push([p[0], p[1], 0.0]);
132 }
133 let mut exit = entry;
135 let mut tail = i;
136 while tail < river.len() {
137 let p = river[tail];
138 tail += 1;
139 if !inside(p) {
140 break;
141 }
142 exit = p;
143 }
144 pathfinder.set_mode(3);
145 pathfinder.mask = 0x380;
146 let dock_cell = cell([dock[0], dock[1]]);
147 let into = pathfinder.find(cell(entry), dock_cell).unwrap_or_default();
148 let out = pathfinder.find(dock_cell, cell(exit)).unwrap_or_default();
149 if !into.is_empty() && !out.is_empty() {
150 for c in into.into_iter().chain(out) {
151 route.push(crate::town::floor_ground(floors, size, terrain, c));
153 }
154 }
155 for p in &river[tail.min(river.len())..] {
156 route.push([p[0], p[1], 0.0]);
157 }
158 let mut yaw = dock_yaw;
160 if 2 <= route.len() {
161 let d2 = |q: [f32; 3]| {
162 let v = [dock[0] - q[0], dock[1] - q[1], dock[2] - q[2]];
163 v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
164 };
165 let mut k = 0;
166 let mut best = d2(route[0]);
167 for (j, &q) in route.iter().enumerate() {
168 let d = d2(q);
169 if d < best {
170 best = d;
171 k = j;
172 }
173 }
174 let a = route[k];
175 let b = route[k.saturating_sub(2)];
176 let flow = normalize([a[0] - b[0], a[1] - b[1], 0.0]);
177 let mut dir = crate::math::from_yaw_pitch(dock_yaw, 0.0);
178 if flow[0] * dir[0] + flow[1] * dir[1] < 0.0 {
179 dir = [-dir[0], -dir[1], 0.0];
180 }
181 yaw = heading(dir);
182 }
183 (route, yaw)
184}
185
186impl Sim<'_, '_> {
187 fn boat_group(
188 &self,
189 id: usize,
190 ) -> Option<std::sync::Arc<banished_formats::types::AnimationGroup>> {
191 let b = self.wildlife.boats.get(&id)?;
192 self.wildlife.groups.get(&(b.template, 0)).cloned()
193 }
194
195 fn boat_clip(&mut self, id: usize, clip: usize) {
197 let Some(group) = self.boat_group(id) else {
198 return;
199 };
200 if let Some(b) = self.wildlife.boats.get_mut(&id) {
201 b.character.play(clip, &group, 1.0);
202 }
203 }
204
205 pub(crate) fn add_boat(&mut self, template: usize, post: usize) -> Option<usize> {
209 let kind = *self.wildlife.boat_kinds.get(&template)?;
210 let (dock, dock_yaw) = self.named_point(post, "boat")?;
212 let (route, yaw) = route(
213 self.size,
214 self.terrain,
215 &self.cells.floors,
216 self.pathfinder,
217 self.river,
218 dock,
219 dock_yaw,
220 );
221 let start = *route.first()?;
222 let id = self.placed.len();
223 let t = &self.templates[template];
224 self.placed.push(crate::nature::seeding::Placed {
225 template,
226 bucket: t.bucket,
227 cell: (0, 0),
228 footprint: (1, 1),
229 position: start,
230 yaw: 0.0,
231 variant: 0,
232 mesh: usize::MAX,
233 growth: 0.0,
234 growth_state: None,
235 spawn_state: None,
236 alive: true,
237 removal_requested: false,
238 animation: None,
239 scale: [1.0; 4],
240 });
241 let mut boat = Boat {
242 template,
243 character: Character::new(kind.character, start, 1.0),
244 state: 0,
245 post: Some(post),
246 dock,
247 dock_yaw: yaw,
248 target: start,
249 route,
250 index: 0,
251 };
252 if length([boat.target[0] - start[0], boat.target[1] - start[1], 0.0]) < NEAR {
253 if let Some(i) = ahead(&boat.route, boat.index, start, AHEAD) {
254 boat.index = i;
255 let q = boat.route[i as usize];
256 boat.target = [q[0], q[1], 0.0];
257 }
258 }
259 let h = heading([boat.target[0] - start[0], boat.target[1] - start[1], 0.0]);
260 boat.character.yaw = h + kind.character.yaw_offset;
261 boat.character.target_heading = h;
262 boat.character.tree_slot = self.wildlife.tree.insert(
263 crate::behaviour::character_tree::Who::Boat(id),
264 boat.character.position,
265 boat.character.desc.radius,
266 );
267 self.wildlife.boats.insert(id, boat);
268 self.boat_clip(id, 1);
269 *self.models_dirty = true;
270 Some(id)
271 }
272
273 fn named_point(&self, e: usize, name: &str) -> Option<([f32; 3], f32)> {
275 let p = &self.placed[e];
276 let t = &self.templates[p.template];
277 let (local, yaw) = t
278 .named_points
279 .iter()
280 .find(|(n, _, _)| n == name)
281 .map(|(_, p, y)| (*p, *y))?;
282 let model = crate::math::Transform::of_model(p.yaw, p.position);
284 Some((model.apply(local), model.heading_of(yaw)))
285 }
286
287 fn boat_sail(&mut self, id: usize) -> bool {
290 let Some(b) = self.wildlife.boats.get_mut(&id) else {
291 return false;
292 };
293 let p = b.character.position;
294 let mut ok = true;
295 if length([b.target[0] - p[0], b.target[1] - p[1], b.target[2] - p[2]]) < NEAR {
296 match ahead(&b.route, b.index, p, AHEAD) {
297 Some(i) => {
298 b.index = i;
299 let q = b.route[i as usize];
300 b.target = [q[0], q[1], 0.0];
301 }
302 None => ok = false,
303 }
304 }
305 if ok {
306 let d = normalize([b.target[0] - p[0], b.target[1] - p[1], b.target[2] - p[2]]);
307 let dir = normalize([d[0], d[1], 0.0]);
308 b.character.flag_28 = false;
309 b.character.direction = dir;
310 b.character.target_heading = heading(dir);
311 }
312 ok
313 }
314
315 fn boat_state(&mut self, id: usize, state: i8) {
316 let Some(b) = self.wildlife.boats.get_mut(&id) else {
317 return;
318 };
319 b.state = state;
320 let clip = match state {
321 1 => 2,
322 2 => 0,
323 3 => 3,
324 _ => 1,
325 };
326 self.boat_clip(id, clip);
327 if state == 2 {
328 if let Some(post) = self.wildlife.boats.get(&id).and_then(|b| b.post) {
330 self.trade_message(post, DOCKED_MESSAGE);
331 }
332 }
333 }
334
335 fn remove_boat(&mut self, id: usize) {
336 if let Some(boat) = self.wildlife.boats.remove(&id) {
337 self.wildlife.tree.remove(boat.character.tree_slot);
338 self.placed[id].alive = false;
339 *self.models_dirty = true;
340 }
341 }
342
343 pub fn update_boats(&mut self) {
349 let ids: Vec<usize> = self.wildlife.boats.keys().copied().collect();
350 for id in ids {
351 let Some(state) = self.wildlife.boats.get(&id).map(|b| b.state) else {
352 continue;
353 };
354 match state {
355 0 => {
356 if !self.boat_sail(id) {
357 let post = self.wildlife.boats[&id].post;
358 self.remove_boat(id);
359 if let Some(post) = post {
360 self.trade_message(post, GONE_MESSAGE);
361 }
362 continue;
363 }
364 let b = &self.wildlife.boats[&id];
365 let p = b.character.position;
366 let v = [p[0] - b.dock[0], p[1] - b.dock[1], p[2] - b.dock[2]];
367 if v[0] * v[0] + v[1] * v[1] + v[2] * v[2] < APPROACH {
368 self.boat_state(id, 1);
369 }
370 }
371 1 => {
372 let b = self.wildlife.boats.get_mut(&id).unwrap();
373 let p = b.character.position;
374 let v = [b.dock[0] - p[0], b.dock[1] - p[1], b.dock[2] - p[2]];
375 let len = length(v);
376 let factor = (len * SLOW).clamp(SLOWEST, 1.0);
377 b.character.flag_28 = true;
379 b.character.direction = normalize(v);
380 b.character.target_heading = b.dock_yaw;
381 b.character.speed = b.character.scale * factor;
382 if len < DOCKED {
383 self.boat_state(id, 2);
384 }
385 }
386 3 => {
387 let b = self.wildlife.boats.get_mut(&id).unwrap();
388 let p = b.character.position;
389 let v = [b.target[0] - p[0], b.target[1] - p[1], b.target[2] - p[2]];
390 let len = length(v);
391 let dir = normalize([v[0], v[1], 0.0]);
392 b.character.flag_28 = false;
393 b.character.direction = dir;
394 b.character.target_heading = heading(dir);
395 if len < NEAR {
396 self.boat_state(id, 4);
397 }
398 }
399 4 if !self.boat_sail(id) => self.remove_boat(id),
400 _ => {}
401 }
402 }
403 }
404
405 pub(crate) fn boat_message(&mut self, id: usize, msg: i32) {
408 let Some(state) = self.wildlife.boats.get(&id).map(|b| b.state) else {
409 return;
410 };
411 match (state, msg) {
412 (2, 0) => self.boat_state(id, 3),
413 (0..=2, 1) => self.boat_state(id, 4),
414 _ => {}
415 }
416 }
417
418 pub(crate) fn boat_orphan(&mut self, id: usize) {
420 if let Some(b) = self.wildlife.boats.get_mut(&id) {
421 b.post = None;
422 }
423 self.boat_message(id, 1);
424 }
425}