//! Game state use ::bracket_lib::prelude::*; use ::specs::prelude::*; use crate::components::*; use crate::gui::{self, show_cheat_mode, CheatMenuResult, MainMenuSelection}; use crate::map::{self, *}; use crate::player::*; use crate::raws::*; use crate::systems::{self, particle_system}; use crate::{camera, colors, damage_system, gamelog, player, saveload_system, spawner}; /// Whether to show a visual representation of map generation #[cfg(not(feature = "debug"))] pub const SHOW_MAPGEN_VISUALIZER: bool = false; #[cfg(feature = "debug")] pub const SHOW_MAPGEN_VISUALIZER: bool = true; /// Whether to show an FPS counter pub const SHOW_FPS: bool = true; /// The main actions possible with a vendor #[derive(PartialEq, Copy, Clone)] pub enum VendorMode { Buy, Sell, } #[derive(PartialEq, Copy, Clone)] /// The states for the game engine's state machine pub enum RunState { AwaitingInput, PreRun, Ticking, ShowInventory, ShowDropItem, ShowTargeting { range: i32, item: Entity }, MainMenu { menu_selection: MainMenuSelection }, SaveGame, NextLevel, PreviousLevel, TownPortal, ShowRemoveItem, GameOver, MagicMapReveal { row: i32 }, MapGeneration, ShowCheatMenu, ShowVendor { vendor: Entity, mode: VendorMode }, TeleportingToOtherLevel { x: i32, y: i32, depth: i32 }, ShowRemoveCurse, ShowIdentify, } struct MapGen { next_state: Option, history: Vec, index: usize, timer: f32, } /// The main wrapper around the game's state pub struct State { pub ecs: World, mapgen: MapGen, dispatcher: Box, } impl State { pub(super) fn new() -> Self { State { ecs: World::new(), mapgen: MapGen { next_state: Some(RunState::MainMenu { menu_selection: MainMenuSelection::NewGame, }), index: 0, history: Vec::new(), timer: 0.0, }, dispatcher: systems::build(), } } fn run_systems(&mut self) { self.dispatcher.run_now(&mut self.ecs); self.ecs.maintain(); } fn goto_level(&mut self, offset: i32) { freeze_level_entities(&mut self.ecs); // Build a new map and place the player let current_depth = self.ecs.fetch::().depth; self.generate_world_map(current_depth + offset, offset); // Notify the player gamelog::log_line("You change level."); } fn game_over_cleanup(&mut self) { // Delete everything let mut to_delete = Vec::new(); for e in self.ecs.entities().join() { to_delete.push(e); } for del in to_delete.iter() { self.ecs .delete_entity(*del) .expect("Failed to delete entity"); } // Spawn a new player { let player_entity = spawner::player(&mut self.ecs, 0, 0); let mut player_entity_writer = self.ecs.write_resource::(); *player_entity_writer = player_entity; } // Replace the world maps self.ecs.insert(map::MasterDungeonMap::new()); // Build a new map and place the player self.generate_world_map(1, 0); } pub(super) fn generate_world_map(&mut self, new_depth: i32, offset: i32) { self.mapgen.index = 0; self.mapgen.timer = 0.0; self.mapgen.history.clear(); if let Some(history) = map::level_transition(&mut self.ecs, new_depth, offset) { self.mapgen.history = history; } else { map::thaw_level_entities(&mut self.ecs); } // Set up the game log gamelog::clear_log(); if new_depth == 1 { gamelog::line("Welcome to") .append_color(colors::CYAN, "Rusty Roguelike") .log(); } gamelog::clear_events(); } } impl GameState for State { /// The big, nasty, state machine handler fn tick(&mut self, ctx: &mut BTerm) { let mut newrunstate; { let runstate = self.ecs.fetch::(); newrunstate = *runstate; } ctx.set_active_console(1); ctx.cls(); ctx.set_active_console(0); ctx.cls(); particle_system::update_particles(&mut self.ecs, ctx); match newrunstate { RunState::MainMenu { .. } => {} RunState::GameOver { .. } => {} _ => { camera::render_camera(&self.ecs, ctx); gui::draw_ui(&self.ecs, ctx); } } match newrunstate { RunState::MapGeneration => { if !SHOW_MAPGEN_VISUALIZER { newrunstate = self.mapgen.next_state.unwrap(); } ctx.set_active_console(1); ctx.cls(); ctx.set_active_console(0); ctx.cls(); if self.mapgen.index < self.mapgen.history.len() { camera::render_debug_map(&self.mapgen.history[self.mapgen.index], ctx); } self.mapgen.timer += ctx.frame_time_ms; if self.mapgen.timer > 300.0 { self.mapgen.timer = 0.0; self.mapgen.index += 1; if self.mapgen.index >= self.mapgen.history.len() { newrunstate = self.mapgen.next_state.unwrap(); } } } RunState::PreRun => { self.run_systems(); self.ecs.maintain(); newrunstate = RunState::AwaitingInput; } RunState::AwaitingInput => { newrunstate = player_input(self, ctx); if newrunstate != RunState::AwaitingInput { crate::gamelog::record_event("Turn", 1); } } RunState::Ticking => { let mut should_change_target = false; while newrunstate == RunState::Ticking { self.run_systems(); self.ecs.maintain(); newrunstate = match *self.ecs.fetch::() { RunState::AwaitingInput => { should_change_target = true; RunState::AwaitingInput } RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 }, RunState::TownPortal => RunState::TownPortal, RunState::TeleportingToOtherLevel { x, y, depth } => { RunState::TeleportingToOtherLevel { x, y, depth } } RunState::ShowRemoveCurse => RunState::ShowRemoveCurse, RunState::ShowIdentify => RunState::ShowIdentify, _ => RunState::Ticking, }; } if should_change_target { player::end_turn_targeting(&mut self.ecs); } } RunState::ShowInventory => { let result = gui::show_inventory(self, ctx); match result.0 { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { let item_entity = result.1.unwrap(); let is_ranged = self.ecs.read_storage::(); if let Some(is_item_ranged) = is_ranged.get(item_entity) { newrunstate = RunState::ShowTargeting { range: is_item_ranged.range, item: item_entity, }; } else { let mut intent = self.ecs.write_storage::(); intent .insert( *self.ecs.fetch::(), WantsToUseItem { item: item_entity, target: None, }, ) .expect("failed to add intent to use item"); newrunstate = RunState::Ticking; } } } } 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("failed to add intent to drop item"); newrunstate = RunState::Ticking; } } } RunState::ShowRemoveItem => { let result = gui::remove_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::(), WantsToRemoveItem { item: item_entity }, ) .expect("Unable to insert intent to remove item"); newrunstate = RunState::Ticking; } } } RunState::ShowTargeting { range, item } => { let result = gui::ranged_target(self, ctx, range); match result.0 { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { if self.ecs.read_storage::().get(item).is_some() { let mut intent = self.ecs.write_storage::(); intent .insert( *self.ecs.fetch::(), WantsToCastSpell { spell: item, target: result.1, }, ) .expect("failed to add intent to cast spell"); newrunstate = RunState::Ticking; } else { let mut intent = self.ecs.write_storage::(); intent .insert( *self.ecs.fetch::(), WantsToUseItem { item, target: result.1, }, ) .expect("failed to add intent to use item"); newrunstate = RunState::Ticking; } } } } RunState::MainMenu { .. } => match gui::main_menu(self, ctx) { gui::MainMenuResult::NoSelection { selected } => { newrunstate = RunState::MainMenu { menu_selection: selected, } } gui::MainMenuResult::Selected { selected } => match selected { gui::MainMenuSelection::NewGame => { self.game_over_cleanup(); 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::GameOver => match gui::game_over(ctx) { gui::GameOverResult::NoSelection => {} gui::GameOverResult::QuitToMenu => { self.game_over_cleanup(); newrunstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame, }; } }, RunState::SaveGame => { saveload_system::save_game(&mut self.ecs); newrunstate = RunState::MainMenu { menu_selection: gui::MainMenuSelection::LoadGame, } } RunState::NextLevel => { self.goto_level(1); newrunstate = RunState::PreRun; } RunState::PreviousLevel => { self.goto_level(-1); self.mapgen.next_state = Some(RunState::PreRun); newrunstate = RunState::MapGeneration; } RunState::MagicMapReveal { row } => { let mut map = self.ecs.fetch_mut::(); for x in 0..map.width { let idx = map.xy_idx(x as i32, row); map.revealed_tiles[idx] = true; } if row == map.height - 1 { newrunstate = RunState::Ticking; } else { newrunstate = RunState::MagicMapReveal { row: row + 1 }; } } RunState::ShowCheatMenu => match show_cheat_mode(self, ctx) { CheatMenuResult::Cancel => newrunstate = RunState::AwaitingInput, CheatMenuResult::NoResponse => {} CheatMenuResult::TeleportToExit => { self.goto_level(1); self.mapgen.next_state = Some(RunState::PreRun); newrunstate = RunState::MapGeneration } CheatMenuResult::Heal => { let player = self.ecs.fetch::(); let mut pools = self.ecs.write_storage::(); let mut player_pools = pools.get_mut(*player).unwrap(); player_pools.hit_points.current = player_pools.hit_points.max; newrunstate = RunState::AwaitingInput; } CheatMenuResult::Reveal => { let mut map = self.ecs.fetch_mut::(); for v in map.revealed_tiles.iter_mut() { *v = true; } newrunstate = RunState::AwaitingInput; } CheatMenuResult::GodMode => { let player = self.ecs.fetch::(); let mut pools = self.ecs.write_storage::(); let mut player_pools = pools.get_mut(*player).unwrap(); player_pools.god_mode = true; newrunstate = RunState::AwaitingInput; } }, RunState::ShowVendor { vendor, mode } => { let result = gui::show_vendor_menu(self, ctx, vendor, mode); match result.0 { gui::VendorResult::Cancel => newrunstate = RunState::AwaitingInput, gui::VendorResult::NoResponse => {} gui::VendorResult::Sell => { let price = self .ecs .read_storage::() .get(result.1.unwrap()) .unwrap() .base_value * 0.8; self.ecs .write_storage::() .get_mut(*self.ecs.fetch::()) .unwrap() .gold += price; self.ecs .delete_entity(result.1.unwrap()) .expect("Unable to delete sold item"); } gui::VendorResult::Buy => { let tag = result.2.unwrap(); let price = result.3.unwrap(); let mut pools = self.ecs.write_storage::(); let player_entity = self.ecs.fetch::(); let mut identified = self.ecs.write_storage::(); identified .insert(*player_entity, IdentifiedItem { name: tag.clone() }) .expect("Unable to identify item"); std::mem::drop(identified); let player_pools = pools.get_mut(*player_entity).unwrap(); std::mem::drop(player_entity); if player_pools.gold >= price { player_pools.gold -= price; std::mem::drop(pools); let player_entity = *self.ecs.fetch::(); spawn_named_item( &RAWS.lock().unwrap(), &mut self.ecs, &tag, SpawnType::Carried { by: player_entity }, ); } } gui::VendorResult::BuyMode => { newrunstate = RunState::ShowVendor { vendor, mode: VendorMode::Buy, } } gui::VendorResult::SellMode => { newrunstate = RunState::ShowVendor { vendor, mode: VendorMode::Sell, } } } } RunState::TownPortal => { // Spawn the portal spawner::spawn_town_portal(&mut self.ecs); // Transition let map_depth = self.ecs.fetch::().depth; let destination_offset = 0 - (map_depth - 1); self.goto_level(destination_offset); self.mapgen.next_state = Some(RunState::PreRun); newrunstate = RunState::MapGeneration; } RunState::TeleportingToOtherLevel { x, y, depth } => { self.goto_level(depth - 1); let player_entity = self.ecs.fetch::(); if let Some(pos) = self.ecs.write_storage::().get_mut(*player_entity) { pos.x = x; pos.y = y; } let mut ppos = self.ecs.fetch_mut::(); ppos.x = x; ppos.y = y; self.mapgen.next_state = Some(RunState::PreRun); newrunstate = RunState::MapGeneration; } RunState::ShowRemoveCurse => { let result = gui::remove_curse_menu(self, ctx); match result.0 { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { let item_entity = result.1.unwrap(); self.ecs.write_storage::().remove(item_entity); newrunstate = RunState::Ticking; } } } RunState::ShowIdentify => { let result = gui::identify_menu(self, ctx); match result.0 { gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput, gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { let item_entity = result.1.unwrap(); if let Some(name) = self.ecs.read_storage::().get(item_entity) { let mut dm = self.ecs.fetch_mut::(); dm.identified_items.insert(name.name.clone()); } newrunstate = RunState::Ticking; } } } } { let mut runwriter = self.ecs.write_resource::(); *runwriter = newrunstate; } damage_system::delete_the_dead(&mut self.ecs); render_draw_buffer(ctx).expect("Failed to render the draw buffer"); if SHOW_FPS { ctx.print(1, 59, &format!("FPS: {}", ctx.fps)); } } }