Skip to main content

banished_game/behaviour/character/
movement.rs

1//! Walking to a place (`MoveToAction`): asking for a path, following its cells and facing the next
2//! one.
3
4use super::*;
5
6// vtable 0x140166260
7/// `MoveTo`.
8///
9/// Original: `FUN_1400ce640`.
10#[derive(Debug, Clone)]
11pub struct MoveTo {
12    // +0x38
13    /// Destination.
14    pub target: [f32; 3],
15    // +0x5c
16    /// Allowed cell types.
17    pub flags: u32,
18    // +0x468 / +0x46c
19    /// Walk and stand clips.
20    pub walk: usize,
21    /// The clip played while standing.
22    pub stand: usize,
23    // +0x470
24    /// Pathfinder cost table.
25    pub mode: u32,
26    // +0x478
27    /// Speed factor (random 0.9–1.1 when constructed with a negative value).
28    pub speed: f32,
29    // +0x474
30    /// Walking behind another character.
31    ///
32    /// Original: `FUN_1400cde40`.
33    pub following: bool,
34    /// Pending request result (`+0x20/+0x24`); available on the next update.
35    pending: Option<Option<Vec<i32>>>,
36    /// `+0x60` index, `+0x64` count, `+0x68` cells.
37    index: i32,
38    count: i32,
39    path: [i32; PATH_CELLS],
40    /// Set by [`MoveTo::restored`]: the first update plays the walk clip again.
41    resume: bool,
42}
43
44impl MoveTo {
45    /// A `MoveTo` action.
46    ///
47    /// Original: `FUN_1400ce640`.
48    pub fn new(
49        target: [f32; 3],
50        walk: usize,
51        stand: usize,
52        flags: u32,
53        mode: u32,
54        speed: f32,
55        rng: &mut Random,
56    ) -> MoveTo {
57        MoveTo {
58            target,
59            flags,
60            walk,
61            stand,
62            mode,
63            speed: if speed >= 0.0 {
64                speed
65            } else {
66                rng.rand_float_tol() * 0.1 + 1.0
67            },
68            following: false,
69            pending: None,
70            index: -1,
71            count: 0,
72            path: [0; PATH_CELLS],
73            resume: false,
74        }
75    }
76
77    // +0x64; +0x68; +0x60
78    /// Plan from `from` to the target. A `MoveTo` from a save keeps its way (its cells and the next
79    /// one) as it was.
80    /// A path request in flight is not saved; one with no way restarts.
81    ///
82    /// Original: `FUN_1400ce390`, `FUN_1400ce0e0`.
83    #[allow(clippy::too_many_arguments)]
84    pub fn restored(
85        target: [f32; 3],
86        walk: usize,
87        stand: usize,
88        flags: u32,
89        mode: u32,
90        speed: f32,
91        following: bool,
92        index: i32,
93        path: &[i32],
94    ) -> MoveTo {
95        let n = path.len().min(PATH_CELLS);
96        let mut cells = [0; PATH_CELLS];
97        cells[..n].copy_from_slice(&path[..n]);
98        MoveTo {
99            target,
100            flags,
101            walk,
102            stand,
103            mode,
104            speed,
105            following,
106            pending: None,
107            index,
108            count: n as i32,
109            path: cells,
110            resume: true,
111        }
112    }
113
114    // +0x60; +0x64; +0x68
115    /// What a save holds of the way: the next cell's index and the cells.
116    pub fn saved_way(&self) -> (i32, &[i32]) {
117        (
118            self.index,
119            &self.path[..(self.count.max(0) as usize).min(PATH_CELLS)],
120        )
121    }
122
123    pub(super) fn request(&mut self, from: [f32; 3], world: &mut World) {
124        let a = world.cell(from);
125        let b = world.cell(self.target);
126        self.index = -1;
127        if a == b {
128            self.count = 1;
129            self.path[0] = a;
130            self.index = 0;
131            return;
132        }
133        world.pathfinder.set_mode(self.mode);
134        self.pending = Some(pathfind::request(
135            world.pathfinder,
136            world.types,
137            world.regions,
138            self.flags,
139            a,
140            b,
141        ));
142    }
143
144    pub(super) fn face_waypoint(&self, ch: &mut Character, world: &World) {
145        let cell = self.path[(self.index as usize).min(PATH_CELLS - 1)];
146        ch.face(world.cell_centre(cell));
147    }
148
149    /// `FUN_1400cdf80` (start).
150    pub(super) fn start(
151        &mut self,
152        ch: &mut Character,
153        world: &mut World,
154        group: &AnimationGroup,
155    ) -> Status {
156        let d = [
157            ch.position[0] - self.target[0],
158            ch.position[1] - self.target[1],
159        ];
160        if (d[0] * d[0] + d[1] * d[1]).sqrt() < ARRIVE {
161            return Status::Done;
162        }
163        self.request(ch.position, world);
164        if self.index < 0 {
165            return match self.pending {
166                Some(Some(_)) => Status::Running,
167                _ => Status::Failed,
168            };
169        }
170        self.face_waypoint(ch, world);
171        ch.play(self.walk, group, 1.0);
172        Status::Running
173    }
174
175    /// `FUN_1400cda40` (update).
176    pub(super) fn update(
177        &mut self,
178        ch: &mut Character,
179        world: &mut World,
180        group: &AnimationGroup,
181    ) -> Status {
182        // Deviation: a walk restored from a save plays its walk clip again and faces its next
183        // cell on its first update. The original restores the clip with the animation block
184        // (`AnimationManager::Serialize`, `FUN_18005d130`), which the port does not decode
185        // yet; without a walking clip the body has no root motion, never reaches the next
186        // cell, and the citizen stands still for good.
187        if std::mem::take(&mut self.resume) && self.index >= 0 && self.index < self.count {
188            self.face_waypoint(ch, world);
189            ch.play(self.walk, group, 1.0);
190        }
191        if self.index != -1 {
192            let here = world.cell(ch.position);
193            if world.cell(self.target) == here {
194                let d = [
195                    ch.position[0] - self.target[0],
196                    ch.position[1] - self.target[1],
197                ];
198                if (d[0] * d[0] + d[1] * d[1]).sqrt() >= ARRIVE {
199                    ch.face(self.target);
200                    return Status::Running;
201                }
202                return Status::Done;
203            }
204            if self.count < 2 {
205                return Status::Running;
206            }
207            if self.index < self.count {
208                let t = world.types[here as usize].0;
209                let scale = if self.following {
210                    ch.desc.follow_speed
211                } else if t == 1 || t == 9 {
212                    ch.desc.road_speed
213                } else if t == 0 {
214                    ch.desc.type0_speed
215                } else {
216                    1.0
217                };
218                ch.speed = scale * self.speed;
219                if here != self.path[self.index as usize] {
220                    return Status::Running;
221                }
222                self.index += 1;
223                self.face_waypoint(ch, world);
224                return Status::Running;
225            }
226            ch.play(self.stand, group, 1.0);
227            self.index = -1;
228            return Status::Running;
229        }
230        if let Some(result) = self.pending.take() {
231            let Some(path) = result else {
232                return Status::Failed;
233            };
234            let n = path.len().min(PATH_CELLS);
235            self.path[..n].copy_from_slice(&path[..n]);
236            self.count = n as i32;
237            if n == 0 {
238                return Status::Failed;
239            }
240            self.index = 1;
241            self.face_waypoint(ch, world);
242            ch.play(self.walk, group, 1.0);
243            return Status::Running;
244        }
245        match self.start(ch, world, group) {
246            Status::Failed => Status::Failed,
247            _ => Status::Running,
248        }
249    }
250}