use crate::config::Config; pub struct Player { pub x: f32, pub y: f32, pub prev_y: f32, pub vy: f32, pub width: f32, pub height: f32, pub on_ground: bool, pub alive: bool, /// Countdown from `coyote_time` to 0. Positive value = jump still allowed. pub coyote_timer: f32, } impl Player { pub fn new(cfg: &Config) -> Self { let y = cfg.start_platform_y - cfg.player_height; Self { x: cfg.player_x, y, prev_y: y, vy: 0.0, width: cfg.player_width, height: cfg.player_height, on_ground: true, alive: true, coyote_timer: cfg.coyote_time, } } /// Apply gravity and integrate position. Must be called before collision resolution. pub fn update(&mut self, dt: f32, cfg: &Config) { self.prev_y = self.y; // Refresh coyote window while grounded; tick it down while airborne. if self.on_ground { self.coyote_timer = cfg.coyote_time; } else { self.coyote_timer = (self.coyote_timer - dt).max(0.0); } self.vy += cfg.gravity * dt; self.y += self.vy * dt; // Reset ground flag; platform collision will set it back. self.on_ground = false; if self.y > cfg.screen_height as f32 + 50.0 { self.alive = false; } } /// Jump if the coyote window is open (on ground OR just left a platform). /// `jump_multiplier` comes from active effects. pub fn jump(&mut self, cfg: &Config, jump_multiplier: f32) { if self.coyote_timer > 0.0 { self.vy = cfg.jump_velocity * jump_multiplier; self.coyote_timer = 0.0; // consume the window self.on_ground = false; } } pub fn stomp_bounce(&mut self, cfg: &Config) { self.vy = cfg.stomp_bounce_velocity; self.on_ground = false; } pub fn bottom(&self) -> f32 { self.y + self.height } pub fn prev_bottom(&self) -> f32 { self.prev_y + self.height } pub fn right(&self) -> f32 { self.x + self.width } }