dungeon-game/src/player.rs

107 lines
3.2 KiB
Rust
Raw Normal View History

2022-01-06 13:07:53 -06:00
//! Code for controlling the player, and for I/O.
use pancurses::Window;
use specs::prelude::*;
use crate::{
components::{CharRender, MobAction, Mobile, Player, Position},
2022-01-15 14:20:33 -06:00
level::DungeonLevel,
2022-01-06 13:07:53 -06:00
quit,
};
/// Runs a player turn on the ECS, using the given `screen` for input
/// and output.
///
/// At some point this should maybe become a system rather than a
/// standalone function.
pub fn player_turn(ecs: &mut World, screen: &mut Window) {
render_screen(ecs, screen);
let action = loop {
let key = screen.getch();
use pancurses::Input;
let action = match key {
Some(key) => match key {
Input::Character(ch) => match ch {
'.' => Some(MobAction::Nop),
'h' => Some(MobAction::Move(-1, 0)),
'j' => Some(MobAction::Move(0, 1)),
'k' => Some(MobAction::Move(0, -1)),
'l' => Some(MobAction::Move(1, 0)),
'y' => Some(MobAction::Move(-1, -1)),
'u' => Some(MobAction::Move(1, -1)),
'b' => Some(MobAction::Move(-1, 1)),
'n' => Some(MobAction::Move(1, 1)),
'q' => quit(),
_ => None,
},
Input::KeyUp => Some(MobAction::Move(0, -1)),
Input::KeyLeft => Some(MobAction::Move(-1, 0)),
Input::KeyDown => Some(MobAction::Move(0, 1)),
Input::KeyRight => Some(MobAction::Move(1, 0)),
_ => None,
},
// User closed stdin.
None => quit(),
};
if let Some(action) = action {
2022-01-06 14:04:55 -06:00
if possible(ecs, &action) {
break action;
}
2022-01-06 13:07:53 -06:00
}
};
let plrs = ecs.read_storage::<Player>();
let mut mobs = ecs.write_storage::<Mobile>();
for (_plr, mob) in (&plrs, &mut mobs).join() {
mob.next_action = action;
}
}
2022-01-06 14:04:55 -06:00
/// Checks whether an action is possible for the player to execute in
/// the given world.
fn possible(ecs: &World, action: &MobAction) -> bool {
match action {
MobAction::Nop => true,
MobAction::Move(dx, dy) => {
let players = ecs.read_storage::<Player>();
let positions = ecs.read_storage::<Position>();
let map = ecs.fetch::<DungeonLevel>();
(&players, &positions)
.join()
2022-01-12 11:53:39 -06:00
.all(|(_plr, pos)| map.tile(pos.x + dx, pos.y + dy).is_navigable())
2022-01-06 14:04:55 -06:00
}
}
}
2022-01-06 13:07:53 -06:00
/// Renders the state of the world onto the screen.
fn render_screen(ecs: &mut World, screen: &mut Window) {
// Draw the base level.
let level = ecs.fetch::<DungeonLevel>();
level.draw(screen);
// Draw all renderable entities.
let renderables = ecs.read_storage::<CharRender>();
let positions = ecs.read_storage::<Position>();
for (render, pos) in (&renderables, &positions).join() {
screen.mvaddch(pos.y as _, pos.x as _, render.glyph);
}
2022-01-15 17:53:51 -06:00
// Leave the cursor on the player's position.
let plrs = ecs.read_storage::<Player>();
let pos = ecs.read_storage::<Position>();
let (_plr, pos) = (&plrs, &pos).join().next().unwrap();
screen.mv(pos.y, pos.x);
2022-01-06 13:07:53 -06:00
screen.refresh();
}