nightshade-api 0.47.0

Procedural high level API for the nightshade game engine
Documentation
//! Keyboard, mouse, and time queries, plus the movement helpers every game
//! rewrites.

use nightshade::prelude::*;

/// True while `key` is held down.
#[inline]
pub fn key_down(world: &World, key: KeyCode) -> bool {
    world.resources.input.keyboard.is_key_pressed(key)
}

/// True only on the frame `key` went down.
#[inline]
pub fn key_pressed(world: &World, key: KeyCode) -> bool {
    world.resources.input.keyboard.just_pressed(key)
}

/// True while the mouse button is held down.
#[inline]
pub fn mouse_down(world: &World, button: MouseButton) -> bool {
    let state = world.resources.input.mouse.state;
    match button {
        MouseButton::Left => state.contains(MouseState::LEFT_CLICKED),
        MouseButton::Right => state.contains(MouseState::RIGHT_CLICKED),
        MouseButton::Middle => state.contains(MouseState::MIDDLE_CLICKED),
        _ => false,
    }
}

/// True only on the frame the mouse button went down.
#[inline]
pub fn mouse_clicked(world: &World, button: MouseButton) -> bool {
    let state = world.resources.input.mouse.state;
    match button {
        MouseButton::Left => state.contains(MouseState::LEFT_JUST_PRESSED),
        MouseButton::Right => state.contains(MouseState::RIGHT_JUST_PRESSED),
        MouseButton::Middle => state.contains(MouseState::MIDDLE_JUST_PRESSED),
        _ => false,
    }
}

/// The cursor position in window pixels.
#[inline]
pub fn mouse_position(world: &World) -> Vec2 {
    world.resources.input.mouse.position
}

/// How far the cursor moved this frame.
#[inline]
pub fn mouse_delta(world: &World) -> Vec2 {
    world.resources.input.mouse.position_delta
}

/// How far the scroll wheel moved this frame: positive scrolling up or away,
/// negative toward you, 0.0 when still.
#[inline]
pub fn mouse_scroll(world: &World) -> f32 {
    world.resources.input.mouse.wheel_delta.y
}

/// True when the cursor is over a retained UI widget. Gate world clicks on this
/// so a press on a panel or button does not also place or pick in the scene.
#[inline]
pub fn pointer_over_ui(world: &World) -> bool {
    world
        .resources
        .retained_ui
        .interaction
        .hovered_entity
        .is_some()
}

/// A signed 1d input: -1.0 while `negative` is held, 1.0 while `positive` is
/// held, 0.0 otherwise or when both are held.
#[inline]
pub fn axis(world: &World, negative: KeyCode, positive: KeyCode) -> f32 {
    let mut value = 0.0;
    if key_down(world, negative) {
        value -= 1.0;
    }
    if key_down(world, positive) {
        value += 1.0;
    }
    value
}

/// WASD as a camera relative movement direction on the ground plane,
/// normalized so diagonals are not faster. Returns zero when nothing is held.
/// Multiply by speed and [`delta_time`] and add to a position.
pub fn wasd(world: &World) -> Vec3 {
    let forward_input = axis(world, KeyCode::KeyS, KeyCode::KeyW);
    let strafe_input = axis(world, KeyCode::KeyA, KeyCode::KeyD);
    if forward_input == 0.0 && strafe_input == 0.0 {
        return Vec3::zeros();
    }
    let camera_forward = crate::camera::camera_forward(world);
    let mut planar_forward = Vec3::new(camera_forward.x, 0.0, camera_forward.z);
    if planar_forward.magnitude_squared() < f32::EPSILON {
        planar_forward = Vec3::new(0.0, 0.0, -1.0);
    }
    let planar_forward = planar_forward.normalize();
    let right = nalgebra_glm::cross(&planar_forward, &Vec3::y());
    (planar_forward * forward_input + right * strafe_input).normalize()
}

/// Seconds the previous frame took. Multiply rates by this for frame rate
/// independent motion.
#[inline]
pub fn delta_time(world: &World) -> f32 {
    world.resources.window.timing.delta_time
}

/// Seconds since the program started.
#[inline]
pub fn elapsed_seconds(world: &World) -> f32 {
    world.resources.window.timing.uptime_milliseconds as f32 / 1000.0
}

/// A gamepad button. `South`/`East`/`West`/`North` are the face buttons (A/B/X/Y
/// on Xbox), with `LeftTrigger`/`RightTrigger` as the shoulder bumpers and
/// `LeftTrigger2`/`RightTrigger2` as the analog triggers.
#[cfg(feature = "gamepad")]
pub use nightshade::prelude::gilrs::Button as GamepadButton;

/// A gamepad analog axis: the two sticks (`LeftStickX`/`Y`, `RightStickX`/`Y`)
/// and the trigger axes (`LeftZ`/`RightZ`).
#[cfg(feature = "gamepad")]
pub use nightshade::prelude::gilrs::Axis as GamepadAxis;

/// True while `button` is held on the active gamepad.
#[cfg(feature = "gamepad")]
pub fn gamepad_button_down(world: &mut World, button: GamepadButton) -> bool {
    query_active_gamepad(world).is_some_and(|gamepad| gamepad.is_pressed(button))
}

/// True only on the frame `button` went down on the active gamepad.
#[cfg(feature = "gamepad")]
pub fn gamepad_button_pressed(world: &World, button: GamepadButton) -> bool {
    world.resources.input.gamepad.just_pressed(button)
}

/// An analog axis on the active gamepad, normalized to about -1.0 to 1.0.
#[cfg(feature = "gamepad")]
pub fn gamepad_axis(world: &mut World, axis: GamepadAxis) -> f32 {
    query_active_gamepad(world)
        .map(|gamepad| gamepad.value(axis))
        .unwrap_or(0.0)
}

/// The left stick as a 2d vector, x to the right and y up.
#[cfg(feature = "gamepad")]
pub fn gamepad_left_stick(world: &mut World) -> Vec2 {
    Vec2::new(
        gamepad_axis(world, GamepadAxis::LeftStickX),
        gamepad_axis(world, GamepadAxis::LeftStickY),
    )
}

/// The right stick as a 2d vector, x to the right and y up.
#[cfg(feature = "gamepad")]
pub fn gamepad_right_stick(world: &mut World) -> Vec2 {
    Vec2::new(
        gamepad_axis(world, GamepadAxis::RightStickX),
        gamepad_axis(world, GamepadAxis::RightStickY),
    )
}

/// True when a gamepad is connected and active.
#[cfg(feature = "gamepad")]
pub fn gamepad_connected(world: &mut World) -> bool {
    query_active_gamepad(world).is_some()
}