Compare commits
2 Commits
49562d62a6
...
01f10c5153
| Author | SHA1 | Date | |
|---|---|---|---|
| 01f10c5153 | |||
| a2171b852b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
.vscode
|
||||
wasm
|
||||
wasm.tgz
|
||||
savegame.json
|
||||
@ -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
|
||||
}
|
||||
14
src/gui.rs
14
src/gui.rs
@ -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 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 }}
|
||||
|
||||
18
src/main.rs
18
src/main.rs
@ -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));
|
||||
|
||||
@ -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
122
src/saveload_system.rs
Normal 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"); }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user