Implement basic game saving

This commit is contained in:
Timothy Warren 2021-11-08 13:58:40 -05:00
parent 84934128d5
commit 48609aee1f
8 changed files with 163 additions and 40 deletions

View File

@ -7,6 +7,8 @@ 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.0" } rltk = { version = "0.8.0", 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"

View File

@ -1,14 +1,17 @@
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)] #[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,10 +19,10 @@ pub struct Renderable {
pub render_order: i32, pub render_order: i32,
} }
#[derive(Component, Debug, Default)] #[derive(Component, Debug, Clone, Serialize, Deserialize, Default)]
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,
@ -36,10 +39,10 @@ impl Default for Viewshed {
} }
} }
#[derive(Component, Debug, Default)] #[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
pub struct Monster {} pub struct Monster {}
#[derive(Component, Debug)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Name { pub struct Name {
pub name: String, pub name: String,
} }
@ -52,10 +55,10 @@ impl Name {
} }
} }
#[derive(Component, Debug, Default)] #[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
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,
@ -63,12 +66,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>,
} }
@ -86,55 +89,62 @@ impl SufferDamage {
} }
} }
#[derive(Component, Debug, Default)] #[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
pub struct Item {} pub struct Item {}
#[derive(Component, Debug)] #[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
pub struct Consumable {}
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Ranged {
pub range: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct InflictsDamage {
pub damage: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct AreaOfEffect {
pub radius: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Confusion {
pub turns: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct ProvidesHealing { pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: 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)] #[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, Clone)] #[derive(Component, Debug, ConvertSaveload)]
pub struct WantsToDropItem { pub struct WantsToDropItem {
pub item: Entity, pub item: Entity,
} }
#[derive(Component, Debug, Default)] pub struct SerializeMe;
pub struct Consumable {}
#[derive(Component, Debug)] #[derive(Component, Serialize, Deserialize, Clone)]
pub struct Ranged { pub struct SerializationHelper {
pub range: i32, pub map: crate::map::Map,
}
#[derive(Component, Debug)]
pub struct InflictsDamage {
pub damage: i32,
}
#[derive(Component, Debug)]
pub struct AreaOfEffect {
pub radius: i32,
}
#[derive(Component, Debug)]
pub struct Confusion {
pub turns: i32,
} }

View File

@ -1,5 +1,7 @@
use rltk::{GameState, Point, Rltk}; use rltk::{GameState, Point, Rltk};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
extern crate serde;
mod components; mod components;
mod damage_system; mod damage_system;
@ -12,6 +14,7 @@ mod melee_combat_system;
mod monster_ai_system; mod monster_ai_system;
mod player; mod player;
mod rect; mod rect;
pub mod saveload_system;
mod spawner; mod spawner;
mod visibility_system; mod visibility_system;
@ -55,6 +58,7 @@ pub enum RunState {
MainMenu { MainMenu {
menu_selection: gui::MainMenuSelection, menu_selection: gui::MainMenuSelection,
}, },
SaveGame,
} }
pub struct State { pub struct State {
@ -240,6 +244,13 @@ impl GameState for State {
}, },
} }
} }
RunState::SaveGame => {
saveload_system::save_game(&mut self.ecs);
newrunstate = RunState::MainMenu {
menu_selection: gui::MainMenuSelection::LoadGame,
}
}
} }
{ {
@ -283,8 +294,12 @@ fn main() -> rltk::BError {
InflictsDamage, InflictsDamage,
AreaOfEffect, AreaOfEffect,
Confusion, Confusion,
SimpleMarker<SerializeMe>,
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].center(); let (player_x, player_y) = map.rooms[0].center();

View File

@ -1,5 +1,6 @@
use crate::Rect; use crate::Rect;
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, SmallVec, RGB}; use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, SmallVec, RGB};
use serde::{Deserialize, Serialize};
use specs::prelude::*; use specs::prelude::*;
use std::cmp::{max, min}; use std::cmp::{max, min};
@ -7,12 +8,13 @@ pub const MAP_WIDTH: usize = 80;
pub const MAP_HEIGHT: usize = 43; pub const MAP_HEIGHT: usize = 43;
pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH; pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum TileType { pub enum TileType {
Wall, Wall,
Floor, Floor,
} }
#[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>,
@ -21,6 +23,9 @@ 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>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub tile_content: Vec<Vec<Entity>>, pub tile_content: Vec<Vec<Entity>>,
} }

View File

@ -104,6 +104,9 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
// Show item drop screen // Show item drop screen
VirtualKeyCode::D => return RunState::ShowDropItem, VirtualKeyCode::D => return RunState::ShowDropItem,
// Save and Quit
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,

79
src/saveload_system.rs Normal file
View File

@ -0,0 +1,79 @@
use crate::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();
)*
};
}
pub fn save_game(ecs: &mut World) {
// Create helper
let mapcopy = ecs.get_mut::<crate::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,
Confusion,
ProvidesHealing,
InBackpack,
WantsToPickupItem,
WantsToUseItem,
WantsToDropItem,
SerializationHelper
);
}
// Clean up
ecs.delete_entity(savehelper)
.expect("Failed to clean up savehelper component");
}
pub fn does_save_exist() -> bool {
Path::new("./savegame.json").exists()
}

View File

@ -1,10 +1,11 @@
use crate::components::{ use crate::components::{
AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster, AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster,
Name, Player, Position, ProvidesHealing, Ranged, Renderable, Viewshed, Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed,
}; };
use crate::{Rect, MAP_WIDTH}; use crate::{Rect, MAP_WIDTH};
use rltk::{RandomNumberGenerator, RGB}; use rltk::{RandomNumberGenerator, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker};
const MAX_MONSTERS: i32 = 4; const MAX_MONSTERS: i32 = 4;
const MAX_ITEMS: i32 = 2; const MAX_ITEMS: i32 = 2;
@ -31,6 +32,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
defense: 2, defense: 2,
power: 5, power: 5,
}) })
.marked::<SimpleMarker<SerializeMe>>()
.build() .build()
} }
@ -76,6 +78,7 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy
defense: 1, defense: 1,
power: 4, power: 4,
}) })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -166,6 +169,7 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(InflictsDamage { damage: 8 }) .with(InflictsDamage { damage: 8 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -199,6 +203,7 @@ fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(InflictsDamage { damage: 20 }) .with(InflictsDamage { damage: 20 })
.with(AreaOfEffect { radius: 3 }) .with(AreaOfEffect { radius: 3 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
@ -216,5 +221,6 @@ fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
.with(Consumable {}) .with(Consumable {})
.with(Ranged { range: 6 }) .with(Ranged { range: 6 })
.with(Confusion { turns: 4 }) .with(Confusion { turns: 4 })
.marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }