Compare commits

..

3 Commits

Author SHA1 Message Date
a1ffc7b95d
Add chapter 2.10 2022-01-27 20:14:25 +01:00
f2f4c40c28
WIP: 2.10 2022-01-27 19:42:51 +01:00
ecbecc1a58
WIP: 2.10 2022-01-27 18:28:40 +01:00
14 changed files with 544 additions and 159 deletions

85
Cargo.lock generated
View File

@ -22,14 +22,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
@ -114,6 +109,7 @@ dependencies = [
"byteorder", "byteorder",
"lazy_static", "lazy_static",
"parking_lot", "parking_lot",
"serde",
] ]
[[package]] [[package]]
@ -122,6 +118,7 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4db22c32c68bd9330ab982f8ff7ffe7b10541d16ea7d7d51aac074499850402b" checksum = "4db22c32c68bd9330ab982f8ff7ffe7b10541d16ea7d7d51aac074499850402b"
dependencies = [ dependencies = [
"serde",
"ultraviolet", "ultraviolet",
] ]
@ -170,6 +167,7 @@ dependencies = [
"rand", "rand",
"rand_xorshift", "rand_xorshift",
"regex", "regex",
"serde",
] ]
[[package]] [[package]]
@ -443,12 +441,13 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.3" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 0.1.10",
"crossbeam-utils 0.8.6", "crossbeam-utils 0.7.2",
"maybe-uninit",
] ]
[[package]] [[package]]
@ -753,11 +752,12 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf"
dependencies = [ dependencies = [
"ahash", "ahash",
"autocfg",
] ]
[[package]] [[package]]
@ -822,6 +822,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.0" version = "0.3.0"
@ -924,6 +930,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.4.1"
@ -1367,6 +1379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [ dependencies = [
"rand_core", "rand_core",
"serde",
] ]
[[package]] [[package]]
@ -1463,6 +1476,8 @@ name = "roguelike"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"rltk", "rltk",
"serde",
"serde_json",
"specs", "specs",
"specs-derive", "specs-derive",
] ]
@ -1477,6 +1492,12 @@ dependencies = [
"owned_ttf_parser", "owned_ttf_parser",
] ]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]] [[package]]
name = "safe_arch" name = "safe_arch"
version = "0.5.2" version = "0.5.2"
@ -1515,9 +1536,34 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.135" version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "shared_library" name = "shared_library"
@ -1531,9 +1577,9 @@ dependencies = [
[[package]] [[package]]
name = "shred" name = "shred"
version = "0.12.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb0210289d693217926314867c807e0b7b42f7e23c136adb31f8697f5bf242d3" checksum = "c5f08237e667ac94ad20f8878b5943d91a93ccb231428446c57c21c57779016d"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"hashbrown", "hashbrown",
@ -1591,15 +1637,16 @@ dependencies = [
[[package]] [[package]]
name = "specs" name = "specs"
version = "0.17.0" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcc1e4ba7ab1f08ecb3d7e2f693defc3907e2c03bb0924f9978be45b364f83f" checksum = "fff28a29366aff703d5da8a7e2c8875dc8453ac1118f842cbc0fa70c7db51240"
dependencies = [ dependencies = [
"crossbeam-queue", "crossbeam-queue",
"hashbrown", "hashbrown",
"hibitset", "hibitset",
"log", "log",
"rayon", "rayon",
"serde",
"shred", "shred",
"shrev", "shrev",
"tuple_utils", "tuple_utils",

View File

@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
rltk = {version = "0.8.0"} rltk = { version = "0.8.0", features = ["serde"] }
specs = "0.17.0" specs = { version = "0.16.1", features = ["serde"] }
specs-derive = "0.4.1" specs-derive = { version = "0.4.1" }
serde = { version = "^1.0.44", features = ["derive"] }
serde_json = "1.0.44"

View File

@ -1,14 +1,17 @@
use rltk::{Point, RGB}; use rltk::{Point, RGB};
use serde::{Deserialize, Serialize};
use specs::error::NoError;
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{ConvertSaveload, Marker};
use specs_derive::*; use specs_derive::*;
#[derive(Component)] #[derive(Component, ConvertSaveload, Clone)]
pub struct Position { pub struct Position {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
} }
#[derive(Component)] #[derive(Component, ConvertSaveload, Clone)]
pub struct Renderable { pub struct Renderable {
pub glyph: rltk::FontCharType, pub glyph: rltk::FontCharType,
pub fg: RGB, pub fg: RGB,
@ -16,28 +19,28 @@ pub struct Renderable {
pub render_order: i32, pub render_order: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct Player {} pub struct Player {}
#[derive(Component)] #[derive(Component, ConvertSaveload, Clone)]
pub struct Viewshed { pub struct Viewshed {
pub visible_tiles: Vec<rltk::Point>, pub visible_tiles: Vec<rltk::Point>,
pub range: i32, pub range: i32,
pub dirty: bool, pub dirty: bool,
} }
#[derive(Component)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct Monster {} pub struct Monster {}
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Name { pub struct Name {
pub name: String, pub name: String,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct BlocksTile {} pub struct BlocksTile {}
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct CombatStats { pub struct CombatStats {
pub max_hp: i32, pub max_hp: i32,
pub hp: i32, pub hp: i32,
@ -45,12 +48,12 @@ pub struct CombatStats {
pub power: i32, pub power: i32,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct WantsToMelee { pub struct WantsToMelee {
pub target: Entity, pub target: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct SufferDamage { pub struct SufferDamage {
pub amount: Vec<i32>, pub amount: Vec<i32>,
} }
@ -68,60 +71,67 @@ impl SufferDamage {
} }
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Item {} pub struct Item {}
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Potion { pub struct Potion {
pub heal_amount: i32, pub heal_amount: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct InBackpack { pub struct InBackpack {
pub owner: Entity, pub owner: Entity,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct WantsToPickupItem { pub struct WantsToPickupItem {
pub collected_by: Entity, pub collected_by: Entity,
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct WantsToUseItem { pub struct WantsToUseItem {
pub item: Entity, pub item: Entity,
pub target: Option<Point>, pub target: Option<Point>,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct WantsToDropItem { pub struct WantsToDropItem {
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {} pub struct Consumable {}
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct ProvidesHealing { pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct Ranged { pub struct Ranged {
pub range: i32, pub range: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct InflictsDamage { pub struct InflictsDamage {
pub damage: i32, pub damage: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct AreaOfEffect { pub struct AreaOfEffect {
pub radius: i32, pub radius: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Confusion { 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,
}

View File

@ -1,14 +1,14 @@
use rltk::Rltk; use rltk::{Point, VirtualKeyCode};
use rltk::RGB; use rltk::RGB;
use rltk::{BTerm, Point, VirtualKeyCode}; use rltk::Rltk;
use specs::prelude::*; use specs::prelude::*;
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::Map; use crate::Map;
use crate::Name; use crate::Name;
use crate::Player; use crate::Player;
use crate::Position; use crate::Position;
use crate::{CombatStats, InBackpack, State, Viewshed};
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.draw_box( ctx.draw_box(
@ -388,3 +388,121 @@ pub fn ranged_target(
(ItemMenuResult::NoResponse, None) (ItemMenuResult::NoResponse, None)
} }
#[derive(PartialEq, Copy, Clone)]
pub enum MainMenuSelection {
NewGame,
LoadGame,
Quit,
}
#[derive(PartialEq, Copy, Clone)]
pub enum MainMenuResult {
NoSelection { selected: MainMenuSelection },
Selected { selected: MainMenuSelection },
}
pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
let save_exists = super::save_load_system::does_save_exist();
let runstate = gs.ecs.fetch::<RunState>();
ctx.print_color_centered(
15,
RGB::named(rltk::YELLOW),
RGB::named(rltk::BLACK),
"Rust Roguelike Tutorial",
);
if let RunState::MainMenu {
menu_selection: selection,
} = *runstate
{
if selection == MainMenuSelection::NewGame {
ctx.print_color_centered(
24,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
"Begin New Game",
);
} else {
ctx.print_color_centered(
24,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"Begin New Game",
);
}
if save_exists {
if selection == MainMenuSelection::LoadGame {
ctx.print_color_centered(
25,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
"Load Game",
);
} else {
ctx.print_color_centered(
25,
RGB::named(rltk::WHITE),
RGB::named(rltk::BLACK),
"Load Game",
);
}
}
if selection == MainMenuSelection::Quit {
ctx.print_color_centered(
26,
RGB::named(rltk::MAGENTA),
RGB::named(rltk::BLACK),
"Quit",
);
} else {
ctx.print_color_centered(26, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit");
}
return match ctx.key {
None => MainMenuResult::NoSelection {
selected: selection,
},
Some(key) => match key {
VirtualKeyCode::Escape => MainMenuResult::NoSelection {
selected: MainMenuSelection::Quit,
},
VirtualKeyCode::Up | VirtualKeyCode::K => {
let newselection = match selection {
MainMenuSelection::NewGame => MainMenuSelection::Quit,
MainMenuSelection::LoadGame => MainMenuSelection::NewGame,
MainMenuSelection::Quit => MainMenuSelection::LoadGame,
};
MainMenuResult::NoSelection {
selected: newselection,
}
}
VirtualKeyCode::Down | VirtualKeyCode::J => {
let newselection = match selection {
MainMenuSelection::NewGame => MainMenuSelection::LoadGame,
MainMenuSelection::LoadGame => MainMenuSelection::Quit,
MainMenuSelection::Quit => MainMenuSelection::NewGame,
};
MainMenuResult::NoSelection {
selected: newselection,
}
}
VirtualKeyCode::Return | VirtualKeyCode::I => MainMenuResult::Selected {
selected: selection,
},
_ => MainMenuResult::NoSelection {
selected: selection,
},
},
};
}
MainMenuResult::NoSelection {
selected: MainMenuSelection::NewGame,
}
}

View File

@ -1,10 +1,11 @@
use rltk::Rltk;
use specs::prelude::*; use specs::prelude::*;
use crate::{AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name, Position, Potion, ProvidesHealing, Ranged, State, SufferDamage, WantsToDropItem, WantsToPickupItem, WantsToUseItem};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::gui::ItemMenuResult; use crate::{
use crate::spawner::{confusion_scroll, player}; AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name,
Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, WantsToPickupItem,
WantsToUseItem,
};
pub struct ItemCollectionSystem {} pub struct ItemCollectionSystem {}
@ -80,7 +81,7 @@ impl<'a> System<'a> for ItemUseSystem {
map, map,
mut suffer_damage, mut suffer_damage,
aoe, aoe,
mut confused mut confused,
) = data; ) = data;
for (entity, use_item) in (&entities, &wants_use).join() { for (entity, use_item) in (&entities, &wants_use).join() {
@ -88,7 +89,7 @@ impl<'a> System<'a> for ItemUseSystem {
let mut targets: Vec<Entity> = Vec::new(); let mut targets: Vec<Entity> = Vec::new();
match use_item.target { match use_item.target {
None => { targets.push(*player_entity) } None => targets.push(*player_entity),
Some(target) => { Some(target) => {
let area_effect = aoe.get(use_item.item); let area_effect = aoe.get(use_item.item);
match area_effect { match area_effect {
@ -99,8 +100,11 @@ impl<'a> System<'a> for ItemUseSystem {
} }
} }
Some(area_effect) => { Some(area_effect) => {
let mut black_tiles = rltk::field_of_view(target, area_effect.radius, &*map); let mut black_tiles =
black_tiles.retain(|p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1); rltk::field_of_view(target, area_effect.radius, &*map);
black_tiles.retain(|p| {
p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1
});
for tile_idx in black_tiles.iter() { for tile_idx in black_tiles.iter() {
let idx = map.xy_idx(tile_idx.x, tile_idx.y); let idx = map.xy_idx(tile_idx.x, tile_idx.y);
for mob in map.tile_content[idx].iter() { for mob in map.tile_content[idx].iter() {
@ -119,7 +123,10 @@ impl<'a> System<'a> for ItemUseSystem {
if entity == *player_entity { if entity == *player_entity {
let mob_name = names.get(*mob).unwrap(); let mob_name = names.get(*mob).unwrap();
let item_name = names.get(use_item.item).unwrap(); let item_name = names.get(use_item.item).unwrap();
game_log.entries.push(format!("You use {} on {}, inflicting {} hp.", item_name.name, mob_name.name, item_damages.damage)); game_log.entries.push(format!(
"You use {} on {}, inflicting {} hp.",
item_name.name, mob_name.name, item_damages.damage
));
} }
used_item = true; used_item = true;
@ -153,16 +160,20 @@ impl<'a> System<'a> for ItemUseSystem {
if entity == *player_entity { if entity == *player_entity {
let mob_name = names.get(*mob).unwrap(); let mob_name = names.get(*mob).unwrap();
let item_name = names.get(use_item.item).unwrap(); let item_name = names.get(use_item.item).unwrap();
game_log.entries.push(format!("You use {} on {}, confusing them.", item_name.name, mob_name.name)); game_log.entries.push(format!(
"You use {} on {}, confusing them.",
item_name.name, mob_name.name
));
} }
} }
} }
} }
for mob in add_confusion.iter() { for mob in add_confusion.iter() {
confused.insert(mob.0, Confusion { turns: mob.1 }).expect("Unable to insert status"); confused
.insert(mob.0, Confusion { turns: mob.1 })
.expect("Unable to insert status");
} }
if used_item { if used_item {
let consumable = consumables.get(use_item.item); let consumable = consumables.get(use_item.item);
match consumable { match consumable {

View File

@ -1,7 +1,8 @@
use std::thread::spawn; extern crate serde;
use rltk::{GameState, Point, RGB}; use rltk::{GameState, Point};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
use components::*; use components::*;
use damage_system::DamageSystem; use damage_system::DamageSystem;
@ -27,6 +28,7 @@ mod player;
mod rect; mod rect;
mod spawner; mod spawner;
mod visibility_system; mod visibility_system;
mod save_load_system;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum RunState { pub enum RunState {
@ -36,7 +38,14 @@ pub enum RunState {
MonsterTurn, MonsterTurn,
ShowInventory, ShowInventory,
ShowDropItem, ShowDropItem,
ShowTargeting { range: i32, item: Entity }, ShowTargeting {
range: i32,
item: Entity,
},
MainMenu {
menu_selection: gui::MainMenuSelection,
},
SaveGame,
} }
pub struct State { pub struct State {
@ -51,14 +60,14 @@ impl State {
let mut mob = MonsterAI {}; let mut mob = MonsterAI {};
mob.run_now(&self.ecs); mob.run_now(&self.ecs);
let mut mapindex = MapIndexingSystem {}; let mut map_index = MapIndexingSystem {};
mapindex.run_now(&self.ecs); map_index.run_now(&self.ecs);
let mut meleesystem = MeleeCombatSystem {}; let mut melee_system = MeleeCombatSystem {};
meleesystem.run_now(&self.ecs); melee_system.run_now(&self.ecs);
let mut damagesystem = DamageSystem {}; let mut damage_system = DamageSystem {};
damagesystem.run_now(&self.ecs); damage_system.run_now(&self.ecs);
let mut inventory = ItemCollectionSystem {}; let mut inventory = ItemCollectionSystem {};
inventory.run_now(&self.ecs); inventory.run_now(&self.ecs);
@ -75,9 +84,17 @@ impl State {
impl GameState for State { impl GameState for State {
fn tick(&mut self, ctx: &mut rltk::Rltk) { fn tick(&mut self, ctx: &mut rltk::Rltk) {
let mut new_run_state;
{
let run_state = self.ecs.fetch::<RunState>();
new_run_state = *run_state;
}
ctx.cls(); ctx.cls();
{ match new_run_state {
RunState::MainMenu { .. } => {}
_ => {
draw_map(&self.ecs, ctx); draw_map(&self.ecs, ctx);
let positions = self.ecs.read_storage::<Position>(); let positions = self.ecs.read_storage::<Position>();
@ -95,38 +112,33 @@ impl GameState for State {
gui::draw_ui(&self.ecs, ctx); gui::draw_ui(&self.ecs, ctx);
} }
let mut newrunstate;
{
let runstate = self.ecs.fetch::<RunState>();
newrunstate = *runstate;
} }
match newrunstate { match new_run_state {
RunState::PreRun => { RunState::PreRun => {
self.run_systems(); self.run_systems();
newrunstate = RunState::AwaitingInput; new_run_state = RunState::AwaitingInput;
} }
RunState::AwaitingInput => newrunstate = player_input(self, ctx), RunState::AwaitingInput => new_run_state = player_input(self, ctx),
RunState::PlayerTurn => { RunState::PlayerTurn => {
self.run_systems(); self.run_systems();
newrunstate = RunState::MonsterTurn; new_run_state = RunState::MonsterTurn;
} }
RunState::MonsterTurn => { RunState::MonsterTurn => {
self.run_systems(); self.run_systems();
newrunstate = RunState::AwaitingInput; new_run_state = RunState::AwaitingInput;
} }
RunState::ShowInventory => { RunState::ShowInventory => {
let result = gui::show_inventory(self, ctx); let result = gui::show_inventory(self, ctx);
match result.0 { match result.0 {
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => { gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap(); let item_entity = result.1.unwrap();
let is_ranged = self.ecs.read_storage::<Ranged>(); let is_ranged = self.ecs.read_storage::<Ranged>();
let is_item_ranged = is_ranged.get(item_entity); let is_item_ranged = is_ranged.get(item_entity);
if let Some(is_item_ranged) = is_item_ranged { if let Some(is_item_ranged) = is_item_ranged {
newrunstate = RunState::ShowTargeting { new_run_state = RunState::ShowTargeting {
range: is_item_ranged.range, range: is_item_ranged.range,
item: item_entity, item: item_entity,
} }
@ -135,10 +147,13 @@ impl GameState for State {
intent intent
.insert( .insert(
*self.ecs.fetch::<Entity>(), *self.ecs.fetch::<Entity>(),
WantsToUseItem { item: item_entity, target: None }, WantsToUseItem {
item: item_entity,
target: None,
},
) )
.expect("Unable to insert intent"); .expect("Unable to insert intent");
newrunstate = RunState::PlayerTurn; new_run_state = RunState::PlayerTurn;
} }
} }
} }
@ -146,7 +161,7 @@ impl GameState for State {
RunState::ShowDropItem => { RunState::ShowDropItem => {
let result = gui::drop_item_menu(self, ctx); let result = gui::drop_item_menu(self, ctx);
match result.0 { match result.0 {
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => { gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap(); let item_entity = result.1.unwrap();
@ -157,27 +172,60 @@ impl GameState for State {
WantsToDropItem { item: item_entity }, WantsToDropItem { item: item_entity },
) )
.expect("Unable to insert intent"); .expect("Unable to insert intent");
newrunstate = RunState::PlayerTurn; new_run_state = RunState::PlayerTurn;
} }
} }
} }
RunState::ShowTargeting { range, item } => { RunState::ShowTargeting { range, item } => {
let result = gui::ranged_target(self, ctx, range); let result = gui::ranged_target(self, ctx, range);
match result.0 { match result.0 {
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput,
gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => { gui::ItemMenuResult::Selected => {
let mut intent = self.ecs.write_storage::<WantsToUseItem>(); let mut intent = self.ecs.write_storage::<WantsToUseItem>();
intent.insert(*self.ecs.fetch::<Entity>(), WantsToUseItem { item, target: result.1 }).expect("Unable to insert intent"); intent
newrunstate = RunState::PlayerTurn; .insert(
*self.ecs.fetch::<Entity>(),
WantsToUseItem {
item,
target: result.1,
},
)
.expect("Unable to insert intent");
new_run_state = RunState::PlayerTurn;
} }
} }
} }
RunState::MainMenu { .. } => {
let result = gui::main_menu(self, ctx);
match result {
gui::MainMenuResult::NoSelection { selected } => {
new_run_state = RunState::MainMenu {
menu_selection: selected,
}
}
gui::MainMenuResult::Selected { selected } => match selected {
gui::MainMenuSelection::NewGame => new_run_state = RunState::PreRun,
gui::MainMenuSelection::LoadGame => {
save_load_system::load_game(&mut self.ecs);
new_run_state = RunState::AwaitingInput;
save_load_system::delete_save();
}
gui::MainMenuSelection::Quit => ::std::process::exit(0),
},
}
}
RunState::SaveGame => {
save_load_system::save_game(&mut self.ecs);
new_run_state = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::Quit,
};
}
} }
{ {
let mut runwriter = self.ecs.write_resource::<RunState>(); let mut run_writer = self.ecs.write_resource::<RunState>();
*runwriter = newrunstate; *run_writer = new_run_state;
} }
damage_system::delete_the_dead(&mut self.ecs); damage_system::delete_the_dead(&mut self.ecs);
} }
@ -193,11 +241,7 @@ fn main() -> rltk::BError {
context.with_post_scanlines(true); context.with_post_scanlines(true);
let mut gs = State { ecs: World::new() }; let mut gs = State { ecs: World::new() };
gs.ecs.insert(RunState::PreRun);
gs.ecs.insert(rltk::RandomNumberGenerator::new()); gs.ecs.insert(rltk::RandomNumberGenerator::new());
gs.ecs.insert(gamelog::GameLog {
entries: vec!["Welcome to Rusty Roguelike".to_string()],
});
gs.ecs.register::<Position>(); gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>(); gs.ecs.register::<Renderable>();
@ -220,18 +264,29 @@ fn main() -> rltk::BError {
gs.ecs.register::<InflictsDamage>(); gs.ecs.register::<InflictsDamage>();
gs.ecs.register::<AreaOfEffect>(); gs.ecs.register::<AreaOfEffect>();
gs.ecs.register::<Confusion>(); gs.ecs.register::<Confusion>();
let map = Map::new_map_rooms_and_corridors(); gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
let map = Map::new_map_rooms_and_corridors();
let (player_x, player_y) = map.rooms[0].center(); let (player_x, player_y) = map.rooms[0].center();
gs.ecs.insert(Point::new(player_x, player_y));
let player_entity = spawner::player(&mut gs.ecs, player_x, player_y); let player_entity = spawner::player(&mut gs.ecs, player_x, player_y);
gs.ecs.insert(player_entity);
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);
} }
gs.ecs.insert(map); gs.ecs.insert(map);
gs.ecs.insert(Point::new(player_x, player_y));
gs.ecs.insert(player_entity);
gs.ecs.insert(RunState::MainMenu {
menu_selection: gui::MainMenuSelection::NewGame,
});
gs.ecs.insert(gamelog::GameLog {
entries: vec!["Welcome to Rusty Roguelike".to_string()],
});
rltk::main_loop(context, gs) rltk::main_loop(context, gs)
} }

View File

@ -1,11 +1,12 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, RGB, Rltk};
use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
use crate::rect::Rect; use crate::rect::Rect;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum TileType { pub enum TileType {
Wall, Wall,
Floor, Floor,
@ -17,6 +18,7 @@ pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
pub const MAX_MONSTER: i32 = 4; pub const MAX_MONSTER: i32 = 4;
pub const MAX_ITEMS: i32 = 2; pub const MAX_ITEMS: i32 = 2;
#[derive(Default, Serialize, Deserialize, Clone)]
pub struct Map { pub struct Map {
pub tiles: Vec<TileType>, pub tiles: Vec<TileType>,
pub rooms: Vec<Rect>, pub rooms: Vec<Rect>,
@ -25,6 +27,9 @@ pub struct Map {
pub revealed_tiles: Vec<bool>, pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>, pub visible_tiles: Vec<bool>,
pub blocked: Vec<bool>, pub blocked: Vec<bool>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub tile_content: Vec<Vec<Entity>>, pub tile_content: Vec<Vec<Entity>>,
} }
@ -60,6 +65,27 @@ impl Map {
} }
} }
fn is_exit_valid(&self, x: i32, y: i32) -> bool {
if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 {
return false;
}
let idx = self.xy_idx(x, y);
!self.blocked[idx]
}
pub fn populate_blocked(&mut self) {
for (i, tile) in self.tiles.iter_mut().enumerate() {
self.blocked[i] = *tile == TileType::Wall;
}
}
pub fn clear_content_index(&mut self) {
for content in self.tile_content.iter_mut() {
content.clear();
}
}
pub fn new_map_rooms_and_corridors() -> Map { pub fn new_map_rooms_and_corridors() -> Map {
let mut map = Map { let mut map = Map {
tiles: vec![TileType::Wall; MAP_COUNT], tiles: vec![TileType::Wall; MAP_COUNT],
@ -111,37 +137,11 @@ impl Map {
map map
} }
fn is_exit_valid(&self, x: i32, y: i32) -> bool {
if x < 1 || x > self.width - 1 || y < 1 || y > self.height - 1 {
return false;
}
let idx = self.xy_idx(x, y);
!self.blocked[idx]
}
pub fn populate_blocked(&mut self) {
for (i, tile) in self.tiles.iter_mut().enumerate() {
self.blocked[i] = *tile == TileType::Wall;
}
}
pub fn clear_content_index(&mut self) {
for content in self.tile_content.iter_mut() {
content.clear();
}
}
}
impl Algorithm2D for Map {
fn dimensions(&self) -> rltk::Point {
Point::new(self.width, self.height)
}
} }
impl BaseMap for Map { impl BaseMap for Map {
fn is_opaque(&self, idx: usize) -> bool { fn is_opaque(&self, idx: usize) -> bool {
self.tiles[idx as usize] == TileType::Wall self.tiles[idx] == TileType::Wall
} }
fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 { fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
@ -187,6 +187,12 @@ impl BaseMap for Map {
} }
} }
impl Algorithm2D for Map {
fn dimensions(&self) -> rltk::Point {
Point::new(self.width, self.height)
}
}
pub fn draw_map(ecs: &World, ctx: &mut Rltk) { pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
@ -198,12 +204,12 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
let mut fg; let mut fg;
match tile { match tile {
TileType::Floor => { TileType::Floor => {
fg = RGB::from_f32(0.5, 0.5, 0.5); fg = RGB::from_f32(0.0, 0.5, 0.5);
glyph = rltk::to_cp437('.'); glyph = rltk::to_cp437('.');
} }
TileType::Wall => { TileType::Wall => {
fg = RGB::from_f32(0.0, 1.0, 0.0); fg = RGB::from_f32(0., 1.0, 0.);
glyph = rltk::to_cp437('#'); glyph = rltk::to_cp437('#');
} }
} }
@ -214,7 +220,7 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
} }
x += 1; x += 1;
if x > 79 { if x > MAP_WIDTH as i32 - 1 {
x = 0; x = 0;
y += 1; y += 1;
} }

View File

@ -1,8 +1,7 @@
use crate::gamelog::GameLog;
use rltk::console;
use specs::prelude::*; use specs::prelude::*;
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee}; use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
use crate::gamelog::GameLog;
pub struct MeleeCombatSystem {} pub struct MeleeCombatSystem {}

View File

@ -1,7 +1,11 @@
use rltk::{console, Point}; use rltk::Point;
use specs::prelude::*; use specs::prelude::*;
use crate::{components::{Monster, Position, Viewshed, WantsToMelee}, Confusion, map::Map, RunState}; use crate::{
components::{Monster, Position, Viewshed, WantsToMelee},
Confusion,
map::Map, RunState,
};
pub struct MonsterAI {} pub struct MonsterAI {}
@ -52,7 +56,7 @@ impl<'a> System<'a> for MonsterAI {
} }
if !can_act { if !can_act {
return; continue;
} }
let distance = let distance =

View File

@ -1,14 +1,14 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use rltk::{console, Point, Rltk, VirtualKeyCode}; use rltk::{Point, Rltk, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use crate::gamelog::GameLog;
use crate::{ use crate::{
components::{CombatStats, Player, Position, Viewshed, WantsToMelee}, components::{CombatStats, Player, Position, Viewshed, WantsToMelee},
map::{Map, TileType}, Item,
Item, RunState, State, WantsToPickupItem, map::Map, RunState, State, WantsToPickupItem,
}; };
use crate::gamelog::GameLog;
pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let mut positions = ecs.write_storage::<Position>(); let mut positions = ecs.write_storage::<Position>();
@ -22,6 +22,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
for (entity, _player, pos, viewshed) in for (entity, _player, pos, viewshed) in
(&entities, &mut players, &mut positions, &mut viewsheds).join() (&entities, &mut players, &mut positions, &mut viewsheds).join()
{ {
if pos.x + delta_x < 1 || pos.x + delta_x > map.width - 1 || pos.y + delta_y < 1 || pos.y + delta_y > map.height - 1 { return; }
let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y);
for potential_target in map.tile_content[destination_idx].iter() { for potential_target in map.tile_content[destination_idx].iter() {
@ -99,6 +100,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
VirtualKeyCode::G => get_item(&mut gs.ecs), VirtualKeyCode::G => get_item(&mut gs.ecs),
VirtualKeyCode::I => return RunState::ShowInventory, VirtualKeyCode::I => return RunState::ShowInventory,
VirtualKeyCode::D => return RunState::ShowDropItem, VirtualKeyCode::D => return RunState::ShowDropItem,
VirtualKeyCode::Escape => return RunState::SaveGame,
_ => return RunState::AwaitingInput, _ => return RunState::AwaitingInput,
}, },
} }

View File

@ -1,3 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub struct Rect { pub struct Rect {
pub x1: i32, pub x1: i32,
pub x2: i32, pub x2: i32,

123
src/save_load_system.rs Normal file
View File

@ -0,0 +1,123 @@
use std::fs;
use std::fs::File;
use std::path::Path;
use specs::error::NoError;
use specs::prelude::*;
use specs::saveload::{DeserializeComponents, MarkedBuilder, SerializeComponents, SimpleMarker, SimpleMarkerAllocator};
use super::components::*;
macro_rules! serialize_individually {
($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => {
$(
SerializeComponents::<NoError, SimpleMarker<SerializeMe>>::serialize(
&( $ecs.read_storage::<$type>(), ),
&$data.0,
&$data.1,
&mut $ser,
)
.unwrap();
)*
};
}
#[cfg(target_arch = "wasm32")]
pub fn save_game(_ecs: &mut World) {}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_game(ecs: &mut World) {
// Create helper
let mapcopy = ecs.get_mut::<super::map::Map>().unwrap().clone();
let savehelper = ecs
.create_entity()
.with(SerializationHelper { map: mapcopy })
.marked::<SimpleMarker<SerializeMe>>()
.build();
// Actually serialize
{
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
);
}
// Clean up
ecs.delete_entity(savehelper).expect("Crash on cleanup");
}
pub fn does_save_exist() -> bool {
Path::new("./savegame.json").exists()
}
macro_rules! deserialize_individually {
($ecs:expr, $de:expr, $data:expr, $( $type:ty),*) => {
$(
DeserializeComponents::<NoError, _>::deserialize(
&mut ( &mut $ecs.write_storage::<$type>(), ),
&$data.0, // entities
&mut $data.1, // marker
&mut $data.2, // allocater
&mut $de,
)
.unwrap();
)*
};
}
pub fn load_game(ecs: &mut World) {
{
// Delete everything
let mut to_delete = Vec::new();
for e in ecs.entities().join() {
to_delete.push(e);
}
for del in to_delete.iter() {
ecs.delete_entity(*del).expect("Deletion failed");
}
}
let data = fs::read_to_string("./savegame.json").unwrap();
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>>());
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
);
}
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() {
let mut worldmap = ecs.write_resource::<super::map::Map>();
*worldmap = h.map.clone();
worldmap.tile_content = vec![Vec::new(); super::map::MAP_COUNT];
deleteme = Some(e);
}
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");
}
pub fn delete_save() {
if Path::new("./savegame.json").exists() { std::fs::remove_file("./savegame.json").expect("Unable to delete file"); }
}

View File

@ -1,7 +1,8 @@
use rltk::{FontCharType, RandomNumberGenerator, RGB}; use rltk::{FontCharType, RandomNumberGenerator, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker};
use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER, Monster, Name, Player, Position, Potion, ProvidesHealing, Ranged, Renderable, Viewshed}; use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER, Monster, Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed};
use crate::rect::Rect; use crate::rect::Rect;
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
@ -31,6 +32,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
defense: 2, defense: 2,
power: 5, power: 5,
}) })
.marked::<SimpleMarker<SerializeMe>>()
.build() .build()
} }
@ -79,6 +81,7 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: FontCharType, na
defense: 1, defense: 1,
power: 4, power: 4,
}) })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -146,6 +149,7 @@ pub fn health_potion(ecs: &mut World, x: i32, y: i32) {
.with(Item {}) .with(Item {})
.with(Consumable {}) .with(Consumable {})
.with(ProvidesHealing { heal_amount: 8 }) .with(ProvidesHealing { heal_amount: 8 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -164,7 +168,8 @@ pub fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Item {}) .with(Item {})
.with(Consumable {}) .with(Consumable {})
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(InflictsDamage { damage: 8 }) .with(InflictsDamage { damage: 20 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -173,7 +178,7 @@ pub fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Position { x, y }) .with(Position { x, y })
.with(Renderable { .with(Renderable {
glyph: rltk::to_cp437(')'), glyph: rltk::to_cp437(')'),
fg: RGB::named(rltk::CYAN), fg: RGB::named(rltk::ORANGE),
bg: RGB::named(rltk::BLACK), bg: RGB::named(rltk::BLACK),
render_order: 2, render_order: 2,
}) })
@ -185,6 +190,7 @@ pub fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(InflictsDamage { damage: 20 }) .with(InflictsDamage { damage: 20 })
.with(AreaOfEffect { radius: 3 }) .with(AreaOfEffect { radius: 3 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -193,7 +199,7 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Position { x, y }) .with(Position { x, y })
.with(Renderable { .with(Renderable {
glyph: rltk::to_cp437(')'), glyph: rltk::to_cp437(')'),
fg: RGB::named(rltk::CYAN), fg: RGB::named(rltk::PINK),
bg: RGB::named(rltk::BLACK), bg: RGB::named(rltk::BLACK),
render_order: 2, render_order: 2,
}) })
@ -204,10 +210,10 @@ pub fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(Confusion { turns: 4 }) .with(Confusion { turns: 4 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
pub fn random_item(ecs: &mut World, x: i32, y: i32) { pub fn random_item(ecs: &mut World, x: i32, y: i32) {
let roll: i32; let roll: i32;
{ {

View File

@ -21,11 +21,10 @@ impl<'a> System<'a> for VisibilitySystem {
let (mut map, entities, mut viewshed, pos, player) = data; let (mut map, entities, mut viewshed, pos, player) = data;
for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() { for (ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() {
if !viewshed.dirty { if !viewshed.dirty {
return; continue;
} }
viewshed.dirty = false; viewshed.dirty = false;
viewshed.visible_tiles.clear();
viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map); viewshed.visible_tiles = field_of_view(Point::new(pos.x, pos.y), viewshed.range, &*map);
viewshed viewshed
.visible_tiles .visible_tiles