freenukum 0.4.0

A clone of the 1991 DOS game Duke Nukem 1
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>

use crate::{
    actor::{
        ActParameters, Actor, ActorExt, ActorType, CreateActorWithDetails,
        RenderParameters, ShotParameters, ShotProcessing,
        SingleAnimationType,
    },
    geometry::RectExt,
    level::{tiles::LevelTiles, BackgroundTileStrategy},
    HorizontalDirection, RangedIterator, Result, Sizes, ANIMATION_FAN,
};
use sdl2::rect::{Point, Rect};

#[derive(Debug)]
pub(crate) struct Fan {
    tile: usize,
    frame: RangedIterator,
    running: usize,
    position: Rect,
    direction: HorizontalDirection,
}

impl CreateActorWithDetails for Fan {
    type Details = HorizontalDirection;

    fn create_with_details(
        direction: HorizontalDirection,
        pos: Point,
        sizes: &dyn Sizes,
        _tiles: &mut LevelTiles,
    ) -> Actor {
        Actor::Fan(Self {
            tile: ANIMATION_FAN,
            frame: RangedIterator::new(4),
            running: 10,
            position: Rect::new(
                pos.x,
                pos.y - sizes.height() as i32,
                sizes.width(),
                sizes.height() * 2,
            ),
            direction,
        })
    }
}

impl ActorExt for Fan {
    fn act(&mut self, p: ActParameters) {
        match self.running {
            0 => {}
            1 => {
                self.frame.next();
            }
            2 => {}
            3 => {}
            4 => {}
            5 => {
                self.frame.next();
            }
            6 => {}
            7 => {}
            8 => {
                self.frame.next();
            }
            9 => {}
            10 => {
                self.frame.next();
            }
            _ => unreachable!(),
        }
        if self.running < 10 && self.running > 0 {
            self.running -= 1;
        } else if self.running == 10
            && p.hero.position.geometry.overlaps_vertically(self.position)
        {
            let mut hdistance = p
                .hero
                .position
                .geometry
                .horizontal_distance(self.position);

            let fan_direction = self.direction.as_factor_i32();

            if (fan_direction * hdistance) < 0 {
                return;
            }

            if hdistance == 0 {
                hdistance = p.sizes.half_width() as i32 * fan_direction;
            }

            let range = p.sizes.half_width() as i32 * 8;
            if hdistance.abs() < range {
                p.hero.position.push_horizontally(
                    p.sizes,
                    p.tiles,
                    fan_direction * p.sizes.width() as i32,
                );
            }
        }
    }

    fn render(&mut self, p: RenderParameters) -> Result<()> {
        let mut pos = self.position.top_left();
        p.renderer
            .place_tile(self.tile + self.frame.current() * 2, pos)?;
        pos.y += p.sizes.height() as i32;
        p.renderer
            .place_tile(self.tile + self.frame.current() * 2 + 1, pos)?;
        Ok(())
    }

    fn can_get_shot(&self) -> bool {
        true
    }

    fn shot(&mut self, p: ShotParameters) -> ShotProcessing {
        self.running = 9;
        p.game_commands.add_actor(
            ActorType::SingleAnimation(SingleAnimationType::Steam),
            self.position
                .top_left()
                .offset(0, -(p.sizes.height() as i32 / 2)),
        );
        ShotProcessing::Absorb
    }

    fn position(&self) -> Rect {
        self.position
    }

    fn is_in_foreground(&self) -> bool {
        true
    }

    fn background_tile_strategy(&self) -> BackgroundTileStrategy {
        BackgroundTileStrategy::CopyFromAbove
    }
}