use crate::{ activities::{house, overworld, Activity}, assets::Assets, State }; use comfy::{EngineContext, Vec2}; use std::{ ops::{Add, Sub}, time::Instant }; #[derive(Debug)] pub struct Ghost { /// Current electric charge of the Ghost. pub charge: f32, /// Max electric charge of the Ghost. pub max_charge: f32, /// The position of the ghost in the overworld. Expressed in tile coordinates, but /// as a float as a ghost takes more than one tick to move. pub overworld_pos: Vec2, /// Pending movement of the ghost in the overworld. pub overworld_movement_pending: Vec2, /// The current movement speed of the ghost in tiles/sec. pub overworld_movement_speed: f32, /// The timestamp of the last overworld position update. pub overworld_pos_last_update: Instant } impl Ghost { pub fn update_overworld_pos(&mut self, now: Instant) { // This calculation is extremely simplistic. It will not work properly if both // x and y of movement_pending are non-zero. But we only move left,right,up,down // so that should never happen! let secs = now .duration_since(self.overworld_pos_last_update) .as_secs_f32(); let mut movement = self.overworld_movement_pending.signum() * self.overworld_movement_speed * secs; // limit the movement to the remaining movement if self.overworld_movement_pending.x.abs() < movement.x.abs() { movement.x = self.overworld_movement_pending.x; } if self.overworld_movement_pending.y.abs() < movement.y.abs() { movement.y = self.overworld_movement_pending.y; } // execute the movement self.overworld_pos += movement; self.overworld_movement_pending -= movement; self.overworld_pos_last_update = now; } } impl Default for Ghost { fn default() -> Self { Self { charge: 1000.0, max_charge: 1000.0, overworld_pos: Vec2::ZERO, overworld_movement_pending: Vec2::ZERO, overworld_movement_speed: 0.0, overworld_pos_last_update: Instant::now() } } } #[repr(i32)] pub enum ZLayer { HumanLayer = -8, MagneticLayer = -5, ElectricLayer = -2, MapMax = -1, Human = 0, Ghost = 1, UI = 100 } impl From for i32 { fn from(value: ZLayer) -> Self { // safe because #[repr(i32)] value as i32 } } impl Sub for ZLayer { type Output = i32; fn sub(self, other: i32) -> Self::Output { i32::from(self) - other } } impl Add for ZLayer { type Output = i32; fn add(self, other: i32) -> Self::Output { i32::from(self) + other } } pub fn setup(_state: &mut State, ctx: &mut EngineContext<'_>) { Assets::load(ctx); //house::setup(state, ctx); } /// The amount of energy a ghost consumes idle. pub const GHOST_DISCHARGE_RATE: f32 = 70.0; /// The amount of energy additionally consumed by a moving ghost. pub const GHOST_DISCHARGE_RATE_MOVEMENT: f32 = 70.0; /// The amount of energy a house consumes idle. pub const HOUSE_DISCHARGE_RATE: f32 = 30.0; /// The amount of energy a ghost can charge when inside a house. pub const GHOST_CHARGE_RATE: f32 = 200.0; pub fn update(state: &mut State, ctx: &mut EngineContext<'_>) { // Update the score. It's based on time. state.score += ctx.delta * 10.0; // Update the currently active activity. match state.activity { Activity::House(_) => house::update(state, ctx), Activity::Overworld => overworld::update(state, ctx) } // We update the charge of houses here - the charge will always decrease, even if the // house is not the current activity. for (house_pos, house) in &mut state.houses { house.charge -= ctx.delta * HOUSE_DISCHARGE_RATE; match state.activity { Activity::House(pos) if *house_pos == pos => { // The ghost is currently inside the house. Increase its discarge rate. house.charge -= ctx.delta * GHOST_DISCHARGE_RATE; if house.charge < 0.0 { state.ghost.charge += house.charge; house.charge = 0.0; } // And possibly also charge the ghost when inside a house. if state.ghost.charge < state.ghost.max_charge { let charge_transfer = (ctx.delta * GHOST_CHARGE_RATE) .min(state.ghost.max_charge - state.ghost.charge) .min(house.charge); state.ghost.charge += charge_transfer; house.charge -= charge_transfer; } }, _ => {} } } // Make sure the ghost's charge never drops below 0. state.ghost.charge = state.ghost.charge.max(0.0); } pub fn draw(state: &State, engine: &EngineContext<'_>) { match state.activity { Activity::House(_) => house::draw(state, engine), Activity::Overworld => overworld::draw(state, engine) } crate::ui::draw(state, engine); }