use ::rltk::{GameState, Point, Rltk}; use ::specs::prelude::*; use crate::components::*; use crate::game_log::GameLog; use crate::gui::{self, show_cheat_mode, CheatMenuResult, MainMenuSelection}; use crate::hunger_system::HungerSystem; use crate::inventory_system::{ ItemCollectionSystem, ItemDropSystem, ItemEquipOnUse, ItemIdentificationSystem, ItemRemoveSystem, ItemUseSystem, }; use crate::lighting_system::LightingSystem; use crate::map::{self, *}; use crate::map_indexing_system::MapIndexingSystem; use crate::melee_combat_system::MeleeCombatSystem; use crate::movement_system::MovementSystem; use crate::particle_system::{self, ParticleSpawnSystem}; use crate::player::*; use crate::raws::*; use crate::trigger_system::TriggerSystem; use crate::visibility_system::VisibilitySystem; use crate::{ai, camera, damage_system, effects, saveload_system, spawner}; pub const SHOW_MAPGEN_VISUALIZER: bool = false; #[derive(PartialEq, Copy, Clone)] pub enum VendorMode { Buy, Sell, } #[derive(PartialEq, Copy, Clone)] 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 }, } pub struct State { pub ecs: World, mapgen_next_state: Option, mapgen_history: Vec, mapgen_index: usize, mapgen_timer: f32, } impl State { pub(super) fn new() -> Self { State { ecs: World::new(), mapgen_next_state: Some(RunState::MainMenu { menu_selection: MainMenuSelection::NewGame, }), mapgen_index: 0, mapgen_history: Vec::new(), mapgen_timer: 0.0, } } fn run_systems(&mut self) { let mut mapindex = MapIndexingSystem {}; mapindex.run_now(&self.ecs); let mut vis = VisibilitySystem {}; vis.run_now(&self.ecs); let mut encumbrance = ai::EncumbranceSystem {}; encumbrance.run_now(&self.ecs); let mut initiative = ai::InitiativeSystem {}; initiative.run_now(&self.ecs); let mut turnstatus = ai::TurnStatusSystem {}; turnstatus.run_now(&self.ecs); let mut quipper = ai::QuipSystem {}; quipper.run_now(&self.ecs); let mut adjacent = ai::AdjacentAI {}; adjacent.run_now(&self.ecs); let mut visible = ai::VisibleAI {}; visible.run_now(&self.ecs); let mut approach = ai::ApproachAI {}; approach.run_now(&self.ecs); let mut flee = ai::FleeAI {}; flee.run_now(&self.ecs); let mut chase = ai::ChaseAI {}; chase.run_now(&self.ecs); let mut defaultmove = ai::DefaultMoveAI {}; defaultmove.run_now(&self.ecs); let mut moving = MovementSystem {}; moving.run_now(&self.ecs); let mut triggers = TriggerSystem {}; triggers.run_now(&self.ecs); let mut melee = MeleeCombatSystem {}; melee.run_now(&self.ecs); let mut pickup = ItemCollectionSystem {}; pickup.run_now(&self.ecs); let mut itemequip = ItemEquipOnUse {}; itemequip.run_now(&self.ecs); let mut itemuse = ItemUseSystem {}; itemuse.run_now(&self.ecs); let mut item_id = ItemIdentificationSystem {}; item_id.run_now(&self.ecs); let mut drop_items = ItemDropSystem {}; drop_items.run_now(&self.ecs); let mut item_remove = ItemRemoveSystem {}; item_remove.run_now(&self.ecs); let mut hunger = HungerSystem {}; hunger.run_now(&self.ecs); effects::run_effects_queue(&mut self.ecs); let mut particles = ParticleSpawnSystem {}; particles.run_now(&self.ecs); let mut lighting = LightingSystem {}; lighting.run_now(&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 let mut gamelog = self.ecs.fetch_mut::(); gamelog.append("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); } } } impl GameState for State { fn tick(&mut self, ctx: &mut Rltk) { let mut newrunstate; { let runstate = self.ecs.fetch::(); newrunstate = *runstate; } ctx.cls(); particle_system::cull_dead_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.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); } RunState::Ticking => { while newrunstate == RunState::Ticking { self.run_systems(); self.ecs.maintain(); newrunstate = match *self.ecs.fetch::() { RunState::AwaitingInput => RunState::AwaitingInput, RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 }, RunState::TownPortal => RunState::TownPortal, RunState::TeleportingToOtherLevel { x, y, depth } => { RunState::TeleportingToOtherLevel { x, y, depth } } _ => RunState::Ticking, }; } } 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 => { 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 => 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; } } { let mut runwriter = self.ecs.write_resource::(); *runwriter = newrunstate; } damage_system::delete_the_dead(&mut self.ecs); } }