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
|
||||
|
||||
[dependencies]
|
||||
rltk = { version = "0.8.0" }
|
||||
specs = "0.16.1"
|
||||
rltk = { version = "0.8.0", features=["serde"] }
|
||||
specs = {version = "0.16.1", features=["serde"] }
|
||||
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 serde::{Deserialize, Serialize};
|
||||
use specs::error::NoError;
|
||||
use specs::prelude::*;
|
||||
use specs::saveload::{ConvertSaveload, Marker};
|
||||
use specs_derive::*;
|
||||
|
||||
#[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,
|
||||
@ -16,10 +19,10 @@ pub struct Renderable {
|
||||
pub render_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
#[derive(Component, Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct Player {}
|
||||
|
||||
#[derive(Component)]
|
||||
#[derive(Component, ConvertSaveload, Clone)]
|
||||
pub struct Viewshed {
|
||||
pub visible_tiles: Vec<rltk::Point>,
|
||||
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 {}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||
pub struct Name {
|
||||
pub name: String,
|
||||
}
|
||||
@ -52,10 +55,10 @@ impl Name {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct BlocksTile {}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||
pub struct CombatStats {
|
||||
pub max_hp: i32,
|
||||
pub hp: i32,
|
||||
@ -63,12 +66,12 @@ pub struct CombatStats {
|
||||
pub power: i32,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone)]
|
||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||
pub struct WantsToMelee {
|
||||
pub target: Entity,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
||||
pub struct SufferDamage {
|
||||
pub amount: Vec<i32>,
|
||||
}
|
||||
@ -86,55 +89,62 @@ impl SufferDamage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
#[derive(Component, Debug, Serialize, Deserialize, Clone, Default)]
|
||||
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 heal_amount: i32,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone)]
|
||||
#[derive(Component, Debug, ConvertSaveload)]
|
||||
pub struct InBackpack {
|
||||
pub owner: Entity,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone)]
|
||||
#[derive(Component, Debug, ConvertSaveload)]
|
||||
pub struct WantsToPickupItem {
|
||||
pub collected_by: Entity,
|
||||
pub item: Entity,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[derive(Component, Debug, ConvertSaveload)]
|
||||
pub struct WantsToUseItem {
|
||||
pub item: Entity,
|
||||
pub target: Option<rltk::Point>,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone)]
|
||||
#[derive(Component, Debug, ConvertSaveload)]
|
||||
pub struct WantsToDropItem {
|
||||
pub item: Entity,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
pub struct Consumable {}
|
||||
pub struct SerializeMe;
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct Ranged {
|
||||
pub range: i32,
|
||||
}
|
||||
|
||||
#[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,
|
||||
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||
pub struct SerializationHelper {
|
||||
pub map: crate::map::Map,
|
||||
}
|
||||
|
15
src/main.rs
15
src/main.rs
@ -1,5 +1,7 @@
|
||||
use rltk::{GameState, Point, Rltk};
|
||||
use specs::prelude::*;
|
||||
use specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
|
||||
extern crate serde;
|
||||
|
||||
mod components;
|
||||
mod damage_system;
|
||||
@ -12,6 +14,7 @@ mod melee_combat_system;
|
||||
mod monster_ai_system;
|
||||
mod player;
|
||||
mod rect;
|
||||
pub mod saveload_system;
|
||||
mod spawner;
|
||||
mod visibility_system;
|
||||
|
||||
@ -55,6 +58,7 @@ pub enum RunState {
|
||||
MainMenu {
|
||||
menu_selection: gui::MainMenuSelection,
|
||||
},
|
||||
SaveGame,
|
||||
}
|
||||
|
||||
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,
|
||||
AreaOfEffect,
|
||||
Confusion,
|
||||
SimpleMarker<SerializeMe>,
|
||||
SerializationHelper,
|
||||
);
|
||||
|
||||
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
|
||||
|
||||
let map = Map::new_map_rooms_and_corridors();
|
||||
let (player_x, player_y) = map.rooms[0].center();
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::Rect;
|
||||
use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, SmallVec, RGB};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::prelude::*;
|
||||
use std::cmp::{max, min};
|
||||
|
||||
@ -7,12 +8,13 @@ pub const MAP_WIDTH: usize = 80;
|
||||
pub const MAP_HEIGHT: usize = 43;
|
||||
pub const MAP_COUNT: usize = MAP_HEIGHT * MAP_WIDTH;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum TileType {
|
||||
Wall,
|
||||
Floor,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize, Clone)]
|
||||
pub struct Map {
|
||||
pub tiles: Vec<TileType>,
|
||||
pub rooms: Vec<Rect>,
|
||||
@ -21,6 +23,9 @@ pub struct Map {
|
||||
pub revealed_tiles: Vec<bool>,
|
||||
pub visible_tiles: Vec<bool>,
|
||||
pub blocked: Vec<bool>,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(skip_deserializing)]
|
||||
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
|
||||
VirtualKeyCode::D => return RunState::ShowDropItem,
|
||||
|
||||
// Save and Quit
|
||||
VirtualKeyCode::Escape => return RunState::SaveGame,
|
||||
|
||||
_ => return RunState::AwaitingInput,
|
||||
},
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct Rect {
|
||||
pub x1: 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::{
|
||||
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 rltk::{RandomNumberGenerator, RGB};
|
||||
use specs::prelude::*;
|
||||
use specs::saveload::{MarkedBuilder, SimpleMarker};
|
||||
|
||||
const MAX_MONSTERS: i32 = 4;
|
||||
const MAX_ITEMS: i32 = 2;
|
||||
@ -31,6 +32,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||
defense: 2,
|
||||
power: 5,
|
||||
})
|
||||
.marked::<SimpleMarker<SerializeMe>>()
|
||||
.build()
|
||||
}
|
||||
|
||||
@ -76,6 +78,7 @@ fn monster<S: ToString>(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy
|
||||
defense: 1,
|
||||
power: 4,
|
||||
})
|
||||
.marked::<SimpleMarker<SerializeMe>>()
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -166,6 +169,7 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
|
||||
.with(Consumable {})
|
||||
.with(Ranged { range: 6 })
|
||||
.with(InflictsDamage { damage: 8 })
|
||||
.marked::<SimpleMarker<SerializeMe>>()
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -199,6 +203,7 @@ fn fireball_scroll(ecs: &mut World, x: i32, y: i32) {
|
||||
.with(Ranged { range: 6 })
|
||||
.with(InflictsDamage { damage: 20 })
|
||||
.with(AreaOfEffect { radius: 3 })
|
||||
.marked::<SimpleMarker<SerializeMe>>()
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -216,5 +221,6 @@ fn confusion_scroll(ecs: &mut World, x: i32, y: i32) {
|
||||
.with(Consumable {})
|
||||
.with(Ranged { range: 6 })
|
||||
.with(Confusion { turns: 4 })
|
||||
.marked::<SimpleMarker<SerializeMe>>()
|
||||
.build();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user