use super::player::Player;
use crate::{
constants::*,
traits::{Body, Entity, HitBox, Sprite},
types::{GameSide, Palette},
utils::GOALIE_IMAGE_DATA,
};
use glam::{U16Vec2, Vec2};
use image::RgbaImage;
#[derive(Debug, Default)]
pub struct Goalie {
position: Vec2,
side: GameSide,
pub saves: usize,
was_colliding_with_puck: bool,
random_target_y: Option<f32>,
random_target_timer_ms: f32,
}
impl Goalie {
pub fn new(side: GameSide) -> Self {
let mut g = Self {
side,
position: Vec2::ZERO,
saves: 0,
was_colliding_with_puck: false,
random_target_y: None,
random_target_timer_ms: 0.0,
};
g.position = match side {
GameSide::Red => Vec2::new(MIN_X.into(), RED_INITIAL_POSITION.y),
GameSide::Blue => Vec2::new((MAX_X - g.size().x).into(), BLUE_INITIAL_POSITION.y),
};
g
}
pub fn register_puck_contact(&mut self, colliding: bool, puck_is_free: bool) {
if colliding && !self.was_colliding_with_puck && puck_is_free {
self.saves += 1;
}
self.was_colliding_with_puck = colliding;
}
pub fn align_to_player(&mut self, player: &Player) {
let offset = player.head_position_offset();
self.set_position(player.position() + offset - U16Vec2::new(0, 2));
}
pub fn random_walk(&mut self, deltatime: f32) {
let (min_y, max_y) = self.target_y_bounds();
self.random_target_timer_ms -= deltatime;
if self.random_target_timer_ms <= 0.0 || self.random_target_y.is_none() {
let span = (max_y - min_y).max(0.0);
self.random_target_y = Some(min_y + rand::random::<f32>() * span);
self.random_target_timer_ms = 800.0 + rand::random::<f32>() * 1200.0;
}
if let Some(target) = self.random_target_y {
let speed = 0.04; let delta = target - self.position.y;
let max_step = speed * deltatime;
let step = if delta.abs() < max_step {
delta
} else {
delta.signum() * max_step
};
self.position.y = (self.position.y + step).clamp(min_y, max_y);
}
}
fn target_y_bounds(&self) -> (f32, f32) {
let inner = match self.side {
GameSide::Red => RED_AREA_INNER_RECT,
GameSide::Blue => BLUE_AREA_INNER_RECT,
};
let min_y = inner.y as f32;
let max_y = (inner.y + inner.height - self.size().y) as f32;
(min_y, max_y)
}
}
impl Body for Goalie {
fn mass(&self) -> f32 {
f32::INFINITY
}
fn previous_position(&self) -> U16Vec2 {
self.position()
}
fn position(&self) -> U16Vec2 {
self.position.as_u16vec2()
}
fn set_position(&mut self, position: U16Vec2) {
let inner_area = match self.side {
GameSide::Red => RED_AREA_INNER_RECT,
GameSide::Blue => BLUE_AREA_INNER_RECT,
};
let min_y = inner_area.y;
let max_y = inner_area.y + inner_area.height - self.size().y;
self.position.y = position.y.clamp(min_y, max_y) as f32;
}
fn velocity(&self) -> Vec2 {
Vec2::ZERO
}
fn set_velocity(&mut self, _velocity: Vec2) {}
fn update_body(&mut self, _deltatime: f32) {}
}
impl Sprite for Goalie {
fn image(&self, _palette: Palette) -> &RgbaImage {
&GOALIE_IMAGE_DATA
.get(&self.side)
.expect("There should be goalie data")
.images[0]
}
fn hit_box(&self) -> &HitBox {
&GOALIE_IMAGE_DATA
.get(&self.side)
.expect("There should be goalie data")
.hit_boxes[0]
}
}
impl Entity for Goalie {}