Compare commits

...

2 Commits

Author SHA1 Message Date
01f10c5153 Added save/load 2022-12-04 12:33:50 +11:00
a2171b852b Ignore savegames in repo 2022-12-04 12:33:43 +11:00
6 changed files with 184 additions and 38 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.vscode .vscode
wasm wasm
wasm.tgz wasm.tgz
savegame.json

View File

@ -1,15 +1,17 @@
use rltk::RGB; use rltk::RGB;
use specs::prelude::*; use specs::prelude::*;
use specs_derive::*; use specs_derive::*;
use specs::saveload::{Marker, ConvertSaveload};
use serde::{Serialize, Deserialize};
use specs::error::NoError;
#[derive(Component, ConvertSaveload, Clone)]
#[derive(Component)]
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,
@ -17,30 +19,30 @@ pub struct Renderable {
pub render_order: i32, pub render_order: i32,
} }
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Debug, 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, Debug)] #[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Monster {} pub struct Monster {}
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Name { pub struct Name {
pub name: String, pub name: String,
} }
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct BlocksTile {} pub struct BlocksTile {}
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct CombatStats { pub struct CombatStats {
pub max_hp: i32, pub max_hp: i32,
pub hp: i32, pub hp: i32,
@ -48,12 +50,12 @@ pub struct CombatStats {
pub power: i32, pub power: i32,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct WantsToMelee { pub struct WantsToMelee {
pub target: Entity, pub target: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct SufferDamage { pub struct SufferDamage {
pub amount: Vec<i32>, pub amount: Vec<i32>,
} }
@ -71,56 +73,61 @@ impl SufferDamage {
} }
} }
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Item {} pub struct Item {}
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct ProvidesHealing { pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: i32,
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Debug)]
pub struct InBackpack { pub struct InBackpack {
pub owner: Entity, pub owner: Entity,
} }
#[derive(Component, Debug, Clone)] #[derive(Component, ConvertSaveload, Debug, Clone)]
pub struct WantsToPickupItem { pub struct WantsToPickupItem {
pub collected_by: Entity, pub collected_by: Entity,
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Debug)]
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, Clone)] #[derive(Component, ConvertSaveload, Debug, Clone)]
pub struct WantsToDropItem { pub struct WantsToDropItem {
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Clone, Debug)]
pub struct Consumable {} pub struct Consumable {}
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Ranged { pub struct Ranged {
pub range : i32 pub range : i32
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct InflictsDamage { pub struct InflictsDamage {
pub damage : i32 pub damage : i32
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct AreaOfEffect { pub struct AreaOfEffect {
pub radius : i32 pub radius : i32
} }
#[derive(Component, Debug)] #[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Confusion { pub struct Confusion {
pub turns : i32 pub turns : i32
} }
pub struct SerializeMe; pub struct SerializeMe;
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct SerializationHelper {
pub map : super::map::Map
}

View File

@ -273,6 +273,7 @@ pub fn ranged_target(gs: &mut State, ctx : &mut Rltk, range: i32) -> (ItemMenuRe
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>();
ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Rust Roguelike Tutorial"); ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Rust Roguelike Tutorial");
@ -284,12 +285,15 @@ pub fn main_menu(gs: &mut State, ctx : &mut Rltk) -> MainMenuResult {
ctx.print_color_centered(24, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Start a new game"); ctx.print_color_centered(24, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Start a new game");
} }
if selection == MainMenuSelection::LoadGame { if save_exists {
ctx.print_color_centered(25, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Load a save"); if selection == MainMenuSelection::LoadGame {
} else { ctx.print_color_centered(25, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Load a save");
ctx.print_color_centered(25, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Load a save"); } else {
ctx.print_color_centered(25, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Load a save");
}
} }
if selection == MainMenuSelection::Quit { if selection == MainMenuSelection::Quit {
ctx.print_color_centered(26, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Quit the game"); ctx.print_color_centered(26, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Quit the game");
} else { } else {
@ -302,21 +306,27 @@ pub fn main_menu(gs: &mut State, ctx : &mut Rltk) -> MainMenuResult {
match key { match key {
VirtualKeyCode::Escape => {return MainMenuResult::NoSelection { selected: MainMenuSelection::Quit }}, VirtualKeyCode::Escape => {return MainMenuResult::NoSelection { selected: MainMenuSelection::Quit }},
VirtualKeyCode::Up => { VirtualKeyCode::Up => {
let newselection; let mut newselection;
match selection { match selection {
MainMenuSelection::NewGame => newselection = MainMenuSelection::Quit, MainMenuSelection::NewGame => newselection = MainMenuSelection::Quit,
MainMenuSelection::LoadGame => newselection = MainMenuSelection::NewGame, MainMenuSelection::LoadGame => newselection = MainMenuSelection::NewGame,
MainMenuSelection::Quit => newselection = MainMenuSelection::LoadGame MainMenuSelection::Quit => newselection = MainMenuSelection::LoadGame
} }
if newselection == MainMenuSelection::LoadGame && !save_exists {
newselection = MainMenuSelection::NewGame;
}
return MainMenuResult::NoSelection { selected: newselection } return MainMenuResult::NoSelection { selected: newselection }
} }
VirtualKeyCode::Down => { VirtualKeyCode::Down => {
let newselection; let mut newselection;
match selection { match selection {
MainMenuSelection::NewGame => newselection = MainMenuSelection::LoadGame, MainMenuSelection::NewGame => newselection = MainMenuSelection::LoadGame,
MainMenuSelection::LoadGame => newselection = MainMenuSelection::Quit, MainMenuSelection::LoadGame => newselection = MainMenuSelection::Quit,
MainMenuSelection::Quit => newselection = MainMenuSelection::NewGame MainMenuSelection::Quit => newselection = MainMenuSelection::NewGame
} }
if newselection == MainMenuSelection::LoadGame && !save_exists {
newselection = MainMenuSelection::Quit;
}
return MainMenuResult::NoSelection { selected: newselection } return MainMenuResult::NoSelection { selected: newselection }
} }
VirtualKeyCode::Return => {return MainMenuResult::Selected { selected: selection }} VirtualKeyCode::Return => {return MainMenuResult::Selected { selected: selection }}

View File

@ -42,6 +42,8 @@ mod spawner;
mod inventory_system; mod inventory_system;
use inventory_system::*; use inventory_system::*;
mod saveload_system;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum RunState { pub enum RunState {
AwaitingInput, AwaitingInput,
@ -227,17 +229,18 @@ impl GameState for State {
} }
gui::MainMenuResult::Selected { selected } => match selected { gui::MainMenuResult::Selected { selected } => match selected {
gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun, gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun,
gui::MainMenuSelection::LoadGame => 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), gui::MainMenuSelection::Quit => ::std::process::exit(0),
}, },
} }
} }
RunState::SaveGame => { RunState::SaveGame => {
let data = serde_json::to_string(&*self.ecs.fetch::<Map>()).unwrap(); saveload_system::save_game(&mut self.ecs);
println!("{}", data); newrunstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::LoadGame };
newrunstate = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::LoadGame,
};
} }
} }
@ -279,10 +282,11 @@ fn main() -> rltk::BError {
gs.ecs.register::<AreaOfEffect>(); gs.ecs.register::<AreaOfEffect>();
gs.ecs.register::<Confusion>(); gs.ecs.register::<Confusion>();
gs.ecs.register::<SimpleMarker<SerializeMe>>(); gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
let map = Map::new_map_rooms_and_corridors(); let map = Map::new_map_rooms_and_corridors(1);
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)); gs.ecs.insert(Point::new(player_x, player_y));

View File

@ -23,6 +23,7 @@ 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>,
pub depth : i32,
#[serde(skip_serializing)] #[serde(skip_serializing)]
#[serde(skip_deserializing)] #[serde(skip_deserializing)]
@ -61,7 +62,7 @@ impl Map {
} }
} }
pub fn new_map_rooms_and_corridors() -> Map { pub fn new_map_rooms_and_corridors(new_depth : i32) -> Map {
let mut map = Map { let mut map = Map {
tiles: vec![TileType::Wall; MAPCOUNT], tiles: vec![TileType::Wall; MAPCOUNT],
rooms: Vec::new(), rooms: Vec::new(),
@ -70,7 +71,8 @@ impl Map {
revealed_tiles: vec![false; MAPCOUNT], revealed_tiles: vec![false; MAPCOUNT],
visible_tiles: vec![false; MAPCOUNT], visible_tiles: vec![false; MAPCOUNT],
blocked: vec![false; MAPCOUNT], blocked: vec![false; MAPCOUNT],
tile_content: vec![Vec::new(); MAPCOUNT] tile_content: vec![Vec::new(); MAPCOUNT],
depth: new_depth
}; };
const MAX_ROOMS: i32 = 30; const MAX_ROOMS: i32 = 30;

122
src/saveload_system.rs Normal file
View File

@ -0,0 +1,122 @@
use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator, SerializeComponents, DeserializeComponents, MarkedBuilder};
use specs::error::NoError;
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::<NoError, 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::<NoError, _>::deserialize(
&mut ( &mut $ecs.write_storage::<$type>(), ),
&mut $data.0, // entities
&mut $data.1, // marker
&mut $data.2, // allocater
&mut $de,
)
.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>().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()
}
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::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"); }
}