145cd71601170e2a0da42526e336934215cfb0e7
commit 145cd71601170e2a0da42526e336934215cfb0e7
Author: Simon Watson <spw01@protonmail.com>
Date: Mon Oct 3 21:32:42 2022 -0400

Up to "Delving Deeper". Diverging before cont.

diff --git a/Cargo.toml b/Cargo.toml
index f08d7d1..3d8cade 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
-rltk = { version = "0.8.0" }
-specs = "0.16.1"
-specs-derive = "0.4.1"
\ No newline at end of file
+rltk = { version = "0.8.0", features = ["serde"] }
+specs = { version = "0.17", features = ["serde"] }
+specs-derive = "0.4.1"
+serde = { version = "^1.0.44", features = ["derive"] }
+serde_json = "^1.0.44"
\ No newline at end of file
diff --git a/notes.md b/notes.md
index 7168548..cf593a5 100644
--- a/notes.md
+++ b/notes.md
@@ -17,3 +17,8 @@ that relates to that.
* * Sacrifice turns while in combat to gain faith (Pacifism)
* * Convert HP to Faith (mana) (Flagilation) OR
* * * No HP/Mana, shared resource pool that is used for HP and casting (Faith)
+
+# Reminders
+* When adding components, from the tutorial:
+Adding components has gained some steps: we have to register them in main, tag them for Serialize, Deserialize, and remember to add them to our component type lists in saveload_system.rs. That could be easier - but it's a very solid foundation.
+
diff --git a/src/components.rs b/src/components.rs
index 65e742e..d917b81 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -1,57 +1,68 @@
use specs::prelude::*;
use specs_derive::*;
use rltk::{RGB};
+use serde::{Serialize, Deserialize};
+use specs::saveload::{Marker, ConvertSaveload};
+use specs::error::NoError;

