use std::time::{Duration, Instant}; use sfml::graphics::{FloatRect, IntRect, View}; use sfml::system::Vector2; use sfml::window::Event; use crate::engine::controls::{Action, Controls}; use crate::engine::math::{clamp, duration_as_f32, duration_as_frame}; use crate::engine::physics; use crate::engine::renderer::Drawable; use crate::engine::scene::Updatable; use crate::engine::texture::Texture; /// The different sides a character can face. pub enum Side { /// The character looks to the left. Left, /// The character looks to the right. Right, } /// This enum represents the type of damages that the character can get. #[derive(Copy, Clone)] pub enum Damage { /// No damage were made None, /// A single point of life has been taken from the character. One, /// The character died. Death, } impl Side { /// Returns the side corresponding to the force. /// /// Returns None if the force is null. pub fn from_force(force: Vector2) -> Option { if force.x > 0.0 { Some(Side::Right) } else if force.x < 0.0 { Some(Side::Left) } else { None } } /// Returns the offset in the texture. pub fn offset(&self) -> i32 { match *self { Side::Left => 32, Side::Right => 0, } } } /// A character, enemy or controllable. pub struct Character { /// The position of the character. pub position: Vector2, /// The speed of the character. pub speed: Vector2, /// If the player is controlling a character. controls: Option, /// The side of the character. side: Side, /// The number of hp of the character. hp: u32, /// The counter of jumps. /// /// When it's 0, the character can no longer jump. jump_counter: usize, /// The maximum number of jumps a character can do. /// /// It's reset when the character hits the ground. max_jump: usize, /// The timer of the character's animation. animation_timer: Instant, /// Whether the character is walking or not. walking: bool, /// Indicates that the player has released the jump button. can_jump: bool, } impl Character { /// Creates a character in (0, 0). fn generic(controls: Option) -> Character { Character { position: Vector2::new(0.0, 0.0), speed: Vector2::new(0.0, 0.0), controls, side: Side::Right, hp: 1, jump_counter: 1, max_jump: 1, animation_timer: Instant::now(), can_jump: true, walking: false, } } /// Creates a character in (0, 0). pub fn new() -> Character { Character::generic(None) } /// Creates a character with the specified controls. pub fn with_controls(controls: Controls) -> Character { Character::generic(Some(controls)) } /// Sets the position of the character. pub fn set_position>>(&mut self, position: S) { self.position = position.into(); } /// Makes the character jump. pub fn jump(&mut self) { if self.can_jump && self.jump_counter > 0 { self.jump_counter -= 1; self.speed.y = physics::JUMP_SPEED.y; } } /// Resets the jump counter. pub fn ground_collision(&mut self) { self.jump_counter = self.max_jump; } /// Make the player fall. pub fn fall_off(&mut self) { if self.jump_counter == self.max_jump { self.jump_counter -= 1; } } /// Returns a reference to the controls. pub fn controls(&self) -> &Option { &self.controls } /// Returns a view that looks at the character. pub fn view(&self) -> View { View::new(self.position, Vector2::new(24.0 * 16.0, 24.0 * 9.0)) } /// Returns the collision bounding box of the character. pub fn bbox(&self) -> FloatRect { FloatRect::new(self.position.x + 8.0, self.position.y, 16.0, 32.0) } /// Returns the number of hp of the character. pub fn hp(&self) -> u32 { self.hp } /// Returns true if the character is alive. pub fn is_alive(&self) -> bool { self.hp > 0 } /// Returns true if the character is dead. pub fn is_dead(&self) -> bool { self.hp == 0 } /// Takes the corresponding damage. pub fn take_damage(&mut self, damage: Damage) { match damage { Damage::None => (), Damage::One => self.hp -= 1, Damage::Death => self.hp = 0, } } } impl Updatable for Character { fn update(&mut self, duration: &Duration) { let mut force: Vector2 = Vector2::new(0.0, 0.0); if let Some(ref controls) = self.controls { force += controls.direction(); } if let Some(side) = Side::from_force(force) { if !self.walking { self.animation_timer = Instant::now(); } self.walking = true; self.side = side; } else { if self.walking { self.animation_timer = Instant::now(); } self.walking = false; } let duration = duration_as_f32(duration); // Compute acceleration let accel = physics::G + force * 64.0 * 64.0; // Compute speed self.speed.x *= physics::GROUND_FRICTION.x; self.speed += accel * duration; if self.speed.y > physics::MAXIMUM_VERTICAL_SPEED { self.speed.y = physics::MAXIMUM_VERTICAL_SPEED; } let limit = match self.controls { Some(controls) if controls.is_running() => physics::MAXIMUM_RUNNING_SPEED, _ => physics::MAXIMUM_WALKING_SPEED, }; self.speed.x = clamp(self.speed.x, -limit, limit); // Compute position self.position += self.speed * duration; } fn manage_event(&mut self, event: &Event) { let action = if let Some(ref controls) = self.controls { controls.convert(event) } else { None }; match action { Some(Action::Jump(true)) => { self.jump(); self.can_jump = false; } Some(Action::Jump(false)) => { self.can_jump = true; } _ => (), } } } impl Drawable for Character { fn texture(&self) -> Texture { Texture::Rusty } fn texture_rect(&self) -> IntRect { let frame = duration_as_frame(&Instant::now().duration_since(self.animation_timer), 4); let offset = if self.walking { 64 } else { 0 }; IntRect::new(self.side.offset() + offset, frame * 32, 32, 32) } fn position(&self) -> Vector2 { self.position } }