1
0
Fork 0

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
[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"

View File

@ -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,
}

View File

@ -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();

View File

@ -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>>,
}

View File

@ -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,
},
}

View File

@ -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
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::{
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();
}