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: ®ions,
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: ®ions,
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}