Skip to main content

banished_game/commerce/
boat.rs

1//! The trader boat. See docs/TRADE.md, "The boat".
2//!
3//! The boat comes in along the map's first river from off the map, takes a water path past its
4//! trading post's "boat" point (where it docks), and leaves along the river's tail.
5//!
6//! One state step of the original measures the route point against itself: a length of 0 that is
7//! never below 0, so its jump ahead never happens and it always returns true. There is nothing to
8//! port for it.
9//!
10//! Original: `BoatComponent`: `FUN_140047160`, `FUN_140047200`, `FUN_1400ac590`; the step that
11//! does nothing is `FUN_140045af0`.
12// vtable 0x14015f060
13
14use crate::behaviour::character::Character;
15use crate::pathfind::Pathfinder;
16use crate::town::Sim;
17
18/// `DAT_140178880`, `DAT_14017889c`, `DAT_1401787ec`, `DAT_14017880c`, `DAT_1401787fc`.
19const NEAR: f32 = 3.0;
20const AHEAD: f32 = 6.0;
21const SLOW: f32 = 0.125;
22const SLOWEST: f32 = 0.33;
23const DOCKED: f32 = 0.2;
24/// `DAT_1401788f4`: the squared distance from the dock it starts its approach at.
25const APPROACH: f32 = 64.0;
26/// The trading post's state messages: 9 the boat docked, 0xb it went before docking.
27pub const DOCKED_MESSAGE: i32 = 9;
28/// The message a boat sends when it has left the map.
29pub const GONE_MESSAGE: i32 = 0xb;
30
31/// The boat's character kind (TraderBoat's CharacterDescription).
32#[derive(Debug, Clone, Copy)]
33pub struct BoatKind {
34    /// The character description.
35    pub character: banished_formats::game::CharacterDescription,
36}
37
38// +0x28; +0x40; +0x4c; +0x50; +0x60; +0x70
39/// A trader boat: its state (0 sailing in, 1 approaching, 2 docked, 3 casting off, 4 sailing out),
40/// its trading post, the dock and its heading, the point it heads for, its route, and its place on
41/// the route.
42pub struct Boat {
43    /// The template.
44    pub template: usize,
45    /// The character.
46    pub character: Character,
47    /// The state: 0 sailing in, 1 approaching, 2 docked, 3 casting off, 4 sailing out.
48    pub state: i8,
49    /// The trading post.
50    pub post: Option<usize>,
51    /// The dock.
52    pub dock: [f32; 3],
53    /// The dock's heading, in degrees.
54    pub dock_yaw: f32,
55    /// The point it heads for.
56    pub target: [f32; 3],
57    /// The route.
58    pub route: Vec<[f32; 3]>,
59    /// Its place on the route.
60    pub index: i32,
61}
62
63/// `FUN_1400ac590`: from the route index on, the first point at least `d` from `p` (only the
64/// plane distance and `p`'s height count), or none at the end.
65fn 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
80/// `Math::Vector3::Length`.
81fn length(v: [f32; 3]) -> f32 {
82    crate::math::length3(v)
83}
84
85/// `Math::Vector3::Normalize`.
86fn normalize(v: [f32; 3]) -> [f32; 3] {
87    crate::math::normalize3(v)
88}
89
90/// `Math::Vector3::Heading`.
91fn heading(v: [f32; 3]) -> f32 {
92    crate::behaviour::character::heading([v[0], v[1]])
93}
94
95/// The river's points before it enters the map, a water path (mode 3, types 7..9) from where it
96/// enters to the dock and on to where it leaves, then the river's points after the first one off
97/// the map again. Also the dock heading, turned with the river's flow at the route point nearest
98/// the dock.
99///
100/// Original: `FUN_140046040` (route).
101pub 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    // The run inside the map: its last point, and the index after the first point off it.
134    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            // `FUN_1400a48b0(.., 0)`: a bridge's deck over the cell counts.
152            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    // `FUN_1400ac7e0`: the route point nearest the dock, and the flow two points back.
159    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    /// `FUN_14010f6c0` on the boat: clip `clip` at full speed.
196    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    /// The trader boat entity (`FUN_1400c8bf0` of TradeDescription `+0x30`), set up for its
206    /// trading post (`FUN_140046040`) and activated: state 0's enter (`FUN_140045d50`) puts it
207    /// at the route's start facing its first point six away.
208    pub(crate) fn add_boat(&mut self, template: usize, post: usize) -> Option<usize> {
209        let kind = *self.wildlife.boat_kinds.get(&template)?;
210        // The trading post's "boat" point (`FUN_140128d50`).
211        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    /// A named interaction point in the world and its heading (`FUN_140128d50`).
274    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        // The point's `FromYawPitch(yaw, 0)` turned by the entity transform, then `Heading`.
283        let model = crate::math::Transform::of_model(p.yaw, p.position);
284        Some((model.apply(local), model.heading_of(yaw)))
285    }
286
287    /// `FUN_140046d30`: within three of the point it heads for, the next one six on (none at
288    /// the route's end); it heads there.
289    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            // `FUN_1400458d0`: docked; the trading post hears of it (message 9).
329            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    /// The boats' StateMachine updates: 0 sailing in, gone at the route's end (the post hears it,
344    /// message 0xb), approaching within eight of the dock; 1 slowing to the dock, docked within
345    /// 0.2; 3 heading back to the route, sailing out within three; 4 gone at the route's end.
346    ///
347    /// Original: `FUN_140045bb0`, `FUN_140045950`, `FUN_140045670`, `FUN_1400455a0`.
348    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                    // `FUN_14010f5b0`.
378                    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    /// The boat's state messages: 0 in 2 (`FUN_140045860`) it casts off; 1 in 0..2
406    /// (`FUN_140045fd0`) it sails out.
407    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    /// `FUN_140046040(boat, none)`: the trading post is gone; the boat sails out.
419    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}