Skip to main content

banished_game/behaviour/character/
stack.rs

1//! The action stack: the actions a character carries out in turn, their reservations, and saving
2//! and restoring them.
3
4use super::*;
5
6#[derive(Debug, Clone)]
7pub(super) struct Task {
8    action: Action,
9    started: bool,
10}
11
12// +0x48; +0x58; +0x60
13/// The CharacterComponent's task stack (top index) and its "on empty" chain.
14#[derive(Debug, Clone, Default)]
15pub struct ActionStack {
16    chains: Vec<Vec<Task>>,
17    on_empty: Vec<Task>,
18    /// Job-manager messages from aborted CompleteTasks, for the owner to deliver right after the
19    /// clear.
20    ///
21    /// Original: `FUN_1400caf90`.
22    pub outbox: Vec<Message>,
23}
24
25impl ActionStack {
26    // +0x18; +0x10
27    /// A saved stack: its entries bottom to top, each one action and whether it started, and the
28    /// "on empty" action. Actions linked to an entry are not saved.
29    ///
30    /// Original: `FUN_14010ef80`.
31    pub fn restored(entries: Vec<(Action, bool)>, on_empty: Option<Action>) -> ActionStack {
32        ActionStack {
33            chains: entries
34                .into_iter()
35                .map(|(action, started)| vec![Task { action, started }])
36                .collect(),
37            on_empty: on_empty
38                .into_iter()
39                .map(|action| Task {
40                    action,
41                    started: false,
42                })
43                .collect(),
44            outbox: Vec::new(),
45        }
46    }
47
48    /// What a save holds: each entry's first action with its started flag, and the "on empty"
49    /// chain's first action.
50    ///
51    /// Original: `FUN_14010ef80`.
52    pub fn saved(&self) -> (Vec<(&Action, bool)>, Option<&Action>) {
53        (
54            self.chains
55                .iter()
56                .filter_map(|c| c.first().map(|t| (&t.action, t.started)))
57                .collect(),
58            self.on_empty.first().map(|t| &t.action),
59        )
60    }
61
62    // +0x60 + 1
63    /// How many entries the stack holds.
64    pub fn depth(&self) -> usize {
65        self.chains.len()
66    }
67
68    // +0x60 >= 0
69    /// `>= 0`: something is queued (the AI's "is busy" test).
70    pub fn is_active(&self) -> bool {
71        !self.chains.is_empty()
72    }
73
74    // +0x40
75    /// Abort every chain (slot on all actions) and drop the "on empty" chain.
76    ///
77    /// Original: `FUN_1400ca8d0`.
78    pub fn clear(&mut self, ch: &mut Character, group: &AnimationGroup, storages: &mut [Storage]) {
79        for chain in &mut self.chains {
80            for t in chain.iter_mut() {
81                if let Action::CompleteTask { work, param } = t.action {
82                    if work >= 0 {
83                        self.outbox.push(Message {
84                            component: Component::Jobs,
85                            message: 1,
86                            entity: None,
87                            a: work,
88                            b: param,
89                        });
90                    }
91                }
92                // `FUN_1400cff70`: the resource is free again.
93                if let Action::RemoveResource { entity, .. } = t.action {
94                    self.outbox.push(Message {
95                        component: Component::NaturalResource,
96                        message: 1,
97                        entity: Some(entity),
98                        a: 0,
99                        b: 0,
100                    });
101                }
102                t.action.abort(ch, group, storages);
103            }
104        }
105        self.chains.clear();
106        self.on_empty.clear();
107    }
108
109    /// The stored goods queued actions hold reserved: `(storage, material, count)` of each
110    /// unstarted PickupStoredRawMaterial and reserving ConsumeStoredRawMaterial.
111    pub fn pending_reservations(&self) -> Vec<(usize, usize, i32)> {
112        let mut out = Vec::new();
113        for t in self.chains.iter().flatten().filter(|t| !t.started) {
114            match &t.action {
115                Action::PickupStoredRawMaterial { storage, items, .. }
116                | Action::ConsumeStoredRawMaterial {
117                    storage,
118                    items,
119                    reserved: true,
120                } => out.extend(items.iter().map(|&(m, n)| (*storage, m, n))),
121                _ => {}
122            }
123        }
124        out
125    }
126
127    /// Does a chain start with this work's CompleteTask?
128    ///
129    /// Original: `FUN_1400fc5d0`.
130    pub fn holds_task(&self, work: i32) -> Option<i32> {
131        self.chains
132            .iter()
133            .find_map(|c| match c.first().map(|t| &t.action) {
134                Some(Action::CompleteTask { work: w, param }) if *w == work => Some(*param),
135                _ => None,
136            })
137    }
138
139    // +0x10
140    /// Link `action` into the top chain (the original's of the top action).
141    pub fn attach(&mut self, action: Action) {
142        if let Some(top) = self.chains.last_mut() {
143            top.push(Task {
144                action,
145                started: false,
146            });
147        }
148    }
149
150    /// The top chain's `HuntAnimal`, if any: its lodge and its `done` flag.
151    pub fn hunt_watch(&mut self) -> Option<(usize, &mut bool)> {
152        self.chains
153            .last_mut()?
154            .iter_mut()
155            .filter(|t| t.started)
156            .find_map(|t| match &mut t.action {
157                Action::HuntAnimal { lodge, done } => Some((*lodge, done)),
158                _ => None,
159            })
160    }
161
162    // +0x30
163    /// The scheduled notify: the running chain's started actions get slot; MoveTo's sets whether it
164    /// follows.
165    ///
166    /// Original: `FUN_14010e950`, `FUN_1400ca450`, `FUN_1400cde40`.
167    pub fn notify_moves(&mut self, following: bool) {
168        let Some(chain) = self.chains.last_mut() else {
169            return;
170        };
171        for t in chain.iter_mut().filter(|t| t.started) {
172            if let Action::MoveTo(m) = &mut t.action {
173                m.following = following;
174            }
175        }
176    }
177
178    /// Set the actions to run when the stack is empty.
179    pub fn set_on_empty(&mut self, actions: Vec<Action>) {
180        self.on_empty = actions
181            .into_iter()
182            .map(|action| Task {
183                action,
184                started: false,
185            })
186            .collect();
187    }
188
189    /// Stop the current top's started actions, then push a chain.
190    ///
191    /// Original: `FUN_1400ca9b0`.
192    pub fn push(&mut self, ch: &mut Character, group: &AnimationGroup, action: Action) {
193        if let Some(top) = self.chains.last_mut() {
194            for t in top.iter_mut().filter(|t| t.started) {
195                t.started = false;
196                t.action.stop(ch, group);
197            }
198        }
199        self.chains.push(vec![Task {
200            action,
201            started: false,
202        }]);
203    }
204
205    /// `FUN_1400ca120`.
206    pub(super) fn run(
207        &mut self,
208        ch: &mut Character,
209        world: &mut World,
210        group: &AnimationGroup,
211        dt: f32,
212    ) {
213        let dt = ticks(dt);
214        while let Some(top) = self.chains.last_mut() {
215            let mut status = Status::Running;
216            if !top.first().is_some_and(|t| t.started) {
217                for t in top.iter_mut() {
218                    t.started = true;
219                    status = t.action.start(ch, world, group);
220                    if status != Status::Running || !world.pushes.is_empty() {
221                        break;
222                    }
223                }
224            }
225            // An action's start pushed a chain (`ActionStack::Push`): the top's started
226            // actions stop and the new chain runs first.
227            if !world.pushes.is_empty() {
228                for action in std::mem::take(&mut world.pushes) {
229                    self.push(ch, group, action);
230                }
231                if status == Status::Failed {
232                    self.clear(ch, group, world.storages);
233                }
234                continue;
235            }
236            let mut ran = false;
237            if status == Status::Running {
238                for t in top.iter_mut() {
239                    status = t.action.update(ch, world, group, dt);
240                    if status != Status::Running {
241                        break;
242                    }
243                }
244                ran = status == Status::Running;
245            }
246            match status {
247                Status::Done => {
248                    let mut chain = self.chains.pop().unwrap_or_default();
249                    for t in chain.iter_mut().filter(|t| t.started) {
250                        t.started = false;
251                        t.action.stop(ch, group);
252                    }
253                }
254                // `FUN_1400ca8d0` also drops the "on empty" chain.
255                Status::Failed => self.clear(ch, group, world.storages),
256                Status::Running => {}
257            }
258            if ran {
259                break;
260            }
261        }
262        if self.chains.is_empty() && !self.on_empty.is_empty() {
263            for t in self.on_empty.iter_mut() {
264                t.started = true;
265                if t.action.start(ch, world, group) != Status::Running {
266                    break;
267                }
268            }
269            self.on_empty.clear();
270        }
271    }
272}