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:
parent
dd91a8cca7
commit
51060f1a85
11 changed files with 290 additions and 63 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
.rustfmt.toml
|
.rustfmt.toml
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
savegame.json
|
||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/gui.rs
46
src/gui.rs
|
|
@ -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 },
|
||||||
|
|
|
||||||
34
src/main.rs
34
src/main.rs
|
|
@ -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();
|
||||||
|
|
|
||||||
14
src/map.rs
14
src/map.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
170
src/saveload_system.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue