Skip to main content

banished_game/behaviour/
character.rs

1//! CharacterComponent and the `Action` task stack it runs. See docs/CITIZEN-AI.md.
2//!
3//! - Slot (bit `0x04` phase, every iteration): run the action stack, then turn toward the target
4//!   heading, move by the animation's root motion times the speed, and drop onto the ground.
5//! - Slot (bit `0x08` bucket phase): notify the running chain (action slot) on the started actions
6//!   of the running chain. MoveTo's sets whether it walks behind another character, at the
7//!   description's follow speed ([`ActionStack::notify_moves`]); HuntAnimal's looks for prey.
8//!
9//! Actions (registered `Action` types, vtable slots start, stop, abort) return [`Status`]. The
10//! stack is an array of chains; the top chain is started once, then updated each run. `Done` pops
11//! it and continues with the next chain in the same run, `Failed` clears everything, and when the
12//! stack empties the "on empty" chain runs once.
13//!
14//! Original: `FUN_14010f1f0`, `FUN_14010e990`, `FUN_1400ca120`, `FUN_14010e950`, `FUN_1400ca450`,
15//! `FUN_1400cde40`, `FUN_1400cd2b0`, `FUN_1400ca080`.
16
17mod actions;
18mod movement;
19mod stack;
20pub use actions::Action;
21pub use movement::MoveTo;
22pub use stack::ActionStack;
23
24// vtable 0x140168790; +0x40; +0x48; +0x30; +0x28 update; +0x58; +0x20; +0x38
25
26use banished_formats::game::CharacterDescription;
27use banished_formats::types::AnimationGroup;
28
29use crate::behaviour::animation::AnimationStack;
30use crate::pathfind::{self, Pathfinder};
31use crate::random::Random;
32use crate::scheduler::{seconds, ticks};
33use crate::terrain::{CellType, HeightGrid};
34use crate::work::stock::{consume_stored, pickup_stored, RawMaterial, Storage};
35
36// DAT_1401787d4
37/// Arrival distance.
38pub const ARRIVE: f32 = 0.05;
39/// `DAT_1401787e4`: turns smaller than this snap to the target heading.
40const SNAP_DEGREES: f32 = 0.1;
41/// MoveTo's path buffer (`+0x68`, 256 cells, `FUN_1400ac220`).
42const PATH_CELLS: usize = 0x100;
43/// Cell type whose surface is at height 0 (`FUN_14010e990`, type `0x0b`).
44const FLAT_TYPE: u8 = 11;
45
46/// The result of an action's step.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum Status {
49    /// The action finished.
50    Done = 0,
51    /// The action goes on.
52    Running = 1,
53    /// The action failed; the stack is abandoned.
54    Failed = 2,
55}
56
57// DAT_1401cf0e8; DAT_1401cf158
58/// Which component a `SendMessage` targets (CitizenAI, Citizen).
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Component {
61    /// The citizen's AI.
62    CitizenAi,
63    /// The citizen component (needs, family).
64    Citizen,
65    // DAT_1401cc2c0
66    /// Not identified yet.
67    Happiness,
68    // DAT_1401cf008
69    /// BuildComponent message 0 adds one unit of construction work.
70    Build,
71    // DAT_1401cf5f0
72    /// DestroyComponent message 0 adds one unit of deconstruction work.
73    Destroy,
74    // DAT_1401cc1e0
75    /// FireComponent message 0, a bucket of water.
76    ///
77    /// Original: `FUN_1400499f0`.
78    Fire,
79    // +0x2b
80    /// CompleteRoad's start: `a` the cell, `b` the cell type, `message` the byte.
81    ///
82    /// Original: `FUN_1400cad20`, `FUN_1400a1b10`.
83    Road,
84    /// The job manager: message 0 is CompleteTask's start, 1 its abort; `a` is the work id, `b` the
85    /// task parameter.
86    ///
87    /// Original: `FUN_1400fdc30`, `FUN_1400fdcd0`.
88    Jobs,
89    // +0x30
90    /// A NaturalResourceComponent: 0 remove (RemoveNaturalResource start, `a` its flag), 1 release
91    /// the removal claim (its abort), 2 play animation `a` (AnimateNaturalResource start).
92    ///
93    /// Original: `FUN_14012b910`.
94    NaturalResource,
95    /// A RawMaterialComponent pile: 0 picked up (PickupRawMaterial start).
96    Pile,
97    // +0x4c
98    /// CreateRawMaterial's start: `a` the output template, `b` the cell, `entity` the workplace,
99    /// `message` the yield factor as `f32` bits.
100    ///
101    /// Original: `FUN_1400cc010`.
102    CreateRawMaterial,
103    /// CreateNaturalResource's start once the cell is free: `a` the template, `b` the cell.
104    ///
105    /// Original: `FUN_1400cbb20`.
106    CreateNaturalResource,
107    /// RemoveAnimal's start: the animal `entity` is removed.
108    ///
109    /// Original: `FUN_1400cfcc0`.
110    Animal,
111    /// CropFieldComponent's message slot: `message` 0 planted / 1 tended, `a` the cell index,
112    /// `entity` the field.
113    ///
114    /// Original: `FUN_140120020`.
115    CropField,
116    /// OrchardComponent's message slot: 0 planted, 1 picked, 2 tended; `a` the tree slot.
117    ///
118    /// Original: `FUN_1400522f0`.
119    Orchard,
120    /// PastureComponent's message slot: 0 a rancher's visit.
121    ///
122    /// Original: `FUN_14012cbf0`.
123    Pasture,
124    /// A loose pile of `a` (material), `b` units, left on the ground near the citizen (the units
125    /// already left its storage).
126    ///
127    /// Original: `FUN_1400ccd90`.
128    DropPile,
129    /// QuarryComponent's message slot: the quarry `entity` digs deeper.
130    ///
131    /// Original: `FUN_1400554e0`.
132    Quarry,
133    /// FishingComponent: the fishing sequence's report (`a` the catch's cell). It reaches the
134    /// component's default message handler, which does nothing.
135    Fishing,
136}
137
138// +0x78
139/// A message an action delivered (slot of the receiving component). The owner dispatches these
140/// after the character update.
141#[derive(Debug, Clone, Copy, PartialEq)]
142pub struct Message {
143    /// The component that receives the message.
144    pub component: Component,
145    /// The message's number.
146    pub message: i32,
147    /// The entity it is for, if any.
148    pub entity: Option<usize>,
149    /// The first argument.
150    pub a: i32,
151    /// The second argument.
152    pub b: i32,
153}
154
155/// Map state the character and its actions read. `Town::FindMaterialStorage` for a redirected
156/// drop-off: the storages, the game's Random, the material, the count and the citizen's cell give a
157/// storage and the point to walk to.
158///
159/// Original: `FUN_140092a60`.
160pub type StorageSearch<'a> =
161    dyn FnMut(&[Storage], &mut Random, usize, i32, i32) -> Option<(usize, [f32; 3])> + 'a;
162
163/// What a character needs from the world to move: the map, the ground and the path finder.
164pub struct World<'a> {
165    /// The map's size in cells.
166    pub size: usize,
167    /// The type of each map cell.
168    pub types: &'a [CellType],
169    // +0x08; +0x00; +0x28
170    /// The object stamped into each map cell.
171    pub objects: &'a [Option<usize>],
172    /// The occupant of each map cell.
173    pub occupants: &'a [Option<usize>],
174    /// The flags of each map cell.
175    pub flags: &'a [u8],
176    // +0x2a
177    /// The road kind planned or built (`0xff` none).
178    pub roads: &'a [u8],
179    // +0x2d
180    /// The floors (bridge decks) that characters walk on.
181    pub floors: &'a crate::world::floor::Floors,
182    /// The walkable region of each cell.
183    pub regions: &'a [u16],
184    /// The ground's heights.
185    pub terrain: &'a HeightGrid<'a>,
186    /// The pathfinder.
187    pub pathfinder: &'a mut Pathfinder,
188    /// The game's random numbers.
189    pub rng: &'a mut Random,
190    /// The messages that actions send, delivered after the update.
191    pub messages: &'a mut Vec<Message>,
192    /// What ConsumeRawMaterial used up (material, amount), for the stock manager's count.
193    pub used: &'a mut Vec<(usize, i32)>,
194    /// The town's storages.
195    pub storages: &'a mut [Storage],
196    /// The raw materials.
197    pub materials: &'a [RawMaterial],
198    /// What the character carries.
199    pub carried: &'a mut Storage,
200    /// The drop-off redirect's storage search (citizens only).
201    pub storage_search: Option<&'a mut StorageSearch<'a>>,
202    /// Actions an action pushed from its start (`ActionStack::Push`), pushed once it returns.
203    pub pushes: Vec<Action>,
204    // +0xac
205    /// Citizen: tool uses left (PerformWork with a tool spends one).
206    pub tool_uses: &'a mut i32,
207    /// The tool ShowTool attached (`-1` none, `-2` carried goods).
208    pub shown_tool: &'a mut i32,
209    /// Per placed entity: its model is playing an animation (`MeshInstance::IsAnimating`).
210    pub animating: &'a mut [bool],
211    // +0x1f8; +0x200
212    /// A citizen's accident: the profession whose work it had the accident at.
213    pub accident: &'a mut Option<usize>,
214    /// The time since the last check for an injury at work.
215    pub accident_timer: &'a mut i64,
216    // +0x54; +0x58; +0x30; +0x50
217    /// The current job's profession (→ the work's slot) and its accident odds.
218    pub accident_odds: Option<(usize, f32, f32)>,
219}
220
221impl World<'_> {
222    // RandFloat() < time since the last check / (+0x54 × 12 × 300 s × +0x58); Game +0xd0
223    /// Already hurt, or, at a job whose profession has accidents, `RandFloat < time since the last
224    /// check / (× 12 × 300 s ×)` (the time restarts at every check). Not ported: the debug flag
225    /// that stops it (`Game` bit 8).
226    ///
227    /// Original: `Citizen::InjuryCheck`, `FUN_14011bb20`, `FUN_1400ae760`.
228    pub fn injury_check(&mut self) -> bool {
229        if self.accident.is_some() {
230            return true;
231        }
232        let Some((profession, years, odds)) = self.accident_odds else {
233            return false;
234        };
235        if odds <= 0.0 {
236            *self.accident_timer = 0;
237            return false;
238        }
239        let period = years * 12.0 * 300.0;
240        let since = crate::scheduler::seconds(*self.accident_timer);
241        *self.accident_timer = 0;
242        if self.rng.rand_float() < since / (period * odds) {
243            *self.accident = Some(profession);
244            return true;
245        }
246        false
247    }
248
249    /// Cell under a map position (clamped, as every caller in the original does).
250    pub fn cell(&self, p: [f32; 3]) -> i32 {
251        let n = self.size as i32;
252        let half = (n >> 1) as f32;
253        let x = ((p[0] + half) as i32).clamp(0, n - 1);
254        let y = ((p[1] + half) as i32).clamp(0, n - 1);
255        n * y + x
256    }
257
258    /// Centre of a cell on the ground (on a bridge's floor).
259    ///
260    /// Original: `FUN_1400a48b0`.
261    pub fn cell_centre(&self, cell: i32) -> [f32; 3] {
262        let n = self.size as i32;
263        let c = (n >> 1) as f32 - 0.5;
264        let (x, y) = ((cell % n) as f32 - c, (cell / n) as f32 - c);
265        [x, y, self.ground(x, y)]
266    }
267
268    /// `FUN_1400a48b0`: the terrain, or a bridge's floor above it.
269    fn ground(&self, x: f32, y: f32) -> f32 {
270        self.floors.ground(self.size, self.terrain, x, y)
271    }
272}
273
274/// `Math::Vector3::Heading`: `atan2(-x, y)` in degrees (0 faces +y).
275pub fn heading(dir: [f32; 2]) -> f32 {
276    crate::math::heading(dir[0], dir[1])
277}
278
279/// Normalize the full 3D vector before taking its heading.
280///
281/// Original: `FUN_1400c7e70`.
282pub fn work_facing(source: [f32; 3], target: [f32; 3]) -> f32 {
283    let d = crate::math::normalize3([
284        source[0] - target[0],
285        source[1] - target[1],
286        source[2] - target[2],
287    ]);
288    crate::math::heading(d[0], d[1])
289}
290
291fn normalize2(v: [f32; 2]) -> [f32; 2] {
292    crate::math::normalize2(v)
293}
294
295/// The character state the actions drive.
296pub struct Character {
297    /// The character description: speeds and turning.
298    pub desc: CharacterDescription,
299    // +0x158; +0x168
300    /// The position of the model.
301    pub position: [f32; 3],
302    /// The heading in degrees, including the model's yaw offset.
303    pub yaw: f32,
304    // +0x2c..+0x34; +0x38; +0x3c
305    /// The walking direction.
306    pub direction: [f32; 3],
307    /// The walking speed.
308    pub speed: f32,
309    /// The heading it turns towards.
310    pub target_heading: f32,
311    // +0x28
312    /// Set with a heading of its own, cleared when a waypoint is faced; no byte read of it was
313    /// found in the Application outside the saves, which keep it.
314    ///
315    /// Original: `FUN_14010f5b0`.
316    pub flag_28: bool,
317    // +0x13c; FUN_14010f6c0 multiplies it into the speed.
318    /// The model's z scale, which scales the walking speed.
319    pub scale: f32,
320    /// The model's animation state, its clips blending into each other.
321    pub animation: AnimationStack,
322    /// The actions it runs.
323    pub stack: ActionStack,
324    // +0x40; Game +0x88
325    /// Its slot in the characters' tree (`Game`), −1 while in none.
326    pub tree_slot: i32,
327}
328
329impl Character {
330    /// A character at a position, with its description and its model's scale.
331    pub fn new(desc: CharacterDescription, position: [f32; 3], scale: f32) -> Character {
332        Character {
333            desc,
334            position,
335            yaw: desc.yaw_offset,
336            direction: [0.0; 3],
337            speed: 0.0,
338            target_heading: 0.0,
339            flag_28: false,
340            scale,
341            animation: AnimationStack::new(0, 0),
342            stack: ActionStack::default(),
343            tree_slot: -1,
344        }
345    }
346
347    /// Play clip `anim` (a looping clip already playing keeps its time,
348    /// `AnimationState::PlayAnimation`) and set the speed to `scale * speed`.
349    ///
350    /// Original: `FUN_14010f6c0`.
351    pub fn play(&mut self, anim: usize, group: &AnimationGroup, speed: f32) {
352        self.animation.play(group, anim, 0);
353        self.speed = self.scale * speed;
354    }
355
356    /// Run the actions, turn and move. `dt` is the manager's iteration delta. Call
357    /// [`Character::animate`] first: `AnimationManager::Update` runs before the entity manager in
358    /// each iteration, and the move uses that step's root motion.
359    ///
360    /// Original: `FUN_14010e990`.
361    pub fn update(&mut self, world: &mut World, group: &AnimationGroup, dt: f32) {
362        let mut stack = std::mem::take(&mut self.stack);
363        stack.run(self, world, group, dt);
364        world.messages.append(&mut stack.outbox);
365        self.stack = stack;
366        self.turn();
367        self.walk(world, group);
368    }
369
370    /// `Graphics::AnimationManager::Update`: advance the clip; the root delta moves the body.
371    pub fn animate(&mut self, group: &AnimationGroup, dt: f32) {
372        self.animation.update(group, ticks(dt));
373    }
374
375    /// Root motion of the last animation step (`MeshInstance::RootAnimationOffset`).
376    fn root_offset(&self, group: &AnimationGroup) -> f32 {
377        let Some(pose) = self.animation.pose(group) else {
378            return 0.0;
379        };
380        pose.first().map_or(0.0, |root| {
381            let t = root.translation;
382            (t[0] * t[0] + t[1] * t[1] + t[2] * t[2]).sqrt()
383        })
384    }
385
386    fn turn(&mut self) {
387        // Turn: keep `turn_keep` of the remaining angle (shortest way round).
388        let desc = self.desc;
389        let current = self.yaw - desc.yaw_offset;
390        let mut diff = self.target_heading - current;
391        if diff.abs() >= 180.0 {
392            diff = if diff <= 0.0 {
393                diff + 360.0
394            } else {
395                -(360.0 - diff)
396            };
397        }
398        let mut new_heading = self.target_heading;
399        if diff.abs() > SNAP_DEGREES {
400            new_heading = (diff + current) * (1.0 - desc.turn_keep) + desc.turn_keep * current;
401            if new_heading > 180.0 {
402                new_heading -= 360.0;
403            } else if new_heading < -180.0 {
404                new_heading += 360.0;
405            }
406        }
407        self.yaw = new_heading + desc.yaw_offset;
408    }
409
410    /// The movement part of a character's update, after the actions ran and the clip advanced.
411    ///
412    /// Original: the second half of `FUN_14010e990`.
413    pub fn walk(&mut self, world: &World, group: &AnimationGroup) {
414        let distance = self.root_offset(group) * self.speed;
415        let mut p = self.position;
416        if distance > 0.0 {
417            p[0] += distance * self.direction[0];
418            p[1] += distance * self.direction[1];
419            p[2] += distance * self.direction[2];
420        }
421        let cell = world.cell(p);
422        p[2] = if world.types[cell as usize].0 == FLAT_TYPE {
423            0.0
424        } else if self.desc.fixed_height {
425            self.desc.height
426        } else {
427            world.ground(p[0], p[1]).max(self.desc.min_height)
428        };
429        self.position = p;
430    }
431
432    /// Face a point and walk toward it (the end of a `MoveTo`). The direction is the 2D unit
433    /// vector normalised again in 3D; the heading comes from the 2D vector.
434    ///
435    /// Original: `FUN_1400ce1f0`.
436    pub fn face(&mut self, target: [f32; 3]) {
437        let d = normalize2([target[0] - self.position[0], target[1] - self.position[1]]);
438        self.flag_28 = false;
439        self.direction = crate::math::normalize3([d[0], d[1], 0.0]);
440        self.target_heading = heading(d);
441    }
442}
443
444/// Seconds as manager ticks, for action durations.
445pub fn duration(s: f32) -> i64 {
446    ticks(s)
447}
448
449/// Manager ticks as seconds.
450pub fn as_seconds(t: i64) -> f32 {
451    seconds(t)
452}
453
454#[cfg(test)]
455mod inventory_action_tests {
456    use super::*;
457
458    #[test]
459    fn clearing_pending_consumption_and_pickup_releases_reservations() {
460        let group = AnimationGroup {
461            clips: vec![],
462            default_blend: 0.0,
463            blends: vec![],
464            blend_matrix: vec![],
465        };
466        let mut ch = Character::new(CharacterDescription::default(), [0.0; 3], 1.0);
467        let mut storages = vec![Storage {
468            capacity: 10,
469            accepts: u32::MAX,
470            items: vec![],
471        }];
472        storages[0].add(0, 3);
473        let mut stack = ActionStack::default();
474        for pickup in [false, true] {
475            storages[0].reserve(0, 1);
476            stack.push(
477                &mut ch,
478                &group,
479                if pickup {
480                    Action::PickupStoredRawMaterial {
481                        storage: 0,
482                        items: vec![(0, 1)],
483                        partial: false,
484                    }
485                } else {
486                    Action::ConsumeStoredRawMaterial {
487                        storage: 0,
488                        items: vec![(0, 1)],
489                        reserved: true,
490                    }
491                },
492            );
493        }
494        assert_eq!(storages[0].available(0), 1);
495        stack.clear(&mut ch, &group, &mut storages);
496        assert_eq!(storages[0].available(0), 3);
497        assert_eq!(storages[0].entry(0).unwrap().reserved, 0);
498        assert!(!stack.is_active());
499    }
500}
501
502#[cfg(test)]
503mod drop_off_tests {
504    use super::*;
505
506    /// A drop-off with `redirect` whose storage is full: with another storage found, the
507    /// action points there and a walk is pushed; with none, the goods are left as a pile
508    /// (`DropPile`) and the action fails. Without `redirect` nothing happens.
509    #[test]
510    fn a_full_storage_redirects_or_drops_a_pile() {
511        let n = 8;
512        let types = vec![CellType(2); n * n];
513        let none = vec![None; n * n];
514        let zeros = vec![0u8; n * n];
515        let roads = vec![0xffu8; n * n];
516        let floors = crate::world::floor::Floors::new(n * n);
517        let regions = vec![0u16; n * n];
518        let heights = vec![0i16; (2 * n + 1) * (2 * n + 1)];
519        let terrain = HeightGrid {
520            width: 2 * n + 1,
521            heights: &heights,
522        };
523        let materials = vec![RawMaterial {
524            size: 1,
525            ..Default::default()
526        }];
527        let group = AnimationGroup {
528            clips: vec![],
529            default_blend: 0.0,
530            blends: vec![],
531            blend_matrix: vec![],
532        };
533        for (found, redirect) in [(true, true), (false, true), (false, false)] {
534            let mut pathfinder = Pathfinder::new(n, &types);
535            let mut rng = Random::new(1);
536            let (mut messages, mut used) = (Vec::new(), Vec::new());
537            let full = Storage {
538                capacity: 0,
539                accepts: u32::MAX,
540                items: vec![],
541            };
542            let mut storages = vec![
543                full.clone(),
544                Storage {
545                    capacity: 10,
546                    ..full
547                },
548            ];
549            let mut carried = Storage {
550                capacity: 10,
551                accepts: u32::MAX,
552                items: vec![],
553            };
554            carried.add(0, 3);
555            let (mut tool_uses, mut shown, mut accident, mut timer) = (0, -1, None, 0);
556            let mut animating = vec![];
557            let mut search = |_: &[Storage], _: &mut Random, m: usize, count: i32, _: i32| {
558                assert_eq!((m, count), (0, 3));
559                found.then_some((1, [1.0, 1.0, 0.0]))
560            };
561            let mut world = World {
562                size: n,
563                types: &types,
564                objects: &none,
565                occupants: &none,
566                flags: &zeros,
567                roads: &roads,
568                floors: &floors,
569                regions: &regions,
570                terrain: &terrain,
571                pathfinder: &mut pathfinder,
572                rng: &mut rng,
573                messages: &mut messages,
574                used: &mut used,
575                storages: &mut storages,
576                materials: &materials,
577                carried: &mut carried,
578                storage_search: Some(&mut search),
579                pushes: Vec::new(),
580                tool_uses: &mut tool_uses,
581                shown_tool: &mut shown,
582                animating: &mut animating,
583                accident: &mut accident,
584                accident_timer: &mut timer,
585                accident_odds: None,
586            };
587            let mut ch = Character::new(CharacterDescription::default(), [0.0; 3], 1.0);
588            let mut action = Action::DropOff {
589                storage: 0,
590                material: usize::MAX,
591                count: -1,
592                redirect,
593            };
594            let status = action.start(&mut ch, &mut world, &group);
595            let pushed = world.pushes.len();
596            let Action::DropOff { storage, .. } = action else {
597                unreachable!()
598            };
599            match (found, redirect) {
600                (true, _) => {
601                    assert_eq!((status, storage, pushed), (Status::Running, 1, 1));
602                    assert_eq!(world.carried.available(0), 3);
603                }
604                (false, true) => {
605                    assert_eq!((status, storage, pushed), (Status::Failed, 0, 0));
606                    assert_eq!(world.carried.available(0), 0);
607                    assert_eq!(world.messages[0].component, Component::DropPile);
608                    assert_eq!((world.messages[0].a, world.messages[0].b), (0, 3));
609                }
610                (false, false) => {
611                    assert_eq!((status, pushed), (Status::Running, 0));
612                    assert_eq!(world.carried.available(0), 3);
613                    assert!(world.messages.is_empty());
614                }
615            }
616        }
617    }
618}
619
620#[cfg(test)]
621mod restored_move_tests {
622    use super::*;
623
624    /// A walk restored from a save mid-way (index 1 of 3 cells) plays its walk clip and faces
625    /// its next cell on the first update, once. Without the clip the body has no root motion
626    /// and would never reach the next cell.
627    #[test]
628    fn a_restored_walk_plays_its_clip_and_faces_the_next_cell_once() {
629        let n = 8;
630        let types = vec![CellType(2); n * n];
631        let none = vec![None; n * n];
632        let zeros = vec![0u8; n * n];
633        let roads = vec![0xffu8; n * n];
634        let floors = crate::world::floor::Floors::new(n * n);
635        let regions = vec![0u16; n * n];
636        let heights = vec![0i16; (2 * n + 1) * (2 * n + 1)];
637        let terrain = HeightGrid {
638            width: 2 * n + 1,
639            heights: &heights,
640        };
641        let group = AnimationGroup {
642            clips: vec![],
643            default_blend: 0.0,
644            blends: vec![],
645            blend_matrix: vec![],
646        };
647        let mut pathfinder = Pathfinder::new(n, &types);
648        let mut rng = Random::new(1);
649        let (mut messages, mut used) = (Vec::new(), Vec::new());
650        let mut storages = vec![];
651        let mut carried = Storage {
652            capacity: 0,
653            accepts: u32::MAX,
654            items: vec![],
655        };
656        let (mut tool_uses, mut shown, mut accident, mut timer) = (0, -1, None, 0);
657        let mut animating = vec![];
658        let mut world = World {
659            size: n,
660            types: &types,
661            objects: &none,
662            occupants: &none,
663            flags: &zeros,
664            roads: &roads,
665            floors: &floors,
666            regions: &regions,
667            terrain: &terrain,
668            pathfinder: &mut pathfinder,
669            rng: &mut rng,
670            messages: &mut messages,
671            used: &mut used,
672            storages: &mut storages,
673            materials: &[],
674            carried: &mut carried,
675            storage_search: None,
676            pushes: Vec::new(),
677            tool_uses: &mut tool_uses,
678            shown_tool: &mut shown,
679            animating: &mut animating,
680            accident: &mut accident,
681            accident_timer: &mut timer,
682            accident_odds: None,
683        };
684        let (here, next, end) = (2 * n as i32 + 2, 2 * n as i32 + 3, 2 * n as i32 + 4);
685        let (walk, stand) = (5, 6);
686        let mut ch = Character::new(
687            CharacterDescription::default(),
688            world.cell_centre(here),
689            1.0,
690        );
691        ch.direction = [0.0; 3];
692        ch.play(stand, &group, 1.0);
693        let mut m = MoveTo::restored(
694            world.cell_centre(end),
695            walk,
696            stand,
697            0,
698            0,
699            1.0,
700            false,
701            1,
702            &[here, next, end],
703        );
704        assert_eq!(m.update(&mut ch, &mut world, &group), Status::Running);
705        assert_eq!(ch.animation.clip(), Some(walk));
706        let to_next = world.cell_centre(next);
707        assert!(ch.direction[0] * (to_next[0] - ch.position[0]) > 0.0);
708        // Only the first update resumes: a clip played afterwards is left alone.
709        ch.play(stand, &group, 1.0);
710        m.update(&mut ch, &mut world, &group);
711        assert_eq!(ch.animation.clip(), Some(stand));
712    }
713}