From 5254209c3528c77a2fe3f33849e94b319e4de646 Mon Sep 17 00:00:00 2001 From: Benjamyn Love Date: Tue, 29 Nov 2022 20:28:10 +1100 Subject: [PATCH] SaveLoad --- Cargo.lock | 33 +++++++++ Cargo.toml | 8 ++- src/components.rs | 17 ++--- src/gui.rs | 70 ++++++++++++++++++- src/inventory_system.rs | 2 - src/main.rs | 135 ++++++++++++++++++++++--------------- src/map.rs | 8 ++- src/melee_combat_system.rs | 2 +- src/player.rs | 5 +- src/rect.rs | 2 + 10 files changed, 202 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88ca093..84a1720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,7 @@ source = "git+https://github.com/amethyst/bracket-lib#851f6f08675444fb6fa088b9e6 dependencies = [ "lazy_static", "parking_lot 0.12.1", + "serde", ] [[package]] @@ -108,6 +109,7 @@ name = "bracket-geometry" version = "0.8.7" source = "git+https://github.com/amethyst/bracket-lib#851f6f08675444fb6fa088b9e67bee9fd75554c6" dependencies = [ + "serde", "ultraviolet", ] @@ -155,6 +157,7 @@ dependencies = [ "rand", "rand_xorshift", "regex", + "serde", "wasm-bindgen", ] @@ -800,6 +803,8 @@ name = "hellorust" version = "0.1.0" dependencies = [ "rltk", + "serde", + "serde_json", "specs", "specs-derive", ] @@ -856,6 +861,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + [[package]] name = "jni-sys" version = "0.3.0" @@ -1353,6 +1364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core", + "serde", ] [[package]] @@ -1431,6 +1443,12 @@ dependencies = [ "bracket-lib", ] +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + [[package]] name = "safe_arch" version = "0.5.2" @@ -1484,6 +1502,9 @@ name = "serde" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -1496,6 +1517,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "servo-fontconfig" version = "0.5.1" @@ -1620,6 +1652,7 @@ dependencies = [ "hibitset", "log", "rayon", + "serde", "shred", "shrev", "tuple_utils", diff --git a/Cargo.toml b/Cargo.toml index 11072ec..5f83a4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rltk = { git = "https://github.com/amethyst/bracket-lib" } +rltk = { git = "https://github.com/amethyst/bracket-lib", features= ["serde"] } # rltk = { version = "0.8.7" } -specs = "0.18.0" -specs-derive = "0.4.1" \ No newline at end of file +specs = { version = "0.18.0", features= ["serde"]} +specs-derive = "0.4.1" +serde= { version = "1.0.93", features = ["derive"]} +serde_json = "1.0.39" \ No newline at end of file diff --git a/src/components.rs b/src/components.rs index efbadf8..0b2e047 100644 --- a/src/components.rs +++ b/src/components.rs @@ -2,6 +2,7 @@ use rltk::RGB; use specs::prelude::*; use specs_derive::*; + #[derive(Component)] pub struct Position { pub x: i32, @@ -34,17 +35,7 @@ pub struct Name { pub name: String, } -#[derive(PartialEq, Copy, Clone)] -pub enum RunState { - AwaitingInput, - PreRun, - PlayerTurn, - MonsterTurn, - ShowInventory, - ShowDropItem, - ShowSpawnMenu, - ShowTargeting { range : i32, item : Entity}, -} + #[derive(Component, Debug)] pub struct BlocksTile {} @@ -130,4 +121,6 @@ pub struct AreaOfEffect { #[derive(Component, Debug)] pub struct Confusion { pub turns : i32 -} \ No newline at end of file +} + +pub struct SerializeMe; diff --git a/src/gui.rs b/src/gui.rs index f1f0a5a..0fa4ee9 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,7 +1,13 @@ use rltk::{RGB, Rltk, Point, VirtualKeyCode}; use specs::prelude::*; -use super::{CombatStats, Player, GameLog, Map, Name, Position, State, InBackpack, Viewshed, spawner}; +use super::{CombatStats, Player, GameLog, Map, Name, Position, State, InBackpack, Viewshed, spawner, RunState}; + +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuSelection { NewGame, LoadGame, Quit } + +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuResult { NoSelection{ selected: MainMenuSelection}, Selected{ selected: MainMenuSelection } } fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { let map = ecs.fetch::(); @@ -160,7 +166,7 @@ pub fn spawn_item(gs: &mut State, ctx : &mut Rltk) -> (ItemMenuResult, Option (ItemMenuResult::NoResponse, None), Some(key) => { - match (key) { + match key { VirtualKeyCode::Escape => { (ItemMenuResult::Cancel, None)}, _ => { let selection = rltk::letter_to_option(key); @@ -208,7 +214,7 @@ pub fn drop_item_menu(gs: &mut State, ctx : &mut Rltk) -> (ItemMenuResult, Optio match ctx.key { None => (ItemMenuResult::NoResponse, None), Some(key) => { - match (key) { + match key { VirtualKeyCode::Escape => { (ItemMenuResult::Cancel, None)}, _ => { let selection = rltk::letter_to_option(key); @@ -263,4 +269,62 @@ pub fn ranged_target(gs: &mut State, ctx : &mut Rltk, range: i32) -> (ItemMenuRe } (ItemMenuResult::NoResponse, None) +} + + +pub fn main_menu(gs: &mut State, ctx : &mut Rltk) -> MainMenuResult { + let runstate = gs.ecs.fetch::(); + + ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Rust Roguelike Tutorial"); + + if let RunState::MainMenu{ menu_selection : selection } = *runstate { + if selection == MainMenuSelection::NewGame { + ctx.print_color_centered(24, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Start a new game"); + } else { + ctx.print_color_centered(24, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Start a new game"); + } + + 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"); + } else { + ctx.print_color_centered(26, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "Quit the game"); + } + + match ctx.key { + None => return MainMenuResult::NoSelection { selected: selection }, + Some(key) => { + match key { + VirtualKeyCode::Escape => {return MainMenuResult::NoSelection { selected: MainMenuSelection::Quit }}, + VirtualKeyCode::Up => { + let newselection; + match selection { + MainMenuSelection::NewGame => newselection = MainMenuSelection::Quit, + MainMenuSelection::LoadGame => newselection = MainMenuSelection::NewGame, + MainMenuSelection::Quit => newselection = MainMenuSelection::LoadGame + } + return MainMenuResult::NoSelection { selected: newselection } + } + VirtualKeyCode::Down => { + let newselection; + match selection { + MainMenuSelection::NewGame => newselection = MainMenuSelection::LoadGame, + MainMenuSelection::LoadGame => newselection = MainMenuSelection::Quit, + MainMenuSelection::Quit => newselection = MainMenuSelection::NewGame + } + return MainMenuResult::NoSelection { selected: newselection } + } + VirtualKeyCode::Return => {return MainMenuResult::Selected { selected: selection }} + _ => return MainMenuResult::NoSelection { selected: selection } + } + } + } + } + MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame } + } \ No newline at end of file diff --git a/src/inventory_system.rs b/src/inventory_system.rs index c811eff..a66a419 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -1,7 +1,5 @@ use specs::prelude::*; -use crate::player; - use super::{ gamelog::GameLog, CombatStats, Consumable, InBackpack, Name, Position, ProvidesHealing, WantsToDropItem, WantsToPickupItem, WantsToUseItem, InflictsDamage, Map, SufferDamage, AreaOfEffect, Confusion diff --git a/src/main.rs b/src/main.rs index e62b8ba..171d197 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ -use rltk::{GameState, Point, Rltk, RGB}; +use rltk::{GameState, Point, Rltk}; use specs::prelude::*; +use specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; + +extern crate serde; mod components; pub use components::*; @@ -35,11 +38,25 @@ mod gamelog; pub use gamelog::*; mod spawner; -use spawner::*; + mod inventory_system; use inventory_system::*; +#[derive(PartialEq, Copy, Clone)] +pub enum RunState { + AwaitingInput, + PreRun, + PlayerTurn, + MonsterTurn, + ShowInventory, + ShowDropItem, + ShowSpawnMenu, + ShowTargeting { range : i32, item : Entity}, + MainMenu { menu_selection : gui::MainMenuSelection }, + SaveGame, +} + pub struct State { pub ecs: World, } @@ -68,39 +85,37 @@ impl State { impl GameState for State { fn tick(&mut self, ctx: &mut Rltk) { - ctx.cls(); - draw_map(&self.ecs, ctx); - - { - let positions = self.ecs.read_storage::(); - let renderables = self.ecs.read_storage::(); - let map = self.ecs.fetch::(); - - let mut data = (&positions, &renderables).join().collect::>(); - data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); - - for (pos, render) in data.iter() { - let idx = map.xy_idx(pos.x, pos.y); - if map.visible_tiles[idx] { - ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) - } - } - // for (pos, render) in (&positions, &renderables).join() { - // let idx = map.xy_idx(pos.x, pos.y); - // if map.visible_tiles[idx] { - // ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) - // } - // } - - gui::draw_ui(&self.ecs, ctx); - } - let mut newrunstate; { let runstate = self.ecs.fetch::(); newrunstate = *runstate; } + ctx.cls(); + match newrunstate { + RunState::MainMenu { .. } => {} + _ => { + draw_map(&self.ecs, ctx); + + { + let positions = self.ecs.read_storage::(); + let renderables = self.ecs.read_storage::(); + let map = self.ecs.fetch::(); + + let mut data = (&positions, &renderables).join().collect::>(); + data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); + + for (pos, render) in data.iter() { + let idx = map.xy_idx(pos.x, pos.y); + if map.visible_tiles[idx] { + ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph) + } + } + gui::draw_ui(&self.ecs, ctx); + } + } + } + match newrunstate { RunState::PreRun => { self.run_systems(); @@ -130,7 +145,10 @@ impl GameState for State { let is_ranged = self.ecs.read_storage::(); let is_item_ranged = is_ranged.get(item_entity); if let Some(is_item_ranged) = is_item_ranged { - newrunstate = RunState::ShowTargeting { range: is_item_ranged.range, item: item_entity }; + newrunstate = RunState::ShowTargeting { + range: is_item_ranged.range, + item: item_entity, + }; } else { let mut intent = self.ecs.write_storage::(); intent @@ -144,25 +162,6 @@ impl GameState for State { .expect("Failed to insert intent"); newrunstate = RunState::PlayerTurn; } - - } - } - } - RunState::ShowDropItem => { - let result = gui::drop_item_menu(self, ctx); - match result.0 { - gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, - gui::ItemMenuResult::NoResponse => {} - gui::ItemMenuResult::Selected => { - let item_entity = result.1.unwrap(); - let mut intent = self.ecs.write_storage::(); - intent - .insert( - *self.ecs.fetch::(), - WantsToDropItem { item: item_entity }, - ) - .expect("Unable to insert intent"); - newrunstate = RunState::PlayerTurn; } } } @@ -191,20 +190,46 @@ impl GameState for State { gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { let mut intent = self.ecs.write_storage::(); - intent.insert(*self.ecs.fetch::(), WantsToUseItem { item, target: result.1 }).expect("Unable to insert intent"); + intent + .insert( + *self.ecs.fetch::(), + WantsToUseItem { + item, + target: result.1, + }, + ) + .expect("Unable to insert intent"); newrunstate = RunState::PlayerTurn; } } - } RunState::ShowSpawnMenu => { let result = gui::spawn_item(self, ctx); match result.0 { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} - gui::ItemMenuResult::Selected => {newrunstate = RunState::AwaitingInput;} + gui::ItemMenuResult::Selected => { + newrunstate = RunState::AwaitingInput; + } } - + } + RunState::MainMenu { .. } => { + let result = gui::main_menu(self, ctx); + match result { + gui::MainMenuResult::NoSelection { selected } => newrunstate = RunState::MainMenu{menu_selection: selected}, + gui::MainMenuResult::Selected { selected } => { + match selected { + gui::MainMenuSelection::NewGame => newrunstate = RunState::PreRun, + gui::MainMenuSelection::LoadGame => newrunstate = RunState::PreRun, + gui::MainMenuSelection::Quit => { ::std::process::exit(0)} + } + } + } + } + RunState::SaveGame => { + let data = serde_json::to_string(&*self.ecs.fetch::()).unwrap(); + println!("{}", data); + newrunstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::LoadGame }; } } @@ -245,6 +270,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::>(); let map = Map::new_map_rooms_and_corridors(); let (player_x, player_y) = map.rooms[0].center(); @@ -260,10 +286,11 @@ fn main() -> rltk::BError { gs.ecs.insert(map); gs.ecs.insert(player_entity); - gs.ecs.insert(RunState::PreRun); + gs.ecs.insert(RunState::MainMenu { menu_selection: MainMenuSelection::NewGame }); gs.ecs.insert(gamelog::GameLog { entries: vec!["Welcome to my rougelike".to_string()], }); + gs.ecs.insert(SimpleMarkerAllocator::::new()); // Main loop runner rltk::main_loop(context, gs) diff --git a/src/map.rs b/src/map.rs index 928a2f3..cd62d97 100644 --- a/src/map.rs +++ b/src/map.rs @@ -2,18 +2,19 @@ use super::Rect; use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; use specs::prelude::*; use std::cmp::{max, min}; +use serde::{Serialize, Deserialize}; pub const MAPWIDTH : usize = 80; pub const MAPHIEGHT : usize = 43; pub const MAPCOUNT : usize = MAPHIEGHT * MAPWIDTH; -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] pub enum TileType { Wall, Floor, } -#[derive(Default)] +#[derive(Default, Serialize, Deserialize, Clone)] pub struct Map { pub tiles: Vec, pub rooms: Vec, @@ -22,6 +23,9 @@ pub struct Map { pub revealed_tiles: Vec, pub visible_tiles: Vec, pub blocked : Vec, + + #[serde(skip_serializing)] + #[serde(skip_deserializing)] pub tile_content : Vec> } diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index c916130..82039e2 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -1,6 +1,6 @@ use specs::prelude::*; use super::{CombatStats, WantsToMelee, Name, SufferDamage, GameLog}; -use rltk::console; + pub struct MeleeCombatSystem {} diff --git a/src/player.rs b/src/player.rs index c0e0cff..c175f1b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,5 +1,3 @@ -use crate::spawn_item; - use super::{Map, Player, Position, RunState, State, Viewshed, CombatStats, WantsToMelee, Item, GameLog, WantsToPickupItem, spawner}; use rltk::{Point, Rltk, VirtualKeyCode}; use specs::prelude::*; @@ -7,7 +5,7 @@ use std::cmp::{max, min}; pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { let mut positions = ecs.write_storage::(); - let mut players = ecs.write_storage::(); + let players = ecs.write_storage::(); let mut viewsheds = ecs.write_storage::(); let mut ppos = ecs.write_resource::(); let combat_stats = ecs.read_storage::(); @@ -95,6 +93,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { spawner::health_potion(&mut gs.ecs, mouse_pos.0, mouse_pos.1); return RunState::AwaitingInput; } + VirtualKeyCode::Escape => return RunState::SaveGame, _ => return RunState::AwaitingInput, }, } diff --git a/src/rect.rs b/src/rect.rs index 31cccd8..e5f96a7 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -1,3 +1,5 @@ +use serde::{Serialize, Deserialize}; +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] pub struct Rect { pub x1: i32, pub x2: i32,