// COMPONENTS
-#[derive(Component, Debug)]
+// Special component that exists to help serialize the game data
+#[derive(Component, Serialize, Deserialize, Clone)]
+pub struct SerializationHelper {
+ pub map : super::map::Map
+}
+
+pub struct SerializeMe;
+
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct AreaOfEffect {
pub radius : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Ranged {
pub range : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct InflictsDamage {
pub damage : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {}

-#[derive(Component, Debug, Clone)]
+#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToDropItem {
pub item : Entity
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToUseItem {
pub item : Entity,
pub target: Option<rltk::Point>
}

-#[derive(Component, Debug, Clone)]
+#[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToPickupItem {
pub collected_by : Entity,
pub item : Entity
}

-#[derive(Component, Debug, Clone)]
+#[derive(Component, Debug, ConvertSaveload)]
pub struct InBackpack {
pub owner : Entity
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Item {}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct ProvidesHealing {
pub heal_amount : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct SufferDamage {
pub amount : Vec<i32>
}
@@ -66,12 +77,12 @@ impl SufferDamage {
}
}

-#[derive(Component, Debug, Clone)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct WantsToMelee {
pub target : Entity
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct CombatStats {
pub max_hp : i32,
pub hp : i32,
@@ -79,31 +90,31 @@ pub struct CombatStats {
pub power : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct BlocksTile {}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Name {
pub name : String
}

-#[derive(Component, Debug)]
+#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Monster {}

-#[derive(Component)]
+#[derive(Component, ConvertSaveload, Clone)]
pub struct Viewshed {
pub visible_tiles : Vec<rltk::Point>,
pub range : i32,
pub dirty: bool
}

-#[derive(Component)]
+#[derive(Component, ConvertSaveload, Clone)]
pub struct Position {
pub x: i32,
pub y: i32,
}

-#[derive(Component)]
+#[derive(Component, ConvertSaveload, Clone)]
pub struct Renderable {
pub glyph: rltk::FontCharType,
pub fg: RGB,
@@ -111,5 +122,5 @@ pub struct Renderable {
pub render_order : i32
}

-#[derive(Component, Debug)]
+#[derive(Component, Serialize, Deserialize, Clone)]
pub struct Player {}
\ No newline at end of file
diff --git a/src/gui.rs b/src/gui.rs
index 0f7e684..9a6921c 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -3,10 +3,92 @@ use specs::prelude::*;
use super::{CombatStats, Player, GameLog,
MAPHEIGHT, Map, Name,
Position, Point, InBackpack,
- State, Viewshed};
+ State, Viewshed, RunState};

const GUI_HEIGHT: usize = 50 - MAPHEIGHT - 1;

+#[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::saveload_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");
+ }
+
+ match ctx.key {
+ None => return MainMenuResult::NoSelection{ selected: selection },
+ Some(key) => {
+ match key {
+ VirtualKeyCode::Escape => { return MainMenuResult::NoSelection{ selected: MainMenuSelection::Quit } }
+ VirtualKeyCode::Up => {
+ let mut newselection;
+ match selection {
+ MainMenuSelection::NewGame => newselection = MainMenuSelection::Quit,
+ MainMenuSelection::LoadGame => newselection = MainMenuSelection::NewGame,
+ MainMenuSelection::Quit => newselection = MainMenuSelection::LoadGame
+ }
+ if newselection == MainMenuSelection::LoadGame && !save_exists {
+ newselection = MainMenuSelection::NewGame;
+ }
+ return MainMenuResult::NoSelection{ selected: newselection }
+ }
+ VirtualKeyCode::Down => {
+ let mut newselection;
+ match selection {
+ MainMenuSelection::NewGame => newselection = MainMenuSelection::LoadGame,
+ MainMenuSelection::LoadGame => newselection = MainMenuSelection::Quit,
+ MainMenuSelection::Quit => newselection = MainMenuSelection::NewGame
+ }
+ if newselection == MainMenuSelection::LoadGame && !save_exists {
+ newselection = MainMenuSelection::Quit;
+ }
+ return MainMenuResult::NoSelection{ selected: newselection }
+ }
+ VirtualKeyCode::Return => return MainMenuResult::Selected{ selected : selection },
+ _ => return MainMenuResult::NoSelection{ selected: selection }
+ }
+ }
+ }
+ }
+
+ MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame }
+}
pub fn ranged_target(gs : &mut State, ctx : &mut Rltk, range : i32) -> (ItemMenuResult, Option<Point>) {
let player_entity = gs.ecs.fetch::<Entity>();
let player_pos = gs.ecs.fetch::<Point>();
@@ -55,7 +137,7 @@ pub fn draw_ui(ecs: &World, ctx : &mut Rltk) {
let combat_stats = ecs.read_storage::<CombatStats>();
let players = ecs.read_storage::<Player>();
for (_player, stats) in (&players, &combat_stats).join() {
- let health = format!(" HP: {} / {} ", stats.hp, stats.max_hp);
+ let health = format!("Faith: {} / {} ", stats.hp, stats.max_hp);
ctx.print_color(12, 38, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), &health);

ctx.draw_bar_horizontal(28, 38, 51, stats.hp, stats.max_hp, RGB::named(rltk::RED), RGB::named(rltk::BLACK));
diff --git a/src/inventory_system.rs b/src/inventory_system.rs
index e6e6c47..cfff4db 100644
--- a/src/inventory_system.rs
+++ b/src/inventory_system.rs
@@ -4,7 +4,7 @@ use crate::{Consumable, ProvidesHealing, InflictsDamage};
use super::{WantsToPickupItem, Name, InBackpack,
Position, GameLog, WantsToUseItem,
CombatStats, Item, WantsToDropItem,
- Map, SufferDamage};
+ Map, SufferDamage, AreaOfEffect};

pub struct ItemCollectionSystem {}

@@ -49,7 +49,9 @@ impl<'a> System<'a> for ItemUseSystem {
ReadStorage<'a, Consumable>,
ReadStorage<'a, Item>,
ReadStorage<'a, InflictsDamage>,
- WriteStorage<'a, SufferDamage>
+ WriteStorage<'a, SufferDamage>,
+ ReadStorage<'a, AreaOfEffect>,
+
);

fn run(&mut self, data : Self::SystemData) {
@@ -62,21 +64,48 @@ impl<'a> System<'a> for ItemUseSystem {
healing,
mut combat_stats,
consumables,
- item,
+ _item,
inflict_damage,
- mut suffer_damage) = data;
-
- for (entity, useitem, stats) in (&entities, &wants_use, &mut combat_stats).join() {
- let mut used_item = true;
+ mut suffer_damage,
+ aoe) = data;
+
+ for (entity, useitem) in (&entities, &wants_use).join() {
+
+ // Targeting
+ let mut targets : Vec<Entity> = Vec::new();
+ match useitem.target {
+ None => { targets.push( *player_entity ); }
+ Some(target) => {
+ let area_effect = aoe.get(useitem.item);
+ match area_effect {
+ None => {
+ // Single target in tile
+ let idx = map.xy_idx(target.x, target.y);
+ for mob in map.tile_content[idx].iter() {
+ targets.push(*mob);
+ }
+ }
+ Some(area_effect) => {
+ // AoE
+ let mut blast_tiles = rltk::field_of_view(target, area_effect.radius, &*map);
+ blast_tiles.retain(|p| p.x > 0 && p.x < map.width-1 && p.y > 0 && p.y < map.height-1 );
+ for tile_idx in blast_tiles.iter() {
+ let idx = map.xy_idx(tile_idx.x, tile_idx.y);
+ for mob in map.tile_content[idx].iter() {
+ targets.push(*mob);
+ }
+ }
+ }
+ }
+ }
+ }
+
// If it inflicts damage, apply it to the target cell
let item_damages = inflict_damage.get(useitem.item);
match item_damages {
None => {}
Some(damage) => {
- let target_point = useitem.target.unwrap();
- let idx = map.xy_idx(target_point.x, target_point.y);
- used_item = false;
- for mob in map.tile_content[idx].iter() {
+ for mob in targets.iter() {
SufferDamage::new_damage(&mut suffer_damage, *mob, damage.damage);
if entity == *player_entity {
let mob_name = names.get(*mob).unwrap();
@@ -84,22 +113,27 @@ impl<'a> System<'a> for ItemUseSystem {
gamelog.entries.push(format!("You use {} on {}, inflicting {} damage.", item_name.name, mob_name.name, damage.damage));
}

- used_item = true;
}
}
}

+ // If it heals, apply the healing
let item_heals = healing.get(useitem.item);
match item_heals {
None => {}
Some(healer) => {
- stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
- if entity == *player_entity {
- gamelog.entries.push(format!("You drink the {}, healing {} hp.", names.get(useitem.item).unwrap().name, healer.heal_amount));
+ for target in targets.iter() {
+ let stats = combat_stats.get_mut(*target);
+ if let Some(stats) = stats {
+ stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
+ if entity == *player_entity {
+ gamelog.entries.push(format!("You use the {}, healing {} hp.", names.get(useitem.item).unwrap().name, healer.heal_amount));
+ }
+ }
}
}
}
-
+
let consumable = consumables.get(useitem.item);
match consumable {
None => {}
diff --git a/src/main.rs b/src/main.rs
index b2d8940..425ddd2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,8 @@
use rltk::{GameState, Rltk, Point};
use specs::prelude::*;
+use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
+
+extern crate serde;

mod components;
pub use components::*;
@@ -25,6 +28,7 @@ use gamelog::GameLog;
mod spawner;
mod inventory_system;
use inventory_system::*;
+mod saveload_system;

// ***** //
// STATE //
@@ -39,7 +43,12 @@ pub enum RunState {
ShowDropItem,
ShowTargeting {
range : i32,
- item : Entity },
+ item : Entity
+ },
+ MainMenu {
+ menu_selection: gui::MainMenuSelection
+ },
+ SaveGame,
}

pub struct State {
@@ -70,13 +79,36 @@ impl State {

impl GameState for State {
fn tick(&mut self, ctx : &mut Rltk) {
- ctx.cls();
let mut newrunstate;
{
let runstate = self.ecs.fetch::<RunState>();
newrunstate = *runstate;
}

+ ctx.cls();
+
+ match newrunstate {
+ RunState::MainMenu{..} => {}
+ _ => {
+ draw_map(&self.ecs, ctx);
+
+ {
+ let positions = self.ecs.read_storage::<Position>();
+ let renderables = self.ecs.read_storage::<Renderable>();
+ let map = self.ecs.fetch::<Map>();
+
+ let mut data = (&positions, &renderables).join().collect::<Vec<_>>();
+ data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order) );
+ for (pos, render) in data.iter() {
+ let idx = map.xy_idx(pos.x, pos.y);
+ if map.visible_tiles[idx] { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) }
+ }
+
+ gui::draw_ui(&self.ecs, ctx);
+ }
+ }
+ }
+
match newrunstate {
RunState::PreRun => {
self.run_systems();
@@ -140,28 +172,36 @@ impl GameState for State {
}
}
}
+ RunState::MainMenu{ .. } => {
+ let result = gui::main_menu(self, ctx);
+ match result {
+ gui::MainMenuResult::NoSelection{ selected } => newrunstate = RunState::MainMenu{ menu_selection: selected },
+ gui::MainMenuResult::Selected{ selected } => {
+ match selected {
+ gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun,
+ gui::MainMenuSelection::LoadGame => {
+ saveload_system::load_game(&mut self.ecs);
+ newrunstate = RunState::AwaitingInput;
+ saveload_system::delete_save();
+ }
+ gui::MainMenuSelection::Quit => { ::std::process::exit(0); }
+ }
+ }
+ }
+ }
+ RunState::SaveGame => {
+ saveload_system::save_game(&mut self.ecs);
+ newrunstate = RunState::MainMenu{ menu_selection : gui::MainMenuSelection::LoadGame };
+ }
}

{
let mut runwriter = self.ecs.write_resource::<RunState>();
*runwriter = newrunstate;
}
+
damage_system::delete_the_dead(&mut self.ecs);

- draw_map(&self.ecs, ctx);
-
- let positions = self.ecs.read_storage::<Position>();
- let renderables = self.ecs.read_storage::<Renderable>();
- let map = self.ecs.fetch::<Map>();
-
- let mut data = (&positions, &renderables).join().collect::<Vec<_>>();
- data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order) );
- for (pos, render) in data.iter() {
- let idx = map.xy_idx(pos.x, pos.y);
- if map.visible_tiles[idx] { ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) }
- }
-
- gui::draw_ui(&self.ecs, ctx);
}
}

@@ -199,7 +239,11 @@ fn main() -> rltk::BError {
gs.ecs.register::<Consumable>();
gs.ecs.register::<Ranged>();
gs.ecs.register::<InflictsDamage>();
+ gs.ecs.register::<AreaOfEffect>();
+ 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();
@@ -217,7 +261,7 @@ fn main() -> rltk::BError {

gs.ecs.insert(map);
gs.ecs.insert(Point::new(player_x, player_y));
- gs.ecs.insert(RunState::PreRun);
+ gs.ecs.insert(RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame });

gs.ecs.insert(gamelog::GameLog{ entries : vec!["Saint Antony casts out...".to_string()] });

diff --git a/src/map.rs b/src/map.rs
index 917d102..26f6ae5 100644
--- a/src/map.rs
+++ b/src/map.rs
@@ -2,17 +2,19 @@ use rltk::{RandomNumberGenerator, RGB, Rltk, Algorithm2D, Point, BaseMap};
use super::{Rect};
use std::cmp::{max, min};
use specs::prelude::*;
+use serde::{Serialize, Deserialize};

pub const MAPWIDTH : usize = 80;
pub const MAPHEIGHT : usize = 38;
pub const MAPCOUNT : usize = MAPHEIGHT * MAPWIDTH;

-#[derive(PartialEq, Copy, Clone)]
+#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum TileType {
Wall,
Floor
}

+#[derive(Default, Serialize, Deserialize, Clone)]
pub struct Map {
pub tiles: Vec<TileType>,
pub rooms: Vec<Rect>,
@@ -21,6 +23,8 @@ pub struct Map {
pub revealed_tiles : Vec<bool>,
pub visible_tiles: Vec<bool>,
pub blocked : Vec<bool>,
+ #[serde(skip_serializing)]
+ #[serde(skip_deserializing)]
pub tile_content : Vec<Vec<Entity>>
}
impl Map {
diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs
index 10f0d72..7d8a090 100644
--- a/src/melee_combat_system.rs
+++ b/src/melee_combat_system.rs
@@ -26,7 +26,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
if damage == 0 {
log.entries.push(format!("{} is unable to hurt {}", &name.name, &target_name.name));
} else {
- log.entries.push(format!("{} hits {}, for {} hp.", &name.name, &target_name.name, damage));
+ log.entries.push(format!("{} hits {}, for {} damage.", &name.name, &target_name.name, damage));
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage);
}
}
diff --git a/src/player.rs b/src/player.rs
index 493acd6..6f352e5 100644
--- a/src/player.rs
+++ b/src/player.rs
@@ -109,6 +109,9 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
VirtualKeyCode::G => get_item(&mut gs.ecs),
VirtualKeyCode::I => return RunState::ShowInventory,
VirtualKeyCode::X => return RunState::ShowDropItem,
+
+ // Save and Quit
+ VirtualKeyCode::Escape => return RunState::SaveGame,

_ => { return RunState::AwaitingInput }
},
diff --git a/src/rect.rs b/src/rect.rs
index 5730c2a..d37a49d 100644
--- a/src/rect.rs
+++ b/src/rect.rs
@@ -1,3 +1,6 @@
+use serde::{Serialize, Deserialize};
+
+#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub struct Rect {
pub x1 : i32,
pub x2 : i32,
diff --git a/src/saveload_system.rs b/src/saveload_system.rs
new file mode 100644
index 0000000..5394dbc
--- /dev/null
+++ b/src/saveload_system.rs
@@ -0,0 +1,124 @@
+use specs::prelude::*;
+use specs::saveload::{SimpleMarker, SimpleMarkerAllocator, SerializeComponents, DeserializeComponents, MarkedBuilder};
+use std::convert::Infallible;
+use super::components::*;
+use std::fs::File;
+use std::path::Path;
+use std::fs;
+
+macro_rules! serialize_individually {
+ ($ecs:expr, $ser:expr, $data:expr, $( $type:ty),*) => {
+ $(
+ SerializeComponents::<Infallible, SimpleMarker<SerializeMe>>::serialize(
+ &( $ecs.read_storage::<$type>(), ),
+ &$data.0,
+ &$data.1,
+ &mut $ser,
+ )
+ .unwrap();
+ )*
+ };
+}
+
+macro_rules! deserialize_individually {
+ ($ecs:expr, $de:expr, $data:expr, $( $type:ty),*) => {
+ $(
+ DeserializeComponents::<Infallible, _>::deserialize(
+ &mut ( &mut $ecs.write_storage::<$type>(), ),
+ &mut $data.0, // entities
+ &mut $data.1, // marker
+ &mut $data.2, // allocater
+ &mut $de,
+ )
+ .unwrap();
+ )*
+ };
+}
+
+pub fn does_save_exist() -> bool {
+ Path::new("./savegame.json").exists()
+}
+
+#[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, ProvidesHealing, InBackpack, WantsToPickupItem, WantsToUseItem,
+ WantsToDropItem, SerializationHelper
+ );
+ }
+
+ // Clean up
+ ecs.delete_entity(savehelper).expect("Crash on cleanup");
+}
+
+#[cfg(target_arch = "wasm32")]
+pub fn save_game(_ecs : &mut World) {
+}
+
+pub fn delete_save() {
+ if Path::new("./savegame.json").exists() {
+ std::fs::remove_file("./savegame.json").expect("Unable to delete file");
+ }
+}
+
+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, 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::MAPCOUNT];
+ 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");
+}
\ No newline at end of file
diff --git a/src/spawner.rs b/src/spawner.rs
index 6209566..09a955d 100644
--- a/src/spawner.rs
+++ b/src/spawner.rs
@@ -4,7 +4,9 @@ use super::{CombatStats, Player, Renderable,
Name, Position, Viewshed,
Monster, BlocksTile, Rect,
MAPWIDTH, Item, ProvidesHealing,
- Consumable, InflictsDamage, Ranged};
+ Consumable, InflictsDamage, Ranged,
+ AreaOfEffect,SerializeMe};
+use specs::saveload::{MarkedBuilder, SimpleMarker};

