Added difficulty curve + start of equips

This commit is contained in:
Benjamyn Love 2022-12-05 22:17:08 +11:00
parent dd784fd585
commit d343963a95
4 changed files with 205 additions and 110 deletions

View File

@ -1,9 +1,9 @@
use rltk::RGB; use rltk::RGB;
use specs::prelude::*; use serde::{Deserialize, Serialize};
use specs_derive::*;
use specs::saveload::{Marker, ConvertSaveload};
use serde::{Serialize, Deserialize};
use specs::error::NoError; use specs::error::NoError;
use specs::prelude::*;
use specs::saveload::{ConvertSaveload, Marker};
use specs_derive::*;
#[derive(Component, ConvertSaveload, Clone)] #[derive(Component, ConvertSaveload, Clone)]
pub struct Position { pub struct Position {
@ -37,8 +37,6 @@ pub struct Name {
pub name: String, pub name: String,
} }
#[derive(Component, Serialize, Deserialize, Debug, Clone)] #[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct BlocksTile {} pub struct BlocksTile {}
@ -95,7 +93,7 @@ pub struct WantsToPickupItem {
#[derive(Component, ConvertSaveload, Debug)] #[derive(Component, ConvertSaveload, Debug)]
pub struct WantsToUseItem { pub struct WantsToUseItem {
pub item: Entity, pub item: Entity,
pub target: Option<rltk::Point> pub target: Option<rltk::Point>,
} }
#[derive(Component, ConvertSaveload, Debug, Clone)] #[derive(Component, ConvertSaveload, Debug, Clone)]
pub struct WantsToDropItem { pub struct WantsToDropItem {
@ -107,27 +105,44 @@ pub struct Consumable {}
#[derive(Component, ConvertSaveload, Clone, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Ranged { pub struct Ranged {
pub range : i32 pub range: i32,
} }
#[derive(Component, ConvertSaveload, Clone, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct InflictsDamage { pub struct InflictsDamage {
pub damage : i32 pub damage: i32,
} }
#[derive(Component, ConvertSaveload, Clone, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct AreaOfEffect { pub struct AreaOfEffect {
pub radius : i32 pub radius: i32,
} }
#[derive(Component, ConvertSaveload, Clone, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Confusion { pub struct Confusion {
pub turns : i32 pub turns: i32,
} }
pub struct SerializeMe; pub struct SerializeMe;
#[derive(Component, Serialize, Deserialize, Clone)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct SerializationHelper { pub struct SerializationHelper {
pub map : super::map::Map 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,
} }

View File

