freenukum/
shot.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
3
4use crate::{
5    actor::{
6        ActorMessageQueue, ActorType, ActorsList, SingleAnimationType,
7    },
8    game::GameCommands,
9    hero::Hero,
10    level::tiles::LevelTiles,
11    rendering::Renderer,
12    HorizontalDirection, Result, Sizes, LEVELWINDOW_WIDTH, OBJECT_SHOT,
13};
14use sdl2::rect::Rect;
15
16pub type ShotList = Vec<Shot>;
17
18#[derive(Debug)]
19pub struct Shot {
20    position: Rect,
21    pub is_alive: bool,
22    pub direction: HorizontalDirection,
23    counter: usize,
24    countdown: usize,
25}
26
27impl Shot {
28    pub fn new(
29        sizes: &dyn Sizes,
30        x: i32,
31        y: i32,
32        direction: HorizontalDirection,
33    ) -> Self {
34        let w = sizes.width() / 4;
35        let h = sizes.height() / 4 * 3;
36        Shot {
37            position: Rect::new(
38                x + sizes.width() as i32 - w as i32 / 2
39                    + direction.as_factor_i32() * w as i32,
40                y + sizes.height() as i32 - h as i32,
41                w,
42                h,
43            ),
44            is_alive: true,
45            direction,
46            counter: 0,
47            countdown: 2,
48        }
49    }
50
51    /// Returns whether the shot is still alive after acting.
52    pub fn act(
53        &mut self,
54        sizes: &dyn Sizes,
55        hero: &mut Hero,
56        actors: &mut ActorsList,
57        tiles: &mut LevelTiles,
58        game_commands: &mut dyn GameCommands,
59        actor_message_queue: &mut ActorMessageQueue,
60    ) -> bool {
61        self.counter += 1;
62        self.counter %= 4;
63
64        if self.countdown == 1 {
65            self.is_alive = false;
66            self.countdown -= 1;
67        }
68
69        let x_start = hero.position.geometry.x()
70            - sizes.width() as i32 * LEVELWINDOW_WIDTH as i32 / 2;
71        let x_end = hero.position.geometry.x()
72            + hero.position.geometry.w as i32
73            + sizes.width() as i32 * LEVELWINDOW_WIDTH as i32 / 2;
74
75        if self.countdown >= 2 {
76            let distance =
77                sizes.half_width() as i32 * self.direction.as_factor_i32();
78
79            // we only push half of the distance, but do it twice, so that
80            // also the intermediate position gets covered, not just the
81            // end position.
82            self.push(
83                sizes,
84                hero,
85                actors,
86                tiles,
87                distance,
88                game_commands,
89                actor_message_queue,
90            );
91            self.push(
92                sizes,
93                hero,
94                actors,
95                tiles,
96                distance,
97                game_commands,
98                actor_message_queue,
99            );
100
101            let x = self.position.x;
102
103            if x < x_start || x > x_end {
104                self.countdown = 1;
105            }
106        }
107        self.is_alive
108    }
109
110    pub fn render(
111        &self,
112        renderer: &mut dyn Renderer,
113        sizes: &dyn Sizes,
114        draw_collision_bounds: bool,
115    ) -> Result<()> {
116        if self.is_alive {
117            let mut destrect = self.position;
118            destrect.set_x(
119                destrect.x() + destrect.width() as i32 / 2
120                    - sizes.half_width() as i32,
121            );
122            destrect.set_width(sizes.width());
123
124            renderer.place_tile(
125                OBJECT_SHOT + self.counter,
126                destrect.top_left(),
127            )?;
128            if draw_collision_bounds {
129                let color = crate::collision_bounds_color();
130                renderer.draw_rect(self.position, color)?;
131            }
132        }
133        Ok(())
134    }
135
136    #[allow(clippy::too_many_arguments)]
137    pub fn push(
138        &mut self,
139        sizes: &dyn Sizes,
140        hero: &mut Hero,
141        actors: &mut ActorsList,
142        tiles: &mut LevelTiles,
143        offset: i32,
144        game_commands: &mut dyn GameCommands,
145        actor_message_queue: &mut ActorMessageQueue,
146    ) {
147        if self.countdown >= 2 {
148            self.position.x += offset;
149            if self.countdown == 2 {
150                if actors.process_shot(
151                    self.position,
152                    sizes,
153                    tiles,
154                    game_commands,
155                    hero,
156                    actor_message_queue,
157                ) {
158                    self.countdown = 1;
159                }
160            } else {
161                self.countdown -= 1;
162            }
163        }
164        if self.countdown >= 2 && tiles.collides(sizes, self.position) {
165            self.countdown = 1;
166            game_commands.add_actor(
167                ActorType::SingleAnimation(SingleAnimationType::Explosion),
168                self.position.top_left().offset(
169                    self.position.width() as i32 / 2
170                        - sizes.half_width() as i32,
171                    0,
172                ),
173            );
174        }
175    }
176
177    pub fn set_is_alive(&mut self, is_alive: bool) {
178        self.is_alive = is_alive;
179    }
180}