Movement code
This commit is contained in:
parent
42dc9a8625
commit
eeeeffba53
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -103,7 +103,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"float-ord",
|
||||
"grid",
|
||||
"lazy_static",
|
||||
"pancurses",
|
||||
"pathfinding",
|
||||
"rand",
|
||||
|
|
|
@ -8,7 +8,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
specs = "0.17.0"
|
||||
specs-derive = "0.4.1"
|
||||
lazy_static = "1"
|
||||
pancurses = "0.17.0"
|
||||
rand = "0.8.4"
|
||||
grid = "0.6.0"
|
||||
|
|
|
@ -30,10 +30,28 @@ pub struct TurnTaker {
|
|||
pub maximum: u32,
|
||||
}
|
||||
|
||||
/// Entities that can move, attack other mobile entities, use items,
|
||||
/// etc.
|
||||
#[derive(Component)]
|
||||
pub struct Mobile {
|
||||
pub next_action: MobAction,
|
||||
}
|
||||
|
||||
/// Registers every existing component with the given ECS world.
|
||||
pub fn register_all(world: &mut World) {
|
||||
world.register::<Position>();
|
||||
world.register::<CharRender>();
|
||||
world.register::<Player>();
|
||||
world.register::<TurnTaker>();
|
||||
world.register::<Mobile>();
|
||||
}
|
||||
|
||||
/// An action that a mob can perform that takes up a turn.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum MobAction {
|
||||
/// Do nothing.
|
||||
Nop,
|
||||
|
||||
/// Physically move by the given vector.
|
||||
Move(i32, i32),
|
||||
}
|
||||
|
|
54
src/main.rs
54
src/main.rs
|
@ -1,11 +1,16 @@
|
|||
use components::{register_all, CharRender, Player, Position, TurnTaker};
|
||||
use std::process::exit;
|
||||
|
||||
use components::{register_all, CharRender, MobAction, Mobile, Player, Position, TurnTaker};
|
||||
use game::{BranchConfig, DungeonLevel};
|
||||
|
||||
use pancurses::{endwin, initscr, noecho, Window};
|
||||
use player::player_turn;
|
||||
use specs::prelude::*;
|
||||
use systems::{IOSystem, TurnTickSystem};
|
||||
use systems::{MobSystem, TimeSystem};
|
||||
|
||||
mod components;
|
||||
mod game;
|
||||
mod player;
|
||||
mod rooms;
|
||||
mod systems;
|
||||
mod util;
|
||||
|
@ -25,6 +30,9 @@ fn main() {
|
|||
.with(Position { x: 5, y: 6 })
|
||||
.with(CharRender { glyph: '@' })
|
||||
.with(Player)
|
||||
.with(Mobile {
|
||||
next_action: MobAction::Nop,
|
||||
})
|
||||
.with(TurnTaker {
|
||||
next: 0,
|
||||
maximum: 10,
|
||||
|
@ -32,11 +40,49 @@ fn main() {
|
|||
.build();
|
||||
|
||||
let mut dispatcher = DispatcherBuilder::new()
|
||||
.with(TurnTickSystem, "turn_tick", &[])
|
||||
.with(IOSystem::new(), "render", &["turn_tick"])
|
||||
.with(TimeSystem, "time", &[])
|
||||
.with(MobSystem, "mobs", &[])
|
||||
.build();
|
||||
|
||||
let mut window = init_window();
|
||||
|
||||
loop {
|
||||
dispatcher.dispatch(&world);
|
||||
|
||||
let players = world.read_storage::<Player>();
|
||||
let turns = world.read_storage::<TurnTaker>();
|
||||
|
||||
if (&players, &turns).join().any(|(_plr, turn)| turn.next == 0) {
|
||||
drop(players);
|
||||
drop(turns);
|
||||
player_turn(&mut world, &mut window);
|
||||
} else {
|
||||
drop(players);
|
||||
drop(turns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the terminal to accept user input, and creates a new
|
||||
/// Window.
|
||||
fn init_window() -> Window {
|
||||
// Create a new window over the terminal.
|
||||
let window = initscr();
|
||||
|
||||
// Enable keypad mode (off by default for historical reasons), so
|
||||
// we can read special keycodes other than just characters.
|
||||
window.keypad(true);
|
||||
|
||||
// Disable echoing so the user doesn't see flickering in the
|
||||
// upper-left corner of the screen when they type a character.
|
||||
noecho();
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
/// Cleans everything up and exits the game.
|
||||
fn quit() -> ! {
|
||||
endwin();
|
||||
|
||||
exit(0)
|
||||
}
|
||||
|
|
86
src/player.rs
Normal file
86
src/player.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
//! Code for controlling the player, and for I/O.
|
||||
|
||||
use pancurses::Window;
|
||||
use specs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
components::{CharRender, MobAction, Mobile, Player, Position},
|
||||
game::DungeonLevel,
|
||||
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 {
|
||||
break action;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the state of the world onto the screen.
|
||||
fn render_screen(ecs: &mut World, screen: &mut Window) {
|
||||
// screen.clear();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Leave the cursor at the lower-left.
|
||||
screen.mv(0, 0);
|
||||
|
||||
screen.refresh();
|
||||
}
|
109
src/systems.rs
109
src/systems.rs
|
@ -1,90 +1,15 @@
|
|||
//! ECS systems.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use pancurses::{endwin, initscr, Window};
|
||||
use specs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
components::{CharRender, Player, Position, TurnTaker},
|
||||
game::DungeonLevel,
|
||||
};
|
||||
|
||||
/// System for drawing the state of the game, and potentially waiting
|
||||
/// (blocking) for user input.
|
||||
pub struct IOSystem {
|
||||
window: Window,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref WINDOW_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
impl IOSystem {
|
||||
pub fn new() -> Self {
|
||||
// See the note on `impl Send for IOSystem`.
|
||||
if WINDOW_INITIALIZED.swap(true, Ordering::Relaxed) {
|
||||
panic!("Refusing to initialize the renderer twice");
|
||||
}
|
||||
|
||||
Self { window: initscr() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IOSystem {
|
||||
fn drop(&mut self) {
|
||||
endwin();
|
||||
WINDOW_INITIALIZED.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// The `Window` type from pancurses contains a raw pointer, and as a
|
||||
// result Rust isn't convinced that it's safe to send between threads.
|
||||
// Since we guarantee that only one `Window` object is ever created at
|
||||
// a time, it is in fact safe to send the render system's data between
|
||||
// threads.
|
||||
unsafe impl Send for IOSystem {}
|
||||
|
||||
impl<'a> System<'a> for IOSystem {
|
||||
type SystemData = (
|
||||
ReadExpect<'a, DungeonLevel>,
|
||||
ReadStorage<'a, CharRender>,
|
||||
ReadStorage<'a, Position>,
|
||||
ReadStorage<'a, Player>,
|
||||
ReadStorage<'a, TurnTaker>,
|
||||
);
|
||||
|
||||
fn run(&mut self, (level, renderables, positions, players, turns): Self::SystemData) {
|
||||
self.window.clear();
|
||||
|
||||
// Draw the base level.
|
||||
level.draw(&self.window);
|
||||
|
||||
// Draw all renderable entities in the ECS.
|
||||
for (render, pos) in (&renderables, &positions).join() {
|
||||
self.window.mvaddch(pos.y as _, pos.x as _, render.glyph);
|
||||
}
|
||||
|
||||
// Leave the cursor at the lower-left.
|
||||
self.window.mv(0, 0);
|
||||
|
||||
// On the player's turn, read input.
|
||||
for (_player, turn) in (&players, &turns).join() {
|
||||
if turn.next == 0 {
|
||||
self.window.refresh();
|
||||
self.window.getch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::components::{MobAction, Mobile, Position, TurnTaker};
|
||||
|
||||
/// System for ticking the turn counter on every entity; this system
|
||||
/// implements the relationship between real-world time and in-game
|
||||
/// time.
|
||||
pub struct TurnTickSystem;
|
||||
pub struct TimeSystem;
|
||||
|
||||
impl<'a> System<'a> for TurnTickSystem {
|
||||
impl<'a> System<'a> for TimeSystem {
|
||||
type SystemData = WriteStorage<'a, TurnTaker>;
|
||||
|
||||
fn run(&mut self, mut turn_takers: Self::SystemData) {
|
||||
|
@ -93,3 +18,31 @@ impl<'a> System<'a> for TurnTickSystem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// System for executing actions that mobs have chosen.
|
||||
pub struct MobSystem;
|
||||
|
||||
impl<'a> System<'a> for MobSystem {
|
||||
type SystemData = (
|
||||
WriteStorage<'a, Position>,
|
||||
ReadStorage<'a, TurnTaker>,
|
||||
WriteStorage<'a, Mobile>,
|
||||
);
|
||||
|
||||
fn run(&mut self, (mut pos, turn, mut mob): Self::SystemData) {
|
||||
for (pos, _turn, mob) in (&mut pos, &turn, &mut mob)
|
||||
.join()
|
||||
.filter(|(_pos, turn, _mob)| turn.next == 0)
|
||||
{
|
||||
match mob.next_action {
|
||||
MobAction::Nop => {}
|
||||
MobAction::Move(dx, dy) => {
|
||||
pos.x = (pos.x as i32 + dx) as _;
|
||||
pos.y = (pos.y as i32 + dy) as _;
|
||||
}
|
||||
}
|
||||
|
||||
mob.next_action = MobAction::Nop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue