Add first draft of monsters

This commit is contained in:
Alex Page 2022-01-26 04:09:19 -05:00
parent 71a3eb3135
commit 4696b53f90
9 changed files with 262 additions and 34 deletions

View file

@ -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
View 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,
}
}

View file

@ -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;

View file

@ -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()

View file

@ -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
} }
} }

View file

@ -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;
}
}

View file

@ -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),
);
}
} }

View file

@ -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;

View 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);
}
}
}
}
}