Skip to main content

banished_game/behaviour/agents/
commands.rs

1//! Commands from the town: new citizens, tree slots and newcomers arriving.
2
3use super::*;
4
5impl Agents {
6    /// Player commands and newly placed sites, applied before an iteration (where the
7    /// original's UI events and placement land between iterations).
8    pub fn apply_commands(
9        &mut self,
10        citizens: &mut Vec<Citizen>,
11        env: &mut Env,
12        groups: &[Option<&AnimationGroup>],
13        newborn: &mut dyn FnMut(&mut Random, &Citizen, bool) -> Citizen,
14    ) {
15        self.sync_map(env.cells);
16        let commands = std::mem::take(&mut env.construction.commands);
17        let placed = std::mem::take(&mut env.construction.placed);
18        let destroy = std::mem::take(&mut env.construction.destroy);
19        if commands.is_empty() && placed.is_empty() && destroy.is_empty() {
20            return;
21        }
22        let entities = entity_views(env.placed, env.templates, env.construction);
23        let adult = self.adults(citizens);
24        // A band taken in (ImmigrationUI `0x9858`) becomes citizens where its command stands;
25        // the commands after it run once it has.
26        let mut admission = None;
27        let mut road_markers = Vec::new();
28        let (mut sim, jobs) = self.sim(citizens, env, groups, &adult, &entities);
29        // Placing activates at once in the original (`FUN_1400c9d70`); a cancel comes after.
30        for e in placed {
31            sim.activate(jobs, e);
32            jobs.drain(&mut sim);
33        }
34        for e in destroy {
35            sim.destroy_entity(jobs, e);
36            jobs.drain(&mut sim);
37        }
38        let mut queue = commands.into_iter();
39        while let Some(command) = queue.next() {
40            match command {
41                crate::work::professions::Command::SetCrop { entity, crop } => {
42                    if sim.wildlife.pastures.contains_key(&entity) {
43                        // The kind's list is only enabled while the pasture is empty
44                        // (`FUN_1400ba320`).
45                        if sim.wildlife.pastures[&entity].livestock.is_empty() {
46                            sim.set_pasture_kind(jobs, entity, crop);
47                        }
48                    } else if sim.sites.get(&entity).is_some_and(|s| s.orchard.is_some()) {
49                        sim.set_orchard_tree(entity, crop);
50                    } else {
51                        sim.set_crop(entity, crop);
52                    }
53                    jobs.drain(&mut sim);
54                    continue;
55                }
56                crate::work::professions::Command::FieldAction { entity, action } => {
57                    if sim.wildlife.pastures.contains_key(&entity) {
58                        sim.pasture_action(jobs, entity, action);
59                    } else if sim.sites.get(&entity).is_some_and(|s| s.orchard.is_some()) {
60                        sim.orchard_action(entity, action);
61                    } else {
62                        sim.field_action(entity, action);
63                    }
64                    jobs.drain(&mut sim);
65                    continue;
66                }
67                crate::work::professions::Command::SetLimit { entity, limit } => {
68                    sim.set_pasture_limit(entity, limit);
69                    continue;
70                }
71                crate::work::professions::Command::SetEnabled { entity, enabled } => {
72                    sim.enable_workplace(entity, enabled);
73                    jobs.drain(&mut sim);
74                    continue;
75                }
76                crate::work::professions::Command::PlanRoad {
77                    cell,
78                    kind,
79                    ty,
80                    material,
81                } => {
82                    sim.plan_road(jobs, cell, kind, ty, material);
83                    jobs.drain(&mut sim);
84                    continue;
85                }
86                crate::work::professions::Command::ClearRoad { cell, marker } => {
87                    if sim.unplan_road(jobs, cell) {
88                        road_markers.push((cell, marker));
89                    }
90                    jobs.drain(&mut sim);
91                    continue;
92                }
93                crate::work::professions::Command::Demolish { entity } => {
94                    sim.demolish(jobs, entity);
95                    jobs.drain(&mut sim);
96                    continue;
97                }
98                crate::work::professions::Command::StateMessage { entity, message } => {
99                    if message == 7 {
100                        if let Some(band) = sim.take_band(entity) {
101                            admission = Some((entity, band));
102                            break;
103                        }
104                    }
105                    sim.destroy_message(jobs, entity, message);
106                    sim.immigration_message(entity, message);
107                    sim.trade_message(entity, message);
108                    jobs.drain(&mut sim);
109                    continue;
110                }
111                crate::work::professions::Command::Control {
112                    entity,
113                    kind,
114                    index,
115                } => {
116                    sim.control(jobs, entity, kind, index);
117                    jobs.drain(&mut sim);
118                    continue;
119                }
120                crate::work::professions::Command::SetRecipe { entity, recipe } => {
121                    if let Some(p) = sim.sites.get_mut(&entity).and_then(|s| s.produce.as_mut()) {
122                        p.selector = recipe;
123                    }
124                    continue;
125                }
126                crate::work::professions::Command::Exchange { entity, sell, buy } => {
127                    sim.exchange(entity, &sell, &buy);
128                    continue;
129                }
130                crate::work::professions::Command::Trade { entity, setting } => {
131                    sim.trade_setting(entity, setting);
132                    continue;
133                }
134                crate::work::professions::Command::Upgrade { entity, template } => {
135                    sim.upgrade(jobs, entity, template);
136                    jobs.drain(&mut sim);
137                    continue;
138                }
139                crate::work::professions::Command::CancelDemolition { entity } => {
140                    sim.cancel_demolition(jobs, entity);
141                    jobs.drain(&mut sim);
142                    continue;
143                }
144                _ => {}
145            }
146            let mut workers = sim.workers();
147            let town = TownView {
148                size: sim.size,
149                types: &sim.cells.types,
150                regions: sim.regions,
151                occupants: &sim.cells.occupants,
152                objects: &sim.cells.objects,
153                floors: &sim.cells.floors,
154                entities: sim.entities,
155                happiness: sim.happiness,
156            };
157            let (size, terrain) = (sim.size, sim.terrain);
158            let floors = &sim.cells.floors;
159            let ground = move |c: i32| crate::town::floor_ground(floors, size, terrain, c);
160            let mut geometry = StorageGeometry {
161                town: &town,
162                sectors: sim.sectors,
163                ground: &ground,
164            };
165            let characters = &*sim.characters;
166            let cell = |c: usize| town.cell_of(characters[c].position);
167            let professions = sim.professions;
168            let anywhere = |p: usize| professions.works_anywhere(p);
169            let sites = &*sim.sites;
170            let priority = |e: usize| {
171                sites
172                    .get(&e)
173                    .and_then(|s| s.work_priority)
174                    .unwrap_or(i32::MAX)
175            };
176            let mut search = crate::behaviour::activities::TownSearch {
177                town: &town,
178                cell: &cell,
179                anywhere: &anywhere,
180                priority: &priority,
181                rng: &mut *sim.rng,
182            };
183            sim.workforce.apply(
184                command,
185                &mut workers,
186                sim.professions,
187                sim.homes,
188                &mut geometry,
189                &mut search,
190            );
191            sim.set_workers(&workers);
192        }
193        let rest: Vec<_> = queue.collect();
194        env.construction.road_markers.extend(road_markers);
195        if let Some((entity, band)) = admission {
196            self.admit_band(citizens, env, groups, entity, &band, newborn);
197            if !rest.is_empty() {
198                let later = std::mem::replace(&mut env.construction.commands, rest);
199                env.construction.commands.extend(later);
200                self.apply_commands(citizens, env, groups, newborn);
201            }
202        }
203    }
204
205    /// Citizen `i`'s place in the characters' tree, put in by whoever made it.
206    pub fn set_tree_slot(&mut self, i: usize, slot: i32) {
207        if let Some(ch) = self.characters.get_mut(i) {
208            ch.tree_slot = slot;
209        }
210    }
211
212    /// CharacterComponent activation (`FUN_14010ee90`): citizen `i` joins the characters' tree.
213    pub(super) fn join_tree(&mut self, i: usize, env: &mut Env) {
214        let ch = &mut self.characters[i];
215        if ch.tree_slot < 0 {
216            ch.tree_slot = env.wildlife.tree.insert(
217                crate::behaviour::character_tree::Who::Citizen(i),
218                ch.position,
219                ch.desc.radius,
220            );
221        }
222    }
223
224    /// `FUN_14004ddb0`: the band becomes citizens in fours where each nomad stands
225    /// (`FUN_140050b00` → `FUN_14011c550`, laborers): a man, a woman (married to him), then
226    /// a girl and a boy in her care. Adults are `+0x50 + RandFloat × (+0x54 − +0x50)` years old,
227    /// children `RandFloat × +0x50`. Then the nomads are gone and the town hall goes back to 2.
228    /// It runs in its command's turn (`apply_commands`).
229    pub(super) fn admit_band(
230        &mut self,
231        citizens: &mut Vec<Citizen>,
232        env: &mut Env,
233        groups: &[Option<&AnimationGroup>],
234        entity: usize,
235        band: &[(usize, [f32; 3])],
236        newborn: &mut dyn FnMut(&mut Random, &Citizen, bool) -> Citizen,
237    ) {
238        env.construction.nomads_admitted += band.len() as i32;
239        let laborer = env.construction.professions.laborer;
240        let r = self.life.clone();
241        let (lo, hi) = (r.f(0x50), r.f(0x54));
242        let mut make = |agents: &mut Self,
243                        citizens: &mut Vec<Citizen>,
244                        rng: &mut Random,
245                        position: [f32; 3],
246                        male: bool,
247                        guardian: Option<usize>| {
248            let child = guardian.is_some();
249            let age = if child {
250                rng.rand_float() * lo
251            } else {
252                rng.rand_float() * (hi - lo) + lo
253            };
254            let _flag = rng.rand_below(1);
255            let template = Citizen {
256                position,
257                ..citizens.first().cloned().unwrap_or_else(|| Citizen {
258                    name: String::new(),
259                    male,
260                    age: 0.0,
261                    lifespan: 0.0,
262                    appearance: 0,
263                    educated: false,
264                    education: 0,
265                    position,
266                    spouse: None,
267                    parent: None,
268                    school: None,
269                    alive: true,
270                })
271            };
272            let mut c = newborn(rng, &template, male);
273            c.position = position;
274            c.age = age;
275            c.alive = true;
276            let id = citizens.len();
277            citizens.push(c);
278            let mut state = CitizenState::newborn(&r);
279            state.profession = laborer;
280            state.carried = agents.carried.clone();
281            agents.states.push(state);
282            agents.registry.push(id);
283            let scale = if child { CHILD_SCALE } else { 1.0 };
284            agents
285                .characters
286                .push(Character::new(agents.desc, position, scale));
287            agents.ais.push(CitizenAi::default());
288            if let Some(g) = guardian {
289                agents.states[g].dependents.push(id);
290                citizens[id].parent = Some(g);
291            }
292            id
293        };
294        let first = citizens.len();
295        let mut k = 0;
296        while k < band.len() {
297            let man = make(self, citizens, env.rng, band[k].1, true, None);
298            if band.len() <= k + 1 {
299                break;
300            }
301            let woman = make(self, citizens, env.rng, band[k + 1].1, false, None);
302            citizens[man].spouse = Some(woman);
303            citizens[woman].spouse = Some(man);
304            if band.len() <= k + 2 {
305                break;
306            }
307            make(self, citizens, env.rng, band[k + 2].1, false, Some(woman));
308            if band.len() <= k + 3 {
309                break;
310            }
311            make(self, citizens, env.rng, band[k + 3].1, true, Some(woman));
312            k += 4;
313        }
314        // Each new citizen's entity activates as it is made (`FUN_14010ee90`), before the
315        // nomads go.
316        for i in first..citizens.len() {
317            self.join_tree(i, env);
318        }
319        let entities = entity_views(env.placed, env.templates, env.construction);
320        let adult = self.adults(citizens);
321        let (mut sim, jobs) = self.sim(citizens, env, groups, &adult, &entities);
322        sim.band_taken(entity);
323        jobs.drain(&mut sim);
324    }
325}