mod ai; pub mod camera; mod components; mod damage_system; mod game_log; mod gamesystem; mod gui; mod hunger_system; mod inventory_system; mod lighting_system; mod map; pub mod map_builders; mod map_indexing_system; mod melee_combat_system; mod particle_system; mod player; pub mod random_table; pub mod raws; mod rect; mod rex_assets; pub mod saveload_system; mod spawner; mod trigger_system; mod visibility_system; #[macro_use] extern crate lazy_static; use ::rltk::{GameState, Point, RandomNumberGenerator, Rltk}; use ::specs::prelude::*; use ::specs::saveload::{SimpleMarker, SimpleMarkerAllocator}; use components::*; use damage_system::DamageSystem; pub use game_log::GameLog; use gui::{show_cheat_mode, CheatMenuResult}; use hunger_system::HungerSystem; use inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem}; use lighting_system::LightingSystem; pub use map::*; use map_indexing_system::MapIndexingSystem; use melee_combat_system::MeleeCombatSystem; use particle_system::ParticleSpawnSystem; use player::*; pub use rect::Rect; use trigger_system::TriggerSystem; use visibility_system::VisibilitySystem; /// Cut down on the amount of syntax to register components macro_rules! register { // $gs is needed to get the scope at the usage point // $Type is the Component type that is being registered ($gs: ident <- $( $Type: ty ),*,) => { $( $gs.ecs.register::<$Type>(); )* } } const SHOW_MAPGEN_VISUALIZER: bool = false; #[derive(PartialEq, Copy, Clone)] pub enum RunState { AwaitingInput, PreRun, PlayerTurn, MonsterTurn, ShowInventory, ShowDropItem, ShowTargeting { range: i32, item: Entity, }, MainMenu { menu_selection: gui::MainMenuSelection, }, SaveGame, NextLevel, PreviousLevel, ShowRemoveItem, GameOver, MagicMapReveal { row: i32, }, MapGeneration, ShowCheatMenu, } pub struct State { pub ecs: World, mapgen_next_state: Option, mapgen_history: Vec, mapgen_index: usize, mapgen_timer: f32, } impl State { fn new() -> Self { State { ecs: World::new(), mapgen_next_state: Some(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame, }), mapgen_index: 0, mapgen_history: Vec::new(), mapgen_timer: 0.0, } } fn run_systems(&mut self) { let mut vis = VisibilitySystem {}; vis.run_now(&self.ecs); let mut mob = ai::MonsterAI {}; mob.run_now(&self.ecs); let mut mapindex = MapIndexingSystem {}; mapindex.run_now(&self.ecs); let mut animal = ai::AnimalAI {}; animal.run_now(&self.ecs); let mut bystander = ai::BystanderAI {}; bystander.run_now(&self.ecs); let mut triggers = TriggerSystem {}; triggers.run_now(&self.ecs); let mut melee = MeleeCombatSystem {}; melee.run_now(&self.ecs); let mut damage = DamageSystem {}; damage.run_now(&self.ecs); let mut pickup = ItemCollectionSystem {}; pickup.run_now(&self.ecs); let mut items = ItemUseSystem {}; items.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); let mut particles = ParticleSpawnSystem {}; particles.run_now(&self.ecs); let mut lighting = LightingSystem {}; lighting.run_now(&self.ecs); self.ecs.maintain(); } } 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::PlayerTurn => { self.run_systems(); self.ecs.maintain(); match *self.ecs.fetch::() { RunState::MagicMapReveal { .. } => { newrunstate = RunState::MagicMapReveal { row: 0 } } _ => newrunstate = RunState::MonsterTurn, } } RunState::MonsterTurn => { self.run_systems(); self.ecs.maintain(); newrunstate = RunState::AwaitingInput; } 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::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("failed to add intent to drop item"); newrunstate = RunState::PlayerTurn; } } } 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::PlayerTurn; } } } 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::PlayerTurn; } } } 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::MonsterTurn; } 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 } }, } { let mut runwriter = self.ecs.write_resource::(); *runwriter = newrunstate; } damage_system::delete_the_dead(&mut self.ecs); } } impl State { 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); } 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); } } } fn main() -> ::rltk::BError { let context = ::rltk::RltkBuilder::simple(80, 60) .unwrap() .with_title("Roguelike Tutorial") .build()?; let mut gs = State::new(); register!( gs <- AreaOfEffect, Attributes, BlocksTile, BlocksVisibility, Bystander, Carnivore, Confusion, Consumable, Door, DMSerializationHelper, EntityMoved, EntryTrigger, Equippable, Equipped, Herbivore, Hidden, HungerClock, InBackpack, InflictsDamage, Initiative, Item, LightSource, LootTable, MagicMapper, MeleeWeapon, Monster, Name, NaturalAttackDefense, OtherLevelPosition, ParticleLifetime, Player, Pools, Position, ProvidesFood, ProvidesHealing, Quips, Ranged, Renderable, SerializationHelper, SimpleMarker, SingleActivation, Skills, SufferDamage, Vendor, Viewshed, WantsToDropItem, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, WantsToUseItem, Wearable, ); gs.ecs.insert(SimpleMarkerAllocator::::new()); raws::load_raws(); gs.ecs.insert(map::MasterDungeonMap::new()); gs.ecs.insert(Map::new(1, 64, 64, "New Map")); gs.ecs.insert(Point::zero()); gs.ecs.insert(RandomNumberGenerator::new()); let player_entity = spawner::player(&mut gs.ecs, 0, 0); gs.ecs.insert(player_entity); gs.ecs.insert(RunState::MapGeneration {}); gs.ecs.insert(GameLog::new("Welcome to Rusty Roguelike")); gs.ecs.insert(particle_system::ParticleBuilder::new()); gs.ecs.insert(rex_assets::RexAssets::new()); gs.generate_world_map(1, 0); rltk::main_loop(context, gs) }