const MAX_MONSTERS : i32 = 4;
const MAX_ITEMS : i32 = 2; // PER MAP
@@ -13,14 +15,34 @@ 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, 2);
+ roll = rng.roll_dice(1, 3);
}
match roll {
1 => { health_potion(ecs, x, y) }
+ 2 => { fireball_scroll(ecs, x, y) }
_ => { magic_missile_scroll(ecs, x, y) }
}
}

+fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
+ ecs.create_entity()
+ .with(Position{ x, y })
+ .with(Renderable{
+ glyph: rltk::to_cp437(')'),
+ fg: RGB::named(rltk::ORANGE),
+ bg: RGB::named(rltk::BLACK),
+ render_order: 2
+ })
+ .with(Name{ name : "Fireball Scroll".to_string() })
+ .with(Item{})
+ .with(Consumable{})
+ .with(Ranged{ range: 6 })
+ .with(InflictsDamage{ damage: 20 })
+ .with(AreaOfEffect{ radius: 3 })
+ .marked::<SimpleMarker<SerializeMe>>()
+ .build();
+}
+
fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
.with(Position{ x, y })
@@ -35,6 +57,7 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Consumable{})
.with(Ranged{ range: 6 })
.with(InflictsDamage{ damage: 8 })
+ .marked::<SimpleMarker<SerializeMe>>()
.build();
}

@@ -51,6 +74,7 @@ fn health_potion(ecs: &mut World, x: i32, y: i32) {
.with(Item{})
.with(Consumable{})
.with(ProvidesHealing{ heal_amount: 8 })
+ .marked::<SimpleMarker<SerializeMe>>()
.build();
}

@@ -123,6 +147,7 @@ pub fn player(ecs : &mut World, player_x : i32, player_y : i32) -> Entity {
.with(Viewshed{ visible_tiles : Vec::new(), range: 8, dirty: true })
.with(Name{name: "Player".to_string() })
.with(CombatStats{ max_hp: 30, hp: 30, defense: 2, power: 5 })
+ .marked::<SimpleMarker<SerializeMe>>()
.build()
}

@@ -156,5 +181,6 @@ fn monster<S : ToString>(ecs: &mut World, x: i32, y: i32, glyph : rltk::FontChar
.with(Name{ name : name.to_string() })
.with(BlocksTile{})
.with(CombatStats{ max_hp: 16, hp: 16, defense: 1, power: 4 })
+ .marked::<SimpleMarker<SerializeMe>>()
.build();
}
\ No newline at end of file