rust_warrior/engine/systems/
player.rs

1//! contains system for player-controlled interactions
2
3use std::cmp;
4
5use crate::{
6    actions::{Action, Direction},
7    engine::world::World,
8    floor::Tile,
9    unit::UnitType,
10    Warrior,
11};
12
13/// This system defines all of the interactions that are possible for the
14/// player-controlled [`Warrior`](crate::warrior::Warrior). The `play_turn`
15/// method is called on [`Player`](crate::player::Player), passing a `&mut`
16/// warrior whose actions must be specified.
17pub fn player_system(world: &mut World) -> Vec<String> {
18    let mut events = Vec::new();
19
20    let (wx, wy) = world.warrior.position;
21    let (health, _) = world.warrior.hp;
22    let facing = world.warrior.facing.unwrap();
23
24    // NOTE: with the bow this range is 3 spaces
25    // TODO: conditionally determine warrior's range
26    let x_min = cmp::max(wx - 3, 0);
27    let x_max = cmp::min(wx + 3, world.floor.width as i32 - 1);
28
29    // include the x value with the tile enum variants
30    let west: Vec<(i32, Tile)> = (x_min..wx)
31        .rev()
32        .map(|i| {
33            let unit = world.other_units.iter_mut().find(|unit| {
34                let (x, _) = unit.position;
35                x == i
36            });
37            match unit {
38                Some(unit) => (i, Tile::Unit(unit.unit_type)),
39                _ => (i, Tile::Empty),
40            }
41        })
42        .collect();
43
44    // include the x value with the tile enum variants
45    let east: Vec<(i32, Tile)> = ((wx + 1)..=x_max)
46        .map(|i| {
47            let unit = world.other_units.iter_mut().find(|unit| {
48                let (x, _) = unit.position;
49                x == i
50            });
51            match unit {
52                Some(unit) => (i, Tile::Unit(unit.unit_type)),
53                _ => (i, Tile::Empty),
54            }
55        })
56        .collect();
57
58    let (ahead, behind) = match facing {
59        Direction::Forward => (east, west),
60        Direction::Backward => (west, east),
61    };
62
63    let warrior = Warrior::new(
64        world.warrior_level,
65        // `Vec<(i32, Tile)>` -> `Vec<Tile>`
66        ahead.clone().into_iter().map(|(_, t)| t).collect(),
67        // `Vec<(i32, Tile)>` -> `Vec<Tile>`
68        behind.clone().into_iter().map(|(_, t)| t).collect(),
69        health,
70        facing,
71    );
72
73    world.player.play_turn(&warrior);
74
75    if let Some(action) = warrior.action() {
76        match action {
77            Action::Walk(direction) => {
78                let target_x = if facing == direction {
79                    // either facing Forward and walking Forward
80                    // or facing Backward and walking Backward
81                    wx + 1
82                } else {
83                    // either facing Forward and walking Backward
84                    // or facing Backward and walking Forward
85                    wx - 1
86                };
87
88                let other_unit = world.other_units.iter().find(|unit| {
89                    let (x, _) = unit.position;
90                    x == target_x
91                });
92
93                match other_unit {
94                    Some(unit) => {
95                        events.push(format!(
96                            "{warrior} bumps into {enemy:?}",
97                            warrior = &world.player_name,
98                            enemy = unit.unit_type
99                        ));
100                    }
101                    _ => {
102                        events.push(format!(
103                            "{warrior} walks {direction:?}",
104                            warrior = &world.player_name,
105                            direction = direction
106                        ));
107                        world.warrior.position = (target_x, wy);
108                    }
109                }
110            }
111            Action::Attack(direction) => {
112                let target_x = if facing == direction {
113                    // either facing Forward and attacking Forward
114                    // or facing Backward and attacking Backward
115                    wx + 1
116                } else {
117                    // either facing Forward and attacking Backward
118                    // or facing Backward and attacking Forward
119                    wx - 1
120                };
121
122                let other_unit = world.other_units.iter_mut().enumerate().find(|(_, unit)| {
123                    let (x, _) = unit.position;
124                    x == target_x
125                });
126
127                match other_unit {
128                    Some((i, enemy)) => {
129                        events.push(format!(
130                            "{warrior} attacks {direction:?} and hits {enemy:?}",
131                            warrior = &world.player_name,
132                            direction = direction,
133                            enemy = enemy.unit_type
134                        ));
135                        let atk = match direction {
136                            Direction::Forward => world.warrior.atk,
137                            Direction::Backward => (world.warrior.atk as f32 / 2.0).ceil() as i32,
138                        };
139                        let (current, max) = enemy.hp;
140                        let remaining = cmp::max(current - atk, 0);
141                        events.push(format!(
142                            "{enemy:?} takes {atk} damage, {remaining} HP left",
143                            enemy = enemy.unit_type,
144                            atk = atk,
145                            remaining = remaining
146                        ));
147                        enemy.hp = (remaining, max);
148
149                        if remaining == 0 {
150                            events.push(format!("{:?} is dead!", enemy.unit_type));
151                            world.remove_unit(i);
152                        }
153                    }
154                    _ => {
155                        events.push(format!(
156                            "{warrior} attacks {direction:?} and hits nothing",
157                            warrior = &world.player_name,
158                            direction = direction
159                        ));
160                    }
161                }
162            }
163            Action::Rest => {
164                let (current, max) = world.warrior.hp;
165                if current < max {
166                    let restored = if (current + 2) > max {
167                        max - current
168                    } else {
169                        2
170                    };
171                    events.push(format!(
172                        "{warrior} regains {restored} HP from resting! Now {remaining} HP left",
173                        warrior = &world.player_name,
174                        restored = restored,
175                        remaining = current + restored
176                    ));
177                    world.warrior.hp = (current + restored, max);
178                } else {
179                    events.push(format!(
180                        "{} rests but is already at max HP",
181                        &world.player_name
182                    ));
183                };
184            }
185            Action::Rescue(direction) => {
186                let target_x = if facing == direction {
187                    // either facing Forward and rescuing Forward
188                    // or facing Backward and rescuing Backward
189                    wx + 1
190                } else {
191                    // either facing Forward and rescuing Backward
192                    // or facing Backward and rescuing Forward
193                    wx - 1
194                };
195
196                let other_unit = world.other_units.iter().enumerate().find(|(_, unit)| {
197                    let (x, _) = unit.position;
198                    x == target_x
199                });
200
201                match other_unit {
202                    Some((i, captive)) if captive.unit_type == UnitType::Captive => {
203                        events.push(format!(
204                            "{warrior} frees {captive:?} from their bindings",
205                            warrior = &world.player_name,
206                            captive = captive.unit_type
207                        ));
208                        events.push(format!("{:?} escapes!", captive.unit_type));
209                        world.remove_unit(i);
210                    }
211                    Some((_, enemy)) => {
212                        events.push(format!(
213                                "{warrior} leans {direction:?} to rescue {enemy:?}, but it is not a captive!",
214                                warrior = &world.player_name,
215                                direction = direction,
216                                enemy = enemy.unit_type
217                            ));
218                    }
219                    None => {
220                        events.push(format!(
221                            "{warrior} leans {direction:?} to rescue someone, but nobody is here",
222                            warrior = &world.player_name,
223                            direction = direction
224                        ));
225                    }
226                }
227            }
228            Action::Pivot(direction) => {
229                events.push(format!(
230                    "{warrior} pivots to face {direction:?}",
231                    warrior = &world.player_name,
232                    direction = direction
233                ));
234                world.warrior.facing = Some(direction);
235            }
236            Action::Shoot(direction) => {
237                // find the first unit in the direction the Warrior is shooting, if one exists
238                let target = world.other_units.iter_mut().enumerate().find(|(_, unit)| {
239                        let (x, _) = unit.position;
240                        match direction {
241                            Direction::Forward => {
242                                matches!(ahead.iter().find(|(_, tile)| *tile != Tile::Empty), Some((target_x, _)) if *target_x == x)
243                            }
244                            Direction::Backward => {
245                                matches!(behind.iter().find(|(_, tile)| *tile != Tile::Empty), Some((target_x, _)) if *target_x == x)
246                            }
247                        }
248                    });
249
250                match target {
251                    Some((i, enemy)) => {
252                        events.push(format!(
253                            "{warrior} lets loose an arrow {direction:?} and hits {enemy:?}",
254                            warrior = &world.player_name,
255                            direction = direction,
256                            enemy = enemy.unit_type
257                        ));
258                        let atk = (world.warrior.atk as f32 / 2.0).ceil() as i32;
259                        let (current, max) = enemy.hp;
260                        let remaining = cmp::max(current - atk, 0);
261                        events.push(format!(
262                            "{enemy:?} takes {atk} damage, {remaining} HP left",
263                            enemy = enemy.unit_type,
264                            atk = atk,
265                            remaining = remaining
266                        ));
267                        enemy.hp = (remaining, max);
268
269                        if remaining == 0 {
270                            events.push(format!("{:?} is dead!", enemy.unit_type));
271                            world.remove_unit(i);
272                        }
273                    }
274                    _ => {
275                        events.push(format!(
276                            "{warrior} lets loose an arrow {direction:?} and hits nothing",
277                            warrior = &world.player_name,
278                            direction = direction
279                        ));
280                    }
281                }
282            }
283        }
284    }
285
286    for warning in warrior.warnings() {
287        events.push(warning);
288    }
289
290    events
291}