Added difficulty curve + start of equips
This commit is contained in:
parent
dd784fd585
commit
d343963a95
@ -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,
|
||||
}
|
||||
|
||||
25
src/main.rs
25
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::<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);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
138
src/spawner.rs
138
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::<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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user