@ -42,8 +42,8 @@ mod spawner;
mod inventory_system; mod inventory_system;
use inventory_system::*; use inventory_system::*;
mod saveload_system;
pub mod random_table; pub mod random_table;
mod saveload_system;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum RunState { pub enum RunState {
@ -242,7 +242,9 @@ impl GameState for State {
} }
RunState::SaveGame => { RunState::SaveGame => {
saveload_system::save_game(&mut self.ecs); 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 => { RunState::NextLevel => {
self.goto_next_level(); self.goto_next_level();
@ -266,7 +268,7 @@ impl State {
let backpack = self.ecs.read_storage::<InBackpack>(); let backpack = self.ecs.read_storage::<InBackpack>();
let player_entity = self.ecs.fetch::<Entity>(); let player_entity = self.ecs.fetch::<Entity>();
let mut to_delete : Vec<Entity> = Vec::new(); let mut to_delete: Vec<Entity> = Vec::new();
for entity in entities.join() { for entity in entities.join() {
let mut should_delete = true; let mut should_delete = true;
@ -295,21 +297,24 @@ impl State {
// Delete entities that are not the player or their equipment // Delete entities that are not the player or their equipment
let to_delete = self.entities_to_remove_on_level_change(); let to_delete = self.entities_to_remove_on_level_change();
for target in to_delete { 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 // Build a new map and place the player
let worldmap; let worldmap;
let current_depth;
{ {
let mut worldmap_resource = self.ecs.write_resource::<Map>(); let mut worldmap_resource = self.ecs.write_resource::<Map>();
let current_depth = worldmap_resource.depth; current_depth = worldmap_resource.depth;
*worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1); *worldmap_resource = Map::new_map_rooms_and_corridors(current_depth + 1);
worldmap = worldmap_resource.clone(); worldmap = worldmap_resource.clone();
} }
// Spawn bad guys // Spawn bad guys
for room in worldmap.rooms.iter().skip(1) { 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 // Place the player and update the resources
@ -333,7 +338,9 @@ impl State {
// Notify the player and give them some health // Notify the player and give them some health
let mut gamelog = self.ecs.fetch_mut::<GameLog>(); let mut gamelog = self.ecs.fetch_mut::<GameLog>();
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::<CombatStats>(); let mut player_health_store = self.ecs.write_storage::<CombatStats>();
let player_health = player_health_store.get_mut(*player_entity); let player_health = player_health_store.get_mut(*player_entity);
if let Some(player_health) = player_health { if let Some(player_health) = player_health {
@ -372,6 +379,8 @@ fn main() -> rltk::BError {
gs.ecs.register::<Confusion>(); gs.ecs.register::<Confusion>();
gs.ecs.register::<SimpleMarker<SerializeMe>>(); gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>(); gs.ecs.register::<SerializationHelper>();
gs.ecs.register::<Equippable>();
gs.ecs.register::<Equipped>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
@ -384,7 +393,7 @@ fn main() -> rltk::BError {
gs.ecs.insert(rltk::RandomNumberGenerator::new()); gs.ecs.insert(rltk::RandomNumberGenerator::new());
for room in map.rooms.iter().skip(1) { 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); gs.ecs.insert(map);

View File

@ -1,11 +1,12 @@
use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator, SerializeComponents, DeserializeComponents, MarkedBuilder};
use specs::error::NoError;
use super::components::*; 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::fs::File;
use std::path::Path; use std::path::Path;
use std::fs;
macro_rules! serialize_individually { macro_rules! serialize_individually {
($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => { ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => {
@ -37,29 +38,56 @@ macro_rules! deserialize_individually {
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn save_game(_ecs : &mut World) { pub fn save_game(_ecs: &mut World) {}
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn save_game(ecs : &mut World) { pub fn save_game(ecs: &mut World) {
// Create helper // Create helper
let mapcopy = ecs.get_mut::<super::Map>().unwrap().clone(); let mapcopy = ecs.get_mut::<super::Map>().unwrap().clone();
let savehelper = ecs let savehelper = ecs
.create_entity() .create_entity()
.with(SerializationHelper{ map : mapcopy}) .with(SerializationHelper { map: mapcopy })
.marked::<SimpleMarker<SerializeMe>>() .marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
// Actually serialize // Actually serialize
{ {
let data = ( ecs.entities(), ecs.read_storage::<SimpleMarker<SerializeMe>>() ); let data = (
ecs.entities(),
ecs.read_storage::<SimpleMarker<SerializeMe>>(),
);
let writer = File::create("./savegame.json").unwrap(); let writer = File::create("./savegame.json").unwrap();
let mut serializer = serde_json::Serializer::new(writer); let mut serializer = serde_json::Serializer::new(writer);
serialize_individually!(ecs, serializer, data, Position, Renderable, Player, Viewshed, Monster, serialize_individually!(
Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, ecs,
AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, serializer,
WantsToDropItem, SerializationHelper ); 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 // Clean up
@ -70,7 +98,7 @@ pub fn does_save_exist() -> bool {
Path::new("./savegame.json").exists() Path::new("./savegame.json").exists()
} }
pub fn load_game(ecs : &mut World) { pub fn load_game(ecs: &mut World) {
{ {
// Delete everything // Delete everything
let mut to_delete = Vec::new(); 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 de = serde_json::Deserializer::from_str(&data);
{ {
let mut d = (&mut ecs.entities(), &mut ecs.write_storage::<SimpleMarker<SerializeMe>>(), &mut ecs.write_resource::<SimpleMarkerAllocator<SerializeMe>>()); let mut d = (
&mut ecs.entities(),
&mut ecs.write_storage::<SimpleMarker<SerializeMe>>(),
&mut ecs.write_resource::<SimpleMarkerAllocator<SerializeMe>>(),
);
deserialize_individually!(ecs, de, d, Position, Renderable, Player, Viewshed, Monster, deserialize_individually!(
Name, BlocksTile, CombatStats, SufferDamage, WantsToMelee, Item, Consumable, Ranged, InflictsDamage, ecs,
AreaOfEffect, Confusion, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem, de,
WantsToDropItem, SerializationHelper 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<Entity> = None; let mut deleteme: Option<Entity> = None;
{ {
let entities = ecs.entities(); let entities = ecs.entities();
let helper = ecs.read_storage::<SerializationHelper>(); let helper = ecs.read_storage::<SerializationHelper>();
let player = ecs.read_storage::<Player>(); let player = ecs.read_storage::<Player>();
let position = ecs.read_storage::<Position>(); let position = ecs.read_storage::<Position>();
for (e,h) in (&entities, &helper).join() { for (e, h) in (&entities, &helper).join() {
let mut worldmap = ecs.write_resource::<super::map::Map>(); let mut worldmap = ecs.write_resource::<super::map::Map>();
*worldmap = h.map.clone(); *worldmap = h.map.clone();
worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT]; worldmap.tile_content = vec![Vec::new(); super::map::MAPCOUNT];
deleteme = Some(e); 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::<rltk::Point>(); let mut ppos = ecs.write_resource::<rltk::Point>();
*ppos = rltk::Point::new(pos.x, pos.y); *ppos = rltk::Point::new(pos.x, pos.y);
let mut player_resource = ecs.write_resource::<Entity>(); let mut player_resource = ecs.write_resource::<Entity>();
*player_resource = e; *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() { pub fn delete_save() {
if Path::new("./savegame.json").exists() { std::fs::remove_file("./savegame.json").expect("Unable to delete file"); } if Path::new("./savegame.json").exists() {
std::fs::remove_file("./savegame.json").expect("Unable to delete file");
}
} }

View File

@ -1,8 +1,9 @@
use crate::{AreaOfEffect, SerializeMe}; use crate::{AreaOfEffect, Equippable, SerializeMe};
use super::{ use super::{
BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster, Name, Player, random_table::RandomTable, BlocksTile, CombatStats, Confusion, Consumable, EquipmentSlot,
Position, ProvidesHealing, Ranged, Rect, Renderable, Viewshed, MAPWIDTH, random_table::RandomTable InflictsDamage, Item, Monster, Name, Player, Position, ProvidesHealing, Ranged, Rect,
Renderable, Viewshed, MAPWIDTH,
}; };
use rltk::{RandomNumberGenerator, RGB}; use rltk::{RandomNumberGenerator, RGB};
use specs::{ use specs::{
@ -10,6 +11,8 @@ use specs::{
saveload::{MarkedBuilder, SimpleMarker}, saveload::{MarkedBuilder, SimpleMarker},
}; };
use std::collections::HashMap;
const MAX_MONSTERS: i32 = 4; const MAX_MONSTERS: i32 = 4;
const MAX_ITEMS: i32 = 2; const MAX_ITEMS: i32 = 2;
@ -45,32 +48,6 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
.build() .build()
} }
pub fn random_monster(ecs: &mut World, x: i32, y: i32) {
let roll: i32;
{
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
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::<RandomNumberGenerator>();
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) { fn orc(ecs: &mut World, x: i32, y: i32) {
monster(ecs, x, y, rltk::to_cp437('o'), "Orc"); monster(ecs, x, y, rltk::to_cp437('o'), "Orc");
} }
@ -107,52 +84,45 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy
.build(); .build();
} }
pub fn spawn_room(ecs: &mut World, room: &Rect) { #[allow(clippy::map_entry)]
let mut monster_spawn_points: Vec<usize> = Vec::new(); pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
let mut item_spawn_points: Vec<usize> = Vec::new(); let spawn_table = room_table(map_depth);
let mut spawn_points: HashMap<usize, String> = HashMap::new();
// Scope to keep the borow checker happy // Scope to keep the borow checker happy
{ {
let mut rng = ecs.write_resource::<RandomNumberGenerator>(); let mut rng = ecs.write_resource::<RandomNumberGenerator>();
let num_monsters = rng.roll_dice(1, MAX_MONSTERS + 2) - 3; let num_spawns = rng.roll_dice(1, MAX_MONSTERS + 3) + (map_depth - 1) - 3;
let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3;
for _i in 0..num_monsters { for _i in 0..num_spawns {
let mut added = false; let mut added = false;
while !added { while !added {
let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; 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 y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize;
let idx = (y * MAPWIDTH) + x; let idx = (y * MAPWIDTH) + x;
if !monster_spawn_points.contains(&idx) { if !spawn_points.contains_key(&idx) {
monster_spawn_points.push(idx); spawn_points.insert(idx, spawn_table.roll(&mut rng));
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; added = true;
} }
} }
} }
} }
// Actually spawn the monsters // Actually spawn the monsters
for idx in monster_spawn_points.iter() { for spawn in spawn_points.iter() {
let x = *idx % MAPWIDTH; let x = (*spawn.0 % MAPWIDTH) as i32;
let y = *idx / MAPWIDTH; let y = (*spawn.0 / MAPWIDTH) as i32;
random_monster(ecs, x as i32, y as i32);
}
for idx in item_spawn_points.iter() { match spawn.1.as_ref() {
let x = *idx % MAPWIDTH; "Goblin" => goblin(ecs, x, y),
let y = *idx / MAPWIDTH; "Orc" => orc(ecs, x, y),
random_item(ecs, x as i32, y as i32); "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(); .build();
} }
fn room_table() -> RandomTable { fn room_table(map_depth: i32) -> RandomTable {
RandomTable::new() RandomTable::new()
.add("Goblin", 10) .add("Goblin", 10)
.add("Orc", 1) .add("Orc", 1 + map_depth)
.add("Health Potion", 7) .add("Health Potion", 7)
.add("Fireball Scroll", 2) .add("Fireball Scroll", 2 + map_depth)
.add("Confusion Scroll", 2) .add("Confusion Scroll", 2 + map_depth)
.add("Magic Missile Scroll", 4) .add("Magic Missile Scroll", 4)
.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::<SimpleMarker<SerializeMe>>()
.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::<SimpleMarker<SerializeMe>>()
.build();
} }