Add first draft of monsters
This commit is contained in:
parent
71a3eb3135
commit
4696b53f90
9 changed files with 262 additions and 34 deletions
|
@ -1,9 +1,11 @@
|
||||||
pub use left_mover::LeftMover;
|
|
||||||
pub use player::Player;
|
|
||||||
pub use position::Position;
|
|
||||||
pub use renderable::Renderable;
|
|
||||||
|
|
||||||
pub mod left_mover;
|
pub mod left_mover;
|
||||||
|
pub mod monster;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
pub mod renderable;
|
pub mod renderable;
|
||||||
|
|
||||||
|
pub use left_mover::LeftMover;
|
||||||
|
pub use monster::Monster;
|
||||||
|
pub use player::Player;
|
||||||
|
pub use position::Position;
|
||||||
|
pub use renderable::Renderable;
|
||||||
|
|
41
src/components/monster.rs
Normal file
41
src/components/monster.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::vga_color as vga;
|
||||||
|
use bracket_lib::prelude::*;
|
||||||
|
use specs::prelude::*;
|
||||||
|
use specs_derive::Component;
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct Monster {
|
||||||
|
pub kind: MonsterKind,
|
||||||
|
pub ticks_until_move: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum MonsterKind {
|
||||||
|
Slow,
|
||||||
|
Medium,
|
||||||
|
Fast,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glyphs_for_kind(kind: MonsterKind) -> Vec<u16> {
|
||||||
|
match kind {
|
||||||
|
MonsterKind::Slow => vec![to_cp437('Ä'), to_cp437('A')],
|
||||||
|
MonsterKind::Medium => vec![to_cp437('ö'), to_cp437('Ö')],
|
||||||
|
MonsterKind::Fast => vec![to_cp437('Ω')],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_for_kind(kind: MonsterKind) -> RGB {
|
||||||
|
match kind {
|
||||||
|
MonsterKind::Slow => RGB::named(vga::RED_BRIGHT),
|
||||||
|
MonsterKind::Medium => RGB::named(vga::GREEN_BRIGHT),
|
||||||
|
MonsterKind::Fast => RGB::named(vga::BLUE_BRIGHT),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ticks_for_kind(kind: MonsterKind) -> i32 {
|
||||||
|
match kind {
|
||||||
|
MonsterKind::Slow => 10,
|
||||||
|
MonsterKind::Medium => 8,
|
||||||
|
MonsterKind::Fast => 6,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,8 @@
|
||||||
|
/// Determined from trial and error, comparing to the original game in DOSBox,
|
||||||
|
/// Fast PC mode at 400 cycles/ms.
|
||||||
|
pub const CLOCK_PERIOD: f32 = 0.184;
|
||||||
|
pub const PLAYER_STEP_PERIOD: f32 = 1.0 / 7.5;
|
||||||
|
|
||||||
pub const SIDEBAR_POS_X: i32 = 66;
|
pub const SIDEBAR_POS_X: i32 = 66;
|
||||||
pub const SIDEBAR_POS_Y: i32 = 0;
|
pub const SIDEBAR_POS_Y: i32 = 0;
|
||||||
|
|
||||||
|
|
92
src/main.rs
92
src/main.rs
|
@ -1,17 +1,5 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use bracket_lib::prelude::*;
|
|
||||||
use components::{LeftMover, Player, Position, Renderable};
|
|
||||||
use constants::{MAP_X, MAP_Y};
|
|
||||||
use map::Map;
|
|
||||||
use resources::LevelNumber;
|
|
||||||
use sound::SoundSystem;
|
|
||||||
use sound_effects::SoundEffects;
|
|
||||||
use specs::prelude::*;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use systems::LeftWalker;
|
|
||||||
use vga_color as vga;
|
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
mod map;
|
mod map;
|
||||||
|
@ -22,6 +10,21 @@ mod sound_effects;
|
||||||
pub mod systems;
|
pub mod systems;
|
||||||
pub mod vga_color;
|
pub mod vga_color;
|
||||||
|
|
||||||
|
use bracket_lib::prelude::*;
|
||||||
|
use components::{
|
||||||
|
monster::{color_for_kind, glyphs_for_kind, ticks_for_kind, MonsterKind},
|
||||||
|
LeftMover, Monster, Player, Position, Renderable,
|
||||||
|
};
|
||||||
|
use constants::*;
|
||||||
|
use map::{Map, TileType};
|
||||||
|
use resources::{Clock, LevelNumber, ShowDebugInfo};
|
||||||
|
use sound::SoundSystem;
|
||||||
|
use sound_effects::SoundEffects;
|
||||||
|
use specs::prelude::*;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use systems::{LeftWalker, MonsterMotion};
|
||||||
|
use vga_color as vga;
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
ecs: World,
|
ecs: World,
|
||||||
sound_system: SoundSystem,
|
sound_system: SoundSystem,
|
||||||
|
@ -34,7 +37,7 @@ fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World, sound_system: &m
|
||||||
|
|
||||||
for (player, pos) in (&mut players, &mut positions).join() {
|
for (player, pos) in (&mut players, &mut positions).join() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if now - player.last_moved > Duration::from_secs_f32(0.15) {
|
if now - player.last_moved > Duration::from_secs_f32(PLAYER_STEP_PERIOD) {
|
||||||
let destination = Point {
|
let destination = Point {
|
||||||
x: pos.x + delta_x,
|
x: pos.x + delta_x,
|
||||||
y: pos.y + delta_y,
|
y: pos.y + delta_y,
|
||||||
|
@ -43,13 +46,19 @@ fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World, sound_system: &m
|
||||||
let mut sound_effects = ecs.fetch_mut::<SoundEffects>();
|
let mut sound_effects = ecs.fetch_mut::<SoundEffects>();
|
||||||
|
|
||||||
if map.in_bounds(destination) {
|
if map.in_bounds(destination) {
|
||||||
if map.can_player_enter(destination) {
|
if map.is_solid(destination) {
|
||||||
|
sound_system.play_sound(sound_effects.blocked.clone());
|
||||||
|
} else {
|
||||||
pos.x = destination.x;
|
pos.x = destination.x;
|
||||||
pos.y = destination.y;
|
pos.y = destination.y;
|
||||||
|
|
||||||
|
let mut player_pos = ecs.write_resource::<Point>();
|
||||||
|
player_pos.x = pos.x;
|
||||||
|
player_pos.y = pos.y;
|
||||||
|
|
||||||
|
ecs.write_resource::<Clock>().force_tick();
|
||||||
|
|
||||||
sound_system.play_sound(sound_effects.step.clone());
|
sound_system.play_sound(sound_effects.step.clone());
|
||||||
} else {
|
|
||||||
sound_system.play_sound(sound_effects.blocked.clone());
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let static_sound = sound_effects.get_new_static_effect(sound_system);
|
let static_sound = sound_effects.get_new_static_effect(sound_system);
|
||||||
|
@ -94,6 +103,10 @@ fn player_input(gs: &mut State, ctx: &mut BTerm) {
|
||||||
let sound_effects = gs.ecs.fetch::<SoundEffects>();
|
let sound_effects = gs.ecs.fetch::<SoundEffects>();
|
||||||
gs.sound_system.play_sound(sound_effects.pickup.clone());
|
gs.sound_system.play_sound(sound_effects.pickup.clone());
|
||||||
}
|
}
|
||||||
|
VirtualKeyCode::D => {
|
||||||
|
let mut show_debug_info = gs.ecs.write_resource::<ShowDebugInfo>();
|
||||||
|
show_debug_info.0 = !show_debug_info.0;
|
||||||
|
}
|
||||||
VirtualKeyCode::Escape => {
|
VirtualKeyCode::Escape => {
|
||||||
ctx.quit();
|
ctx.quit();
|
||||||
}
|
}
|
||||||
|
@ -136,7 +149,10 @@ impl State {
|
||||||
fn run_systems(&mut self) {
|
fn run_systems(&mut self) {
|
||||||
let mut lw = LeftWalker {};
|
let mut lw = LeftWalker {};
|
||||||
lw.run_now(&self.ecs);
|
lw.run_now(&self.ecs);
|
||||||
|
let mut mm = MonsterMotion {};
|
||||||
|
mm.run_now(&self.ecs);
|
||||||
self.ecs.maintain();
|
self.ecs.maintain();
|
||||||
|
self.ecs.write_resource::<Clock>().try_tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,17 +173,54 @@ fn main() -> BError {
|
||||||
};
|
};
|
||||||
|
|
||||||
gs.ecs.insert(LevelNumber(0));
|
gs.ecs.insert(LevelNumber(0));
|
||||||
|
gs.ecs.insert(ShowDebugInfo(false));
|
||||||
|
gs.ecs.insert(Clock {
|
||||||
|
last_ticked: Instant::now(),
|
||||||
|
has_ticked: false,
|
||||||
|
ticks: 0,
|
||||||
|
});
|
||||||
gs.ecs.insert(sound_effects);
|
gs.ecs.insert(sound_effects);
|
||||||
gs.ecs.insert(Map::new());
|
|
||||||
|
|
||||||
gs.ecs.register::<Position>();
|
gs.ecs.register::<Position>();
|
||||||
gs.ecs.register::<Renderable>();
|
gs.ecs.register::<Renderable>();
|
||||||
gs.ecs.register::<LeftMover>();
|
gs.ecs.register::<LeftMover>();
|
||||||
|
gs.ecs.register::<Monster>();
|
||||||
gs.ecs.register::<Player>();
|
gs.ecs.register::<Player>();
|
||||||
|
|
||||||
|
let map = Map::new();
|
||||||
|
let mut rng = RandomNumberGenerator::new();
|
||||||
|
for (i, tile) in &mut map.get_tiles().iter().enumerate() {
|
||||||
|
if rng.roll_dice(1, 16) < 2 && *tile == TileType::Floor {
|
||||||
|
let position = map.index_to_point2d(i);
|
||||||
|
let kind = MonsterKind::Slow;
|
||||||
|
gs.ecs
|
||||||
|
.create_entity()
|
||||||
|
.with(Position {
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
})
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: *rng.random_slice_entry(&glyphs_for_kind(kind)).unwrap(),
|
||||||
|
fg: color_for_kind(kind),
|
||||||
|
bg: RGB::named(vga::BLACK),
|
||||||
|
})
|
||||||
|
.with(Monster {
|
||||||
|
kind,
|
||||||
|
ticks_until_move: ticks_for_kind(kind),
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gs.ecs.insert(map);
|
||||||
|
|
||||||
|
let player_start_pos = Point { x: 40, y: 22 };
|
||||||
|
|
||||||
gs.ecs
|
gs.ecs
|
||||||
.create_entity()
|
.create_entity()
|
||||||
.with(Position { x: 40, y: 22 })
|
.with(Position {
|
||||||
|
x: player_start_pos.x,
|
||||||
|
y: player_start_pos.y,
|
||||||
|
})
|
||||||
.with(Renderable {
|
.with(Renderable {
|
||||||
glyph: to_cp437('☻'),
|
glyph: to_cp437('☻'),
|
||||||
fg: RGB::named(vga::YELLOW_BRIGHT),
|
fg: RGB::named(vga::YELLOW_BRIGHT),
|
||||||
|
@ -183,6 +236,9 @@ fn main() -> BError {
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
gs.ecs
|
||||||
|
.insert(Point::new(player_start_pos.x, player_start_pos.y));
|
||||||
|
|
||||||
// for i in 0..10 {
|
// for i in 0..10 {
|
||||||
// gs.ecs
|
// gs.ecs
|
||||||
// .create_entity()
|
// .create_entity()
|
||||||
|
|
14
src/map.rs
14
src/map.rs
|
@ -24,6 +24,12 @@ impl Algorithm2D for Map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Map {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Map {
|
impl Map {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut tiles = vec![TileType::Floor; MAP_SIZE];
|
let mut tiles = vec![TileType::Floor; MAP_SIZE];
|
||||||
|
@ -40,6 +46,10 @@ impl Map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_tiles(&self) -> &Vec<TileType> {
|
||||||
|
&self.tiles
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw(&self, ctx: &mut BTerm) {
|
pub fn draw(&self, ctx: &mut BTerm) {
|
||||||
// Border
|
// Border
|
||||||
ctx.fill_region(
|
ctx.fill_region(
|
||||||
|
@ -85,7 +95,7 @@ impl Map {
|
||||||
self.tiles[self.point2d_to_index(point)]
|
self.tiles[self.point2d_to_index(point)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_player_enter(&self, point: Point) -> bool {
|
pub fn is_solid(&self, point: Point) -> bool {
|
||||||
self.tile_at(point) != TileType::Wall
|
self.tile_at(point) == TileType::Wall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,34 @@
|
||||||
|
use crate::constants::CLOCK_PERIOD;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LevelNumber(pub u32);
|
pub struct LevelNumber(pub u32);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ShowDebugInfo(pub bool);
|
||||||
|
|
||||||
|
pub struct Clock {
|
||||||
|
pub last_ticked: Instant,
|
||||||
|
pub has_ticked: bool,
|
||||||
|
pub ticks: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clock {
|
||||||
|
pub fn try_tick(&mut self) {
|
||||||
|
if Instant::now() - self.last_ticked > Duration::from_secs_f32(CLOCK_PERIOD) {
|
||||||
|
self.tick();
|
||||||
|
} else {
|
||||||
|
self.has_ticked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_tick(&mut self) {
|
||||||
|
self.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self) {
|
||||||
|
self.has_ticked = true;
|
||||||
|
self.last_ticked = Instant::now();
|
||||||
|
self.ticks += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::constants::{SIDEBAR_POS_X, SIDEBAR_POS_Y};
|
use crate::constants::{SIDEBAR_POS_X, SIDEBAR_POS_Y};
|
||||||
|
use crate::resources::{Clock, ShowDebugInfo};
|
||||||
use crate::vga_color as vga;
|
use crate::vga_color as vga;
|
||||||
use crate::{LevelNumber, Player};
|
use crate::{LevelNumber, Player};
|
||||||
use bracket_lib::prelude::*;
|
use bracket_lib::prelude::*;
|
||||||
|
@ -95,6 +96,7 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) {
|
||||||
ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 23, "Save");
|
ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 23, "Save");
|
||||||
ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 24, "Restore");
|
ctx.print(SIDEBAR_POS_X + 3, SIDEBAR_POS_Y + 24, "Restore");
|
||||||
|
|
||||||
|
if ecs.read_resource::<ShowDebugInfo>().0 {
|
||||||
ctx.print_color_right(
|
ctx.print_color_right(
|
||||||
SIDEBAR_POS_X + 14,
|
SIDEBAR_POS_X + 14,
|
||||||
SIDEBAR_POS_Y,
|
SIDEBAR_POS_Y,
|
||||||
|
@ -102,4 +104,13 @@ pub fn draw(ecs: &World, ctx: &mut BTerm) {
|
||||||
RGB::named(vga::BLACK),
|
RGB::named(vga::BLACK),
|
||||||
&format!("{}", ctx.fps),
|
&format!("{}", ctx.fps),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ctx.print_color(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
RGB::named(vga::YELLOW_BRIGHT),
|
||||||
|
RGB::named(vga::BLACK),
|
||||||
|
&format!("{}", ecs.read_resource::<Clock>().ticks),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
pub use left_walker::LeftWalker;
|
|
||||||
|
|
||||||
pub mod left_walker;
|
pub mod left_walker;
|
||||||
|
pub mod monster_motion;
|
||||||
|
|
||||||
|
pub use left_walker::LeftWalker;
|
||||||
|
pub use monster_motion::MonsterMotion;
|
||||||
|
|
69
src/systems/monster_motion.rs
Normal file
69
src/systems/monster_motion.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::{
|
||||||
|
components::{
|
||||||
|
monster::{self, ticks_for_kind},
|
||||||
|
Monster, Position, Renderable,
|
||||||
|
},
|
||||||
|
map::Map,
|
||||||
|
resources::Clock,
|
||||||
|
};
|
||||||
|
use bracket_lib::{prelude::*, random::RandomNumberGenerator};
|
||||||
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
pub struct MonsterMotion {}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
impl<'a> System<'a> for MonsterMotion {
|
||||||
|
type SystemData = (
|
||||||
|
ReadExpect<'a, Clock>,
|
||||||
|
ReadExpect<'a, Point>,
|
||||||
|
ReadExpect<'a, Map>,
|
||||||
|
WriteStorage<'a, Monster>,
|
||||||
|
WriteStorage<'a, Position>,
|
||||||
|
WriteStorage<'a, Renderable>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&mut self,
|
||||||
|
(clock, player_pos, map, mut monster, mut position, mut renderable): Self::SystemData,
|
||||||
|
) {
|
||||||
|
for (monster, position, renderable) in (&mut monster, &mut position, &mut renderable).join()
|
||||||
|
{
|
||||||
|
if clock.has_ticked {
|
||||||
|
monster.ticks_until_move -= 1;
|
||||||
|
if monster.ticks_until_move <= 0 {
|
||||||
|
// Change glyph
|
||||||
|
let mut rng = RandomNumberGenerator::new();
|
||||||
|
if let Some(glyph) =
|
||||||
|
rng.random_slice_entry(&monster::glyphs_for_kind(monster.kind))
|
||||||
|
{
|
||||||
|
renderable.glyph = *glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move monster
|
||||||
|
let (mut delta_x, mut delta_y) = (0, 0);
|
||||||
|
if player_pos.x < position.x {
|
||||||
|
delta_x = -1;
|
||||||
|
}
|
||||||
|
if player_pos.x > position.x {
|
||||||
|
delta_x = 1;
|
||||||
|
}
|
||||||
|
if player_pos.y < position.y {
|
||||||
|
delta_y = -1;
|
||||||
|
}
|
||||||
|
if player_pos.y > position.y {
|
||||||
|
delta_y = 1;
|
||||||
|
}
|
||||||
|
let destination = Point {
|
||||||
|
x: position.x + delta_x,
|
||||||
|
y: position.y + delta_y,
|
||||||
|
};
|
||||||
|
if !map.is_solid(destination) {
|
||||||
|
position.x = destination.x;
|
||||||
|
position.y = destination.y;
|
||||||
|
}
|
||||||
|
monster.ticks_until_move = ticks_for_kind(monster.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue