From d343963a95572321a50158be68a13922f31e7965 Mon Sep 17 00:00:00 2001 From: benjamyn Date: Mon, 5 Dec 2022 22:17:08 +1100 Subject: [PATCH] Added difficulty curve + start of equips --- src/components.rs | 41 ++++++++---- src/main.rs | 25 +++++--- src/saveload_system.rs | 111 +++++++++++++++++++++++++-------- src/spawner.rs | 138 ++++++++++++++++++++++------------------- 4 files changed, 205 insertions(+), 110 deletions(-) diff --git a/src/components.rs b/src/components.rs index 0d08bb3..459430f 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,9 +1,9 @@ use rltk::RGB; -use specs::prelude::*; -use specs_derive::*; -use specs::saveload::{Marker, ConvertSaveload}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use specs::error::NoError; +use specs::prelude::*; +use specs::saveload::{ConvertSaveload, Marker}; +use specs_derive::*; #[derive(Component, ConvertSaveload, Clone)] pub struct Position { @@ -37,8 +37,6 @@ pub struct Name { pub name: String, } - - #[derive(Component, Serialize, Deserialize, Debug, Clone)] pub struct BlocksTile {} @@ -95,7 +93,7 @@ pub struct WantsToPickupItem { #[derive(Component, ConvertSaveload, Debug)] pub struct WantsToUseItem { pub item: Entity, - pub target: Option + pub target: Option, } #[derive(Component, ConvertSaveload, Debug, Clone)] pub struct WantsToDropItem { @@ -107,27 +105,44 @@ pub struct Consumable {} #[derive(Component, ConvertSaveload, Clone, Debug)] pub struct Ranged { - pub range : i32 + pub range: i32, } #[derive(Component, ConvertSaveload, Clone, Debug)] pub struct InflictsDamage { - pub damage : i32 + pub damage: i32, } #[derive(Component, ConvertSaveload, Clone, Debug)] pub struct AreaOfEffect { - pub radius : i32 + pub radius: i32, } #[derive(Component, ConvertSaveload, Clone, Debug)] pub struct Confusion { - pub turns : i32 + pub turns: i32, } pub struct SerializeMe; #[derive(Component, Serialize, Deserialize, Clone)] pub struct SerializationHelper { - pub map : super::map::Map -} \ No newline at end of file + pub map: super::map::Map, +} + +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum EquipmentSlot { + Melee, + Shield, +} + +#[derive(Component, Serialize, Deserialize, Clone)] +pub struct Equippable { + pub slot: EquipmentSlot, +} + +#[derive(Component, ConvertSaveload, Clone)] +pub struct Equipped { + pub owner: Entity, + pub slot: EquipmentSlot, +} diff --git a/src/main.rs b/src/main.rs index 35f3a38..0ec20ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,8 +42,8 @@ mod spawner; mod inventory_system; use inventory_system::*; -mod saveload_system; pub mod random_table; +mod saveload_system; #[derive(PartialEq, Copy, Clone)] pub enum RunState { @@ -242,7 +242,9 @@ impl GameState for State { } RunState::SaveGame => { saveload_system::save_game(&mut self.ecs); - newrunstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::LoadGame }; + newrunstate = RunState::MainMenu { + menu_selection: gui::MainMenuSelection::LoadGame, + }; } RunState::NextLevel => { self.goto_next_level(); @@ -266,7 +268,7 @@ impl State { let backpack = self.ecs.read_storage::(); let player_entity = self.ecs.fetch::(); - let mut to_delete : Vec = Vec::new(); + let mut to_delete: Vec = Vec::new(); for entity in entities.join() { let mut should_delete = true; @@ -295,21 +297,24 @@ impl State { // Delete entities that are not the player or their equipment let to_delete = self.entities_to_remove_on_level_change(); for target in to_delete { - self.ecs.delete_entity(target).expect("Unable to delete entity"); + self.ecs + .delete_entity(target) + .expect("Unable to delete entity"); } // Build a new map and place the player let worldmap; + let current_depth; { let mut worldmap_resource = self.ecs.write_resource::(); - let current_depth = worldmap_resource.depth; + current_depth = worldmap_resource.depth; *worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1); worldmap = worldmap_resource.clone(); } // Spawn bad guys for room in worldmap.rooms.iter().skip(1) { - spawner::spawn_room(&mut self.ecs, room); + spawner::spawn_room(&mut self.ecs, room, current_depth + 1); } // Place the player and update the resources @@ -333,7 +338,9 @@ impl State { // Notify the player and give them some health let mut gamelog = self.ecs.fetch_mut::(); - gamelog.entries.push("You descend to the next level, and take a moment to heal.".to_string()); + gamelog + .entries + .push("You descend to the next level, and take a moment to heal.".to_string()); let mut player_health_store = self.ecs.write_storage::(); let player_health = player_health_store.get_mut(*player_entity); if let Some(player_health) = player_health { @@ -372,6 +379,8 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.insert(SimpleMarkerAllocator::::new()); @@ -384,7 +393,7 @@ fn main() -> rltk::BError { gs.ecs.insert(rltk::RandomNumberGenerator::new()); for room in map.rooms.iter().skip(1) { - spawner::spawn_room(&mut gs.ecs, room); + spawner::spawn_room(&mut gs.ecs, room, 1); } gs.ecs.insert(map); diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 517e818..e2d3387 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -1,11 +1,12 @@ -use specs::prelude::*; -use specs::saveload::{SimpleMarker, SimpleMarkerAllocator, SerializeComponents, DeserializeComponents, MarkedBuilder}; -use specs::error::NoError; use super::components::*; +use specs::error::NoError; +use specs::prelude::*; +use specs::saveload::{ + DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator, +}; +use std::fs; use std::fs::File; use std::path::Path; -use std::fs; - macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => { @@ -37,29 +38,56 @@ macro_rules! deserialize_individually { } #[cfg(target_arch = "wasm32")] -pub fn save_game(_ecs : &mut World) { -} +pub fn save_game(_ecs: &mut World) {} #[cfg(not(target_arch = "wasm32"))] -pub fn save_game(ecs : &mut World) { +pub fn save_game(ecs: &mut World) { // Create helper let mapcopy = ecs.get_mut::().unwrap().clone(); let savehelper = ecs .create_entity() - .with(SerializationHelper{ map : mapcopy}) + .with(SerializationHelper { map: mapcopy }) .marked::>() .build(); // Actually serialize { - let data = ( ecs.entities(), ecs.read_storage::>() ); + let data = ( + ecs.entities(), + ecs.read_storage::>(), + ); let writer = File::create("./savegame.json").unwrap(); let mut serializer = serde_json::Serializer::new(writer); - serialize_individually!(ecs, serializer, data, Position, Renderable, Player, Viewshed, Monster, - Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, - AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, - WantsToDropItem, SerializationHelper ); + serialize_individually!( + ecs, + serializer, + data, + Position, + Renderable, + Player, + Viewshed, + Monster, + Name, + BlocksTile, + CombatStats, + SufferDamage, + WantsToMelee, + Item, + Consumable, + Ranged, + InflictsDamage, + AreaOfEffect, + Confusion, + ProvidesHealing, + InBackpack, + WantsToPickupItem, + WantsToUseItem, + WantsToDropItem, + SerializationHelper, + Equippable, + Equipped + ); } // Clean up @@ -70,7 +98,7 @@ pub fn does_save_exist() -> bool { Path::new("./savegame.json").exists() } -pub fn load_game(ecs : &mut World) { +pub fn load_game(ecs: &mut World) { { // Delete everything let mut to_delete = Vec::new(); @@ -86,37 +114,68 @@ pub fn load_game(ecs : &mut World) { let mut de = serde_json::Deserializer::from_str(&data); { - let mut d = (&mut ecs.entities(), &mut ecs.write_storage::>(), &mut ecs.write_resource::>()); + let mut d = ( + &mut ecs.entities(), + &mut ecs.write_storage::>(), + &mut ecs.write_resource::>(), + ); - deserialize_individually!(ecs, de, d, Position, Renderable, Player, Viewshed, Monster, - Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, - AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, - WantsToDropItem, SerializationHelper + deserialize_individually!( + ecs, + de, + d, + Position, + Renderable, + Player, + Viewshed, + Monster, + Name, + BlocksTile, + CombatStats, + SufferDamage, + WantsToMelee, + Item, + Consumable, + Ranged, + InflictsDamage, + AreaOfEffect, + Confusion, + ProvidesHealing, + InBackpack, + WantsToPickupItem, + WantsToUseItem, + WantsToDropItem, + SerializationHelper, + Equippable, + Equipped ); } - let mut deleteme : Option = None; + let mut deleteme: Option = None; { let entities = ecs.entities(); let helper = ecs.read_storage::(); let player = ecs.read_storage::(); let position = ecs.read_storage::(); - for (e,h) in (&entities, &helper).join() { + for (e, h) in (&entities, &helper).join() { let mut worldmap = ecs.write_resource::(); *worldmap = h.map.clone(); worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT]; deleteme = Some(e); } - for (e,_p,pos) in (&entities, &player, &position).join() { + for (e, _p, pos) in (&entities, &player, &position).join() { let mut ppos = ecs.write_resource::(); *ppos = rltk::Point::new(pos.x, pos.y); let mut player_resource = ecs.write_resource::(); *player_resource = e; } } - ecs.delete_entity(deleteme.unwrap()).expect("Unable to delete helper"); + ecs.delete_entity(deleteme.unwrap()) + .expect("Unable to delete helper"); } pub fn delete_save() { - if Path::new("./savegame.json").exists() { std::fs::remove_file("./savegame.json").expect("Unable to delete file"); } -} \ No newline at end of file + if Path::new("./savegame.json").exists() { + std::fs::remove_file("./savegame.json").expect("Unable to delete file"); + } +} diff --git a/src/spawner.rs b/src/spawner.rs index e81f523..d1e2c02 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,8 +1,9 @@ -use crate::{AreaOfEffect, SerializeMe}; +use crate::{AreaOfEffect, Equippable, SerializeMe}; use super::{ - BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster, Name, Player, - Position, ProvidesHealing, Ranged, Rect, Renderable, Viewshed, MAPWIDTH, random_table::RandomTable + random_table::RandomTable, BlocksTile, CombatStats, Confusion, Consumable, EquipmentSlot, + InflictsDamage, Item, Monster, Name, Player, Position, ProvidesHealing, Ranged, Rect, + Renderable, Viewshed, MAPWIDTH, }; use rltk::{RandomNumberGenerator, RGB}; use specs::{ @@ -10,6 +11,8 @@ use specs::{ saveload::{MarkedBuilder, SimpleMarker}, }; +use std::collections::HashMap; + const MAX_MONSTERS: i32 = 4; const MAX_ITEMS: i32 = 2; @@ -45,32 +48,6 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { .build() } -pub fn random_monster(ecs: &mut World, x: i32, y: 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), - } -} - -fn random_item(ecs: &mut World, x: i32, y: i32) { - let roll: i32; - { - let mut rng = ecs.write_resource::(); - roll = rng.roll_dice(1, 4); - } - match roll { - 1 => health_potion(ecs, x, y), - 2 => fireball_scroll(ecs, x, y), - 3 => confusion_scroll(ecs, x, y), - _ => magic_missile_scroll(ecs, x, y), - } -} - fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); } @@ -107,52 +84,45 @@ fn monster(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy .build(); } -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(); +#[allow(clippy::map_entry)] +pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { + let spawn_table = room_table(map_depth); + let mut spawn_points: HashMap = HashMap::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; + let num_spawns = rng.roll_dice(1, MAX_MONSTERS + 3) + (map_depth - 1) - 3; - for _i in 0..num_monsters { + for _i in 0..num_spawns { 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); + if !spawn_points.contains_key(&idx) { + spawn_points.insert(idx, spawn_table.roll(&mut rng)); 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 spawn in spawn_points.iter() { + let x = (*spawn.0 % MAPWIDTH) as i32; + let y = (*spawn.0 / MAPWIDTH) as i32; - for idx in item_spawn_points.iter() { - let x = *idx % MAPWIDTH; - let y = *idx / MAPWIDTH; - random_item(ecs, x as i32, y as i32); + match spawn.1.as_ref() { + "Goblin" => goblin(ecs, x, y), + "Orc" => orc(ecs, x, y), + "Health Potion" => health_potion(ecs, x, y), + "Fireball Scroll" => fireball_scroll(ecs, x, y), + "Confusion Scroll" => confusion_scroll(ecs, x, y), + "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y), + "Dagger" => dagger(ecs, x, y), + "Shield" => shield(ecs, x, y), + _ => {} + } } } @@ -236,12 +206,54 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) { .build(); } -fn room_table() -> RandomTable { +fn room_table(map_depth: i32) -> RandomTable { RandomTable::new() .add("Goblin", 10) - .add("Orc", 1) + .add("Orc", 1 + map_depth) .add("Health Potion", 7) - .add("Fireball Scroll", 2) - .add("Confusion Scroll", 2) + .add("Fireball Scroll", 2 + map_depth) + .add("Confusion Scroll", 2 + map_depth) .add("Magic Missile Scroll", 4) -} \ No newline at end of file + .add("Dagger", 3) + .add("Shield", 3) +} + +fn dagger(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('/'), + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + render_order: 2, + }) + .with(Name { + name: "Dagger".to_string(), + }) + .with(Item {}) + .with(Equippable { + slot: EquipmentSlot::Melee, + }) + .marked::>() + .build(); +} + +fn shield(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('('), + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + render_order: 2, + }) + .with(Name { + name: "Shield".to_string(), + }) + .with(Item {}) + .with(Equippable { + slot: EquipmentSlot::Shield, + }) + .marked::>() + .build(); +}