Added save/load

This commit is contained in:
Benjamyn Love 2022-12-04 12:33:50 +11:00
parent a2171b852b
commit 01f10c5153
5 changed files with 182 additions and 37 deletions

View File

@ -1,15 +1,17 @@
use rltk::RGB;
use specs::prelude::*;
use specs_derive::*;
use specs::saveload::{Marker, ConvertSaveload};
use serde::{Serialize, Deserialize};
use specs::error::NoError;
#[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,
@ -17,30 +19,30 @@ pub struct Renderable {
pub render_order: i32,
}
#[derive(Component, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Player {}
#[derive(Component)]
#[derive(Component, ConvertSaveload, Clone)]
pub struct Viewshed {
pub visible_tiles: Vec<rltk::Point>,
pub range: i32,
pub dirty: bool,
}
#[derive(Component, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Monster {}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Name {
pub name: String,
}
#[derive(Component, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct BlocksTile {}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct CombatStats {
pub max_hp: i32,
pub hp: i32,
@ -48,12 +50,12 @@ pub struct CombatStats {
pub power: i32,
}
#[derive(Component, Debug, Clone)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct WantsToMelee {
pub target: Entity,
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct SufferDamage {
pub amount: Vec<i32>,
}
@ -71,56 +73,61 @@ impl SufferDamage {
}
}
#[derive(Component, Debug)]
#[derive(Component, Serialize, Deserialize, Debug, Clone)]
pub struct Item {}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct ProvidesHealing {
pub heal_amount: i32,
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Debug)]
pub struct InBackpack {
pub owner: Entity,
}
#[derive(Component, Debug, Clone)]
#[derive(Component, ConvertSaveload, Debug, Clone)]
pub struct WantsToPickupItem {
pub collected_by: Entity,
pub item: Entity,
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Debug)]
pub struct WantsToUseItem {
pub item: Entity,
pub target: Option<rltk::Point>
}
#[derive(Component, Debug, Clone)]
#[derive(Component, ConvertSaveload, Debug, Clone)]
pub struct WantsToDropItem {
pub item: Entity,
}
#[derive(Component, Debug)]
#[derive(Component, Serialize, Deserialize, Clone, Debug)]
pub struct Consumable {}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Ranged {
pub range : i32
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct InflictsDamage {
pub damage : i32
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct AreaOfEffect {
pub radius : i32
}
#[derive(Component, Debug)]
#[derive(Component, ConvertSaveload, Clone, Debug)]
pub struct Confusion {
pub turns : i32
}
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 {
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");
@ -284,11 +285,14 @@ 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");
}
if selection == MainMenuSelection::LoadGame {
ctx.print_color_centered(25, RGB::named(rltk::MAGENTA), 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 save_exists {
if selection == MainMenuSelection::LoadGame {
ctx.print_color_centered(25, RGB::named(rltk::MAGENTA), 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 {
ctx.print_color_centered(26, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Quit the game");
@ -302,21 +306,27 @@ pub fn main_menu(gs: &mut State, ctx : &mut Rltk) -> MainMenuResult {
match key {
VirtualKeyCode::Escape => {return MainMenuResult::NoSelection { selected: MainMenuSelection::Quit }},
VirtualKeyCode::Up => {
let newselection;
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 newselection;
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 }}

View File

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

View File

@ -23,6 +23,7 @@ pub struct Map {
pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>,
pub blocked : Vec<bool>,
pub depth : i32,
#[serde(skip_serializing)]
#[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 {
tiles: vec![TileType::Wall; MAPCOUNT],
rooms: Vec::new(),
@ -70,7 +71,8 @@ impl Map {
revealed_tiles: vec![false; MAPCOUNT],
visible_tiles: 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;

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"); }
}