Implement basic game saving
This commit is contained in:
parent
84934128d5
commit
48609aee1f
@ -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"
|
@ -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,
|
|
||||||
}
|
}
|
||||||
|
15
src/main.rs
15
src/main.rs
@ -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();
|
||||||
|
|
||||||
|
@ -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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
79
src/saveload_system.rs
Normal 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()
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user