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 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<rltk::Point>
pub target: Option<rltk::Point>,
}
#[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
}
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;
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::<InBackpack>();
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() {
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::<Map>();
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>();
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 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::<Confusion>();
gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();
gs.ecs.register::<Equippable>();
gs.ecs.register::<Equipped>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::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);

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 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::<super::Map>().unwrap().clone();
let savehelper = ecs
.create_entity()
.with(SerializationHelper{ map : mapcopy})
.with(SerializationHelper { map: mapcopy })
.marked::<SimpleMarker<SerializeMe>>()
.build();
// 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 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::<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,
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<Entity> = None;
let mut deleteme: Option<Entity> = None;
{
let entities = ecs.entities();
let helper = ecs.read_storage::<SerializationHelper>();
let player = ecs.read_storage::<Player>();
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>();
*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::<rltk::Point>();
*ppos = rltk::Point::new(pos.x, pos.y);
let mut player_resource = ecs.write_resource::<Entity>();
*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"); }
}
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::{
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::<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) {
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();
}
pub fn spawn_room(ecs: &mut World, room: &Rect) {
let mut monster_spawn_points: Vec<usize> = Vec::new();
let mut item_spawn_points: Vec<usize> = 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<usize, String> = HashMap::new();
// Scope to keep the borow checker happy
{
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
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)
}
.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();
}