diff --git a/src/components.rs b/src/components.rs index 6285064..c3f928f 100644 --- a/src/components.rs +++ b/src/components.rs @@ -38,7 +38,7 @@ pub enum RunState { AwaitingInput, PreRun, PlayerTurn, - MonsterTurn + MonsterTurn, } #[derive(Component, Debug)] @@ -46,29 +46,53 @@ pub struct BlocksTile {} #[derive(Component, Debug)] pub struct CombatStats { - pub max_hp : i32, - pub hp : i32, - pub defence : i32, - pub power : i32 + pub max_hp: i32, + pub hp: i32, + pub defence: i32, + pub power: i32, } #[derive(Component, Debug, Clone)] pub struct WantsToMelee { - pub target: Entity + pub target: Entity, } #[derive(Component, Debug)] pub struct SufferDamage { - pub amount : Vec + pub amount: Vec, } impl SufferDamage { pub fn new_damage(store: &mut WriteStorage, victim: Entity, amount: i32) { if let Some(sufferring) = store.get_mut(victim) { sufferring.amount.push(amount); - } else { - let dmg = SufferDamage { amount : vec![amount] }; + } else { + let dmg = SufferDamage { + amount: vec![amount], + }; store.insert(victim, dmg).expect("Unable to insert damage"); } } -} \ No newline at end of file +} + +#[derive(Component, Debug)] +pub struct Item {} + +#[derive(Component, Debug)] +pub struct Potion { + pub heal_amount: i32, +} + +#[derive(Component, Debug)] +pub struct InBackpack { + pub owner: Entity, +} + +#[derive(Component, Debug, Clone)] +pub struct WantsToPickupItem { + pub collected_by: Entity, + pub item: Entity, +} + +// UPTO https://bfnightly.bracketproductions.com/chapter_9.html +// / Next, we'll put together a system to process WantsToPickupItem notices. We'll make a new file diff --git a/src/main.rs b/src/main.rs index 6fc723b..88f2ed2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ mod spawner; use spawner::*; pub struct State { - pub ecs: World + pub ecs: World, } impl State { @@ -47,11 +47,11 @@ impl State { vis.run_now(&self.ecs); let mut mob = MonsterAI {}; mob.run_now(&self.ecs); - let mut mapindex = MapIndexingSystem{}; + let mut mapindex = MapIndexingSystem {}; mapindex.run_now(&self.ecs); - let mut melee_combat_system = MeleeCombatSystem{}; + let mut melee_combat_system = MeleeCombatSystem {}; melee_combat_system.run_now(&self.ecs); - let mut damage_system = DamageSystem{}; + let mut damage_system = DamageSystem {}; damage_system.run_now(&self.ecs); self.ecs.maintain(); } @@ -112,9 +112,7 @@ fn main() -> rltk::BError { .with_title("Rougelike Tutorial") .build()?; context.with_post_scanlines(true); - let mut gs = State { - ecs: World::new() - }; + let mut gs = State { ecs: World::new() }; gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); @@ -125,7 +123,8 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); - + gs.ecs.register::(); + gs.ecs.register::(); let map = Map::new_map_rooms_and_corridors(); let (player_x, player_y) = map.rooms[0].center(); @@ -136,17 +135,15 @@ fn main() -> rltk::BError { gs.ecs.insert(rltk::RandomNumberGenerator::new()); for room in map.rooms.iter().skip(1) { - let(x,y) = room.center(); - spawner::random_monster(&mut gs.ecs, x, y); + spawner::spawn_room(&mut gs.ecs, room); } gs.ecs.insert(map); gs.ecs.insert(player_entity); gs.ecs.insert(RunState::PreRun); - gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to my rougelike".to_string()]}); - - - + gs.ecs.insert(gamelog::GameLog { + entries: vec!["Welcome to my rougelike".to_string()], + }); // Main loop runner rltk::main_loop(context, gs) diff --git a/src/spawner.rs b/src/spawner.rs index 5461153..931ac54 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,58 +1,152 @@ -use rltk::{ RGB, RandomNumberGenerator }; -use specs::prelude::*; -use super::{CombatStats, Player, Renderable, Name, Position, Viewshed, Monster, BlocksTile}; +use crate::MAPWIDTH; -const MAX_MONSTERS : i32 = 4; -const MAX_ITEMS : i32 = 2; +use super::{ + BlocksTile, CombatStats, Item, Monster, Name, Player, Position, Potion, Rect, Renderable, + Viewshed, +}; +use rltk::{RandomNumberGenerator, RGB}; +use specs::prelude::*; + +const MAX_MONSTERS: i32 = 4; +const MAX_ITEMS: i32 = 2; // Spawns the player and returns his/her entity object. -pub fn player(ecs : &mut World, player_x : i32, player_y : i32) -> Entity { - ecs - .create_entity() - .with(Position{x: player_x, y: player_y}) - .with(Renderable{ +pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { + ecs.create_entity() + .with(Position { + x: player_x, + y: player_y, + }) + .with(Renderable { glyph: rltk::to_cp437('@'), fg: RGB::named(rltk::YELLOW), bg: RGB::named(rltk::BLACK), }) - .with(Player{}) - .with(Viewshed{visible_tiles : Vec::new(), range: 8, dirty : true}) - .with(Name{name: "Player".to_string()}) - .with(CombatStats{ max_hp: 30, hp: 30, defence: 2, power: 5}) + .with(Player {}) + .with(Viewshed { + visible_tiles: Vec::new(), + range: 8, + dirty: true, + }) + .with(Name { + name: "Player".to_string(), + }) + .with(CombatStats { + max_hp: 30, + hp: 30, + defence: 2, + power: 5, + }) .build() } pub fn random_monster(ecs: &mut World, x: i32, y: i32) { - let roll :i32; + let roll: i32; { let mut rng = ecs.write_resource::(); roll = rng.roll_dice(1, 2); } - match roll{ - 1 => { orc(ecs, x, y) } - _ => { goblin(ecs, x, y) } + match roll { + 1 => orc(ecs, x, y), + _ => goblin(ecs, x, y), } } -fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); } -fn goblin(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('g'), "Goblin"); } +fn orc(ecs: &mut World, x: i32, y: i32) { + monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); +} +fn goblin(ecs: &mut World, x: i32, y: i32) { + monster(ecs, x, y, rltk::to_cp437('g'), "Goblin"); +} -fn monster(ecs: &mut World, x: i32, y: i32, glyph : rltk::FontCharType, name : S) { +fn monster(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharType, name: S) { ecs.create_entity() - .with(Position{x, y}) - .with(Renderable{ + .with(Position { x, y }) + .with(Renderable { glyph: glyph, fg: RGB::named(rltk::RED), bg: RGB::named(rltk::BLACK), }) - .with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true}) - .with(Monster{}) - .with(Name{ name: name.to_string()}) - .with(BlocksTile{}) - .with(CombatStats{ max_hp: 16, hp: 16, defence: 1, power: 4}) + .with(Viewshed { + visible_tiles: Vec::new(), + range: 8, + dirty: true, + }) + .with(Monster {}) + .with(Name { + name: name.to_string(), + }) + .with(BlocksTile {}) + .with(CombatStats { + max_hp: 16, + hp: 16, + defence: 1, + power: 4, + }) .build(); } -// https://bfnightly.bracketproductions.com/chapter_9.html -/// Still in spawner.rs, we create a new function - spawn_room that uses these constants -pub fn spawn_room() \ No newline at end of file +pub fn spawn_room(ecs: &mut World, room: &Rect) { + let mut monster_spawn_points: Vec = Vec::new(); + let mut item_spawn_points: Vec = Vec::new(); + + // Scope to keep the borow checker happy + { + let mut rng = ecs.write_resource::(); + let num_monsters = rng.roll_dice(1, MAX_MONSTERS + 2) - 3; + let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3; + + for _i in 0..num_monsters { + let mut added = false; + while !added { + let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; + let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; + let idx = (y * MAPWIDTH) + x; + if !monster_spawn_points.contains(&idx) { + monster_spawn_points.push(idx); + added = true; + } + } + } + for _i in 0..num_items { + let mut added = false; + while !added { + let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; + let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; + let idx = (y * MAPWIDTH) + x; + if !item_spawn_points.contains(&idx) { + item_spawn_points.push(idx); + added = true; + } + } + } + } + // Actually spawn the monsters + for idx in monster_spawn_points.iter() { + let x = *idx % MAPWIDTH; + let y = *idx / MAPWIDTH; + random_monster(ecs, x as i32, y as i32); + } + + for idx in item_spawn_points.iter() { + let x = *idx % MAPWIDTH; + let y = *idx / MAPWIDTH; + health_potion(ecs, x as i32, y as i32); + } +} + +fn health_potion(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437(';'), + fg: RGB::named(rltk::MAGENTA), + bg: RGB::named(rltk::BLACK), + }) + .with(Name { + name: "Health Potion".to_string(), + }) + .with(Item {}) + .with(Potion { heal_amount: 8 }) + .build(); +}