saveload system

localstorage isn't supported by wasm, so playing online will probably just not have save games for a while
This commit is contained in:
Llywelwyn 2023-07-09 12:09:30 +01:00
parent dd91a8cca7
commit 51060f1a85
11 changed files with 290 additions and 63 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
.rustfmt.toml .rustfmt.toml
.vscode/* .vscode/*
savegame.json

6
Cargo.lock generated
View file

@ -142,6 +142,7 @@ dependencies = [
"byteorder", "byteorder",
"lazy_static", "lazy_static",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"serde",
] ]
[[package]] [[package]]
@ -169,6 +170,7 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f31b525fcd65027885f3a1e3a250a5dd397d70de4a6a5a125f03e0bef951499" checksum = "0f31b525fcd65027885f3a1e3a250a5dd397d70de4a6a5a125f03e0bef951499"
dependencies = [ dependencies = [
"serde",
"ultraviolet 0.9.1", "ultraviolet 0.9.1",
] ]
@ -262,6 +264,7 @@ dependencies = [
"rand", "rand",
"rand_xorshift", "rand_xorshift",
"regex", "regex",
"serde",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2275,6 +2278,8 @@ dependencies = [
"bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)", "bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)",
"criterion", "criterion",
"rltk", "rltk",
"serde",
"serde_json",
"specs", "specs",
"specs-derive", "specs-derive",
] ]
@ -2545,6 +2550,7 @@ dependencies = [
"hibitset", "hibitset",
"log", "log",
"rayon", "rayon",
"serde",
"shred", "shred",
"shrev", "shrev",
"tuple_utils", "tuple_utils",

View file

@ -6,10 +6,12 @@ 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.7" } rltk = { version = "^0.8.7", features = ["serde"] }
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] } bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] }
specs = "0.16.1" specs = { version = "0.16.1", features = ["serde"] }
specs-derive = "0.4.1" specs-derive = "0.4.1"
serde = { version = "1.0.93", features = ["derive"]}
serde_json = "1.0.39"
[dev-dependencies] [dev-dependencies]
criterion = { version = "^0.5" } criterion = { version = "^0.5" }

View file

@ -1,14 +1,26 @@
use rltk::RGB; use rltk::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)] // Serialization helper code. We need to implement ConvertSaveload for each type that contains an
// Entity.
pub struct SerializeMe;
// Special component that exists to help serialize the game data
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SerializationHelper {
pub map: super::map::Map,
}
#[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 +28,28 @@ pub struct Renderable {
pub render_order: i32, pub render_order: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {} pub struct Player {}
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Monster {} pub struct Monster {}
#[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, 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 +57,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>,
} }
@ -66,63 +78,63 @@ 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 ProvidesHealing { pub struct ProvidesHealing {
pub amount: i32, pub amount: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct InflictsDamage { pub struct InflictsDamage {
pub amount: i32, pub amount: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Ranged { pub struct Ranged {
pub range: i32, pub range: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct AOE { pub struct AOE {
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,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, ConvertSaveload)]
pub struct InBackpack { pub struct InBackpack {
pub owner: Entity, pub owner: Entity,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, Debug, 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)]
pub struct WantsToDropItem { pub struct WantsToDropItem {
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToUseItem { pub struct WantsToUseItem {
pub item: Entity, pub item: Entity,
pub target: Option<rltk::Point>, pub target: Option<rltk::Point>,
} }
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Consumable {} pub struct Consumable {}
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Destructible {} pub struct Destructible {}
#[derive(Component, Clone)] #[derive(Component, Clone, ConvertSaveload)]
pub struct ParticleLifetime { pub struct ParticleLifetime {
pub lifetime_ms: f32, pub lifetime_ms: f32,
} }

View file

@ -273,34 +273,40 @@ pub enum MainMenuResult {
} }
pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { 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>(); let runstate = gs.ecs.fetch::<RunState>();
let assets = gs.ecs.fetch::<RexAssets>(); let assets = gs.ecs.fetch::<RexAssets>();
ctx.render_xp_sprite(&assets.menu, 0, 0); ctx.render_xp_sprite(&assets.menu, 0, 0);
ctx.print_color(38, 21, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "RUST-RL"); ctx.print_color(40, 21, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "RUST-RL");
if let RunState::MainMenu { menu_selection: selection } = *runstate { if let RunState::MainMenu { menu_selection: selection } = *runstate {
let mut y = 24;
if selection == MainMenuSelection::NewGame { if selection == MainMenuSelection::NewGame {
ctx.print_color(34, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "["); ctx.print_color(37, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
ctx.print_color(36, 24, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "new game"); ctx.print_color(39, 24, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "new game");
ctx.print_color(45, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]"); ctx.print_color(48, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else { } else {
ctx.print_color(36, 24, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "new game"); ctx.print_color(39, 24, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "new game");
} }
if selection == MainMenuSelection::LoadGame { y += 2;
ctx.print_color(38, 26, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "["); if save_exists {
ctx.print_color(40, 26, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "load game"); if selection == MainMenuSelection::LoadGame {
ctx.print_color(50, 26, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]"); ctx.print_color(36, y, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
} else { ctx.print_color(38, y, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "load game");
ctx.print_color(40, 26, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "load game"); ctx.print_color(48, y, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else {
ctx.print_color(38, y, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "load game");
}
y += 2;
} }
if selection == MainMenuSelection::Quit { if selection == MainMenuSelection::Quit {
ctx.print_color(34, 28, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "["); ctx.print_color(37, y, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
ctx.print_color(36, 28, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "goodbye!"); ctx.print_color(39, y, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "goodbye!");
ctx.print_color(45, 28, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]"); ctx.print_color(48, y, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else { } else {
ctx.print_color(36, 28, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "quit"); ctx.print_color(43, y, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "quit");
} }
match ctx.key { match ctx.key {
@ -312,21 +318,27 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
VirtualKeyCode::N => return MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame }, VirtualKeyCode::N => return MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame },
VirtualKeyCode::L => return MainMenuResult::NoSelection { selected: MainMenuSelection::LoadGame }, VirtualKeyCode::L => return MainMenuResult::NoSelection { selected: MainMenuSelection::LoadGame },
VirtualKeyCode::Up => { VirtualKeyCode::Up => {
let new_selection; let mut new_selection;
match selection { match selection {
MainMenuSelection::NewGame => new_selection = MainMenuSelection::Quit, MainMenuSelection::NewGame => new_selection = MainMenuSelection::Quit,
MainMenuSelection::LoadGame => new_selection = MainMenuSelection::NewGame, MainMenuSelection::LoadGame => new_selection = MainMenuSelection::NewGame,
MainMenuSelection::Quit => new_selection = MainMenuSelection::LoadGame, MainMenuSelection::Quit => new_selection = MainMenuSelection::LoadGame,
} }
if new_selection == MainMenuSelection::LoadGame && !save_exists {
new_selection = MainMenuSelection::NewGame;
}
return MainMenuResult::NoSelection { selected: new_selection }; return MainMenuResult::NoSelection { selected: new_selection };
} }
VirtualKeyCode::Down => { VirtualKeyCode::Down => {
let new_selection; let mut new_selection;
match selection { match selection {
MainMenuSelection::NewGame => new_selection = MainMenuSelection::LoadGame, MainMenuSelection::NewGame => new_selection = MainMenuSelection::LoadGame,
MainMenuSelection::LoadGame => new_selection = MainMenuSelection::Quit, MainMenuSelection::LoadGame => new_selection = MainMenuSelection::Quit,
MainMenuSelection::Quit => new_selection = MainMenuSelection::NewGame, MainMenuSelection::Quit => new_selection = MainMenuSelection::NewGame,
} }
if new_selection == MainMenuSelection::LoadGame && !save_exists {
new_selection = MainMenuSelection::Quit;
}
return MainMenuResult::NoSelection { selected: new_selection }; return MainMenuResult::NoSelection { selected: new_selection };
} }
VirtualKeyCode::Return => return MainMenuResult::Selected { selected: selection }, VirtualKeyCode::Return => return MainMenuResult::Selected { selected: selection },

View file

@ -1,6 +1,8 @@
use rltk::{GameState, Point, Rltk, RGB}; use rltk::{GameState, Point, Rltk, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
use std::ops::Add; use std::ops::Add;
extern crate serde;
mod components; mod components;
pub use components::*; pub use components::*;
@ -12,6 +14,7 @@ mod rect;
pub use rect::Rect; pub use rect::Rect;
mod gamelog; mod gamelog;
mod gui; mod gui;
mod saveload_system;
mod spawner; mod spawner;
mod visibility_system; mod visibility_system;
use visibility_system::VisibilitySystem; use visibility_system::VisibilitySystem;
@ -44,6 +47,7 @@ pub enum RunState {
ShowDropItem, ShowDropItem,
ShowTargeting { range: i32, item: Entity, aoe: i32 }, ShowTargeting { range: i32, item: Entity, aoe: i32 },
MainMenu { menu_selection: gui::MainMenuSelection }, MainMenu { menu_selection: gui::MainMenuSelection },
SaveGame,
} }
pub struct State { pub struct State {
@ -200,22 +204,23 @@ impl GameState for State {
gui::MainMenuResult::NoSelection { selected } => { gui::MainMenuResult::NoSelection { selected } => {
new_runstate = RunState::MainMenu { menu_selection: selected } new_runstate = RunState::MainMenu { menu_selection: selected }
} }
gui::MainMenuResult::Selected { selected } => { gui::MainMenuResult::Selected { selected } => match selected {
match selected { gui::MainMenuSelection::NewGame => new_runstate = RunState::PreRun,
gui::MainMenuSelection::NewGame => new_runstate = RunState::PreRun, gui::MainMenuSelection::LoadGame => {
gui::MainMenuSelection::LoadGame => { saveload_system::load_game(&mut self.ecs);
new_runstate = RunState::PreRun; new_runstate = RunState::AwaitingInput;
//saveload_system::load_game(&mut self.ecs); saveload_system::delete_save();
//rew_runstate = RunState::AwaitingInput;
//saveload_system::delete_save();
}
gui::MainMenuSelection::Quit => {
::std::process::exit(0);
}
} }
} gui::MainMenuSelection::Quit => {
::std::process::exit(0);
}
},
} }
} }
RunState::SaveGame => {
saveload_system::save_game(&mut self.ecs);
new_runstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::LoadGame };
}
} }
{ {
@ -268,6 +273,9 @@ fn main() -> rltk::BError {
gs.ecs.register::<Consumable>(); gs.ecs.register::<Consumable>();
gs.ecs.register::<Destructible>(); gs.ecs.register::<Destructible>();
gs.ecs.register::<ParticleLifetime>(); gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
let map = Map::new_map_rooms_and_corridors(); let map = Map::new_map_rooms_and_corridors();
let (player_x, player_y) = map.rooms[0].centre(); let (player_x, player_y) = map.rooms[0].centre();

View file

@ -1,11 +1,12 @@
use super::Rect; use super::Rect;
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB};
use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::collections::HashSet; use std::collections::HashSet;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum TileType { pub enum TileType {
Wall, Wall,
Floor, Floor,
@ -14,9 +15,9 @@ pub enum TileType {
pub const MAPWIDTH: usize = 80; pub const MAPWIDTH: usize = 80;
pub const MAPHEIGHT: usize = 43; pub const MAPHEIGHT: usize = 43;
const MAX_OFFSET: u8 = 32; const MAX_OFFSET: u8 = 32;
const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH; pub const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH;
#[derive(Default)] #[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>,
@ -28,8 +29,11 @@ pub struct Map {
pub green_offset: Vec<u8>, pub green_offset: Vec<u8>,
pub blue_offset: Vec<u8>, pub blue_offset: Vec<u8>,
pub blocked: Vec<bool>, pub blocked: Vec<bool>,
pub tile_content: Vec<Vec<Entity>>,
pub bloodstains: HashSet<usize>, pub bloodstains: HashSet<usize>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub tile_content: Vec<Vec<Entity>>,
} }
impl Map { impl Map {
@ -98,8 +102,8 @@ impl Map {
green_offset: vec![0; MAPCOUNT], green_offset: vec![0; MAPCOUNT],
blue_offset: vec![0; MAPCOUNT], blue_offset: vec![0; MAPCOUNT],
blocked: vec![false; MAPCOUNT], blocked: vec![false; MAPCOUNT],
tile_content: vec![Vec::new(); MAPCOUNT],
bloodstains: HashSet::new(), bloodstains: HashSet::new(),
tile_content: vec![Vec::new(); MAPCOUNT],
}; };
const MAX_ROOMS: i32 = 30; const MAX_ROOMS: i32 = 30;

View file

@ -98,7 +98,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::MainMenu { menu_selection: gui::MainMenuSelection::NewGame }, 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,

170
src/saveload_system.rs Normal file
View file

@ -0,0 +1,170 @@
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;
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,
Destructible,
Ranged,
InflictsDamage,
AOE,
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,
Destructible,
Ranged,
InflictsDamage,
AOE,
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::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");
}
pub fn delete_save() {
if Path::new("./savegame.json").exists() {
std::fs::remove_file("./savegame.json").expect("Unable to delete file");
}
}

View file

@ -1,9 +1,10 @@
use super::{ use super::{
BlocksTile, CombatStats, Confusion, Consumable, Destructible, InflictsDamage, Item, Monster, Name, Player, BlocksTile, CombatStats, Confusion, Consumable, Destructible, InflictsDamage, Item, Monster, Name, Player,
Position, ProvidesHealing, Ranged, Rect, Renderable, Viewshed, AOE, MAPWIDTH, Position, ProvidesHealing, Ranged, Rect, Renderable, SerializeMe, Viewshed, AOE, MAPWIDTH,
}; };
use rltk::{RandomNumberGenerator, RGB}; use rltk::{RandomNumberGenerator, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker};
/// Spawns the player and returns his/her entity object. /// Spawns the player and returns his/her entity object.
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 {
@ -19,6 +20,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
.with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true }) .with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true })
.with(Name { name: "hero (you)".to_string() }) .with(Name { name: "hero (you)".to_string() })
.with(CombatStats { max_hp: 30, hp: 30, defence: 2, power: 5 }) .with(CombatStats { max_hp: 30, hp: 30, defence: 2, power: 5 })
.marked::<SimpleMarker<SerializeMe>>()
.build() .build()
} }
@ -64,6 +66,7 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy
.with(Name { name: name.to_string() }) .with(Name { name: name.to_string() })
.with(BlocksTile {}) .with(BlocksTile {})
.with(CombatStats { max_hp: 16, hp: 16, defence: 1, power: 4 }) .with(CombatStats { max_hp: 16, hp: 16, defence: 1, power: 4 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -143,6 +146,7 @@ fn health_potion(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Destructible {}) .with(Destructible {})
.with(ProvidesHealing { amount: 12 }) .with(ProvidesHealing { amount: 12 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -160,6 +164,7 @@ fn weak_health_potion(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Destructible {}) .with(Destructible {})
.with(ProvidesHealing { amount: 6 }) .with(ProvidesHealing { amount: 6 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -177,6 +182,7 @@ fn poison_potion(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Destructible {}) .with(Destructible {})
.with(ProvidesHealing { amount: -12 }) .with(ProvidesHealing { amount: -12 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -197,6 +203,7 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Destructible {}) .with(Destructible {})
.with(Ranged { range: 12 }) // Long range - as far as default vision range .with(Ranged { range: 12 }) // Long range - as far as default vision range
.with(InflictsDamage { amount: 10 }) // Low~ damage .with(InflictsDamage { amount: 10 }) // Low~ damage
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -216,6 +223,7 @@ fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Ranged { range: 10 }) .with(Ranged { range: 10 })
.with(InflictsDamage { amount: 20 }) .with(InflictsDamage { amount: 20 })
.with(AOE { radius: 3 }) .with(AOE { radius: 3 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -234,5 +242,6 @@ fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Destructible {}) .with(Destructible {})
.with(Ranged { range: 10 }) .with(Ranged { range: 10 })
.with(Confusion { turns: 4 }) .with(Confusion { turns: 4 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }