diff --git a/src/ai/approach_ai_system.rs b/src/ai/approach_ai_system.rs index c891675..42281b7 100644 --- a/src/ai/approach_ai_system.rs +++ b/src/ai/approach_ai_system.rs @@ -1,8 +1,8 @@ use ::rltk::a_star_search; use ::specs::prelude::*; -use crate::components::{EntityMoved, MyTurn, Position, Viewshed, WantsToApproach}; -use crate::{spatial, Map}; +use crate::components::{ApplyMove, MyTurn, Position, WantsToApproach}; +use crate::Map; pub struct ApproachAI {} @@ -11,33 +11,18 @@ impl<'a> System<'a> for ApproachAI { type SystemData = ( WriteStorage<'a, MyTurn>, WriteStorage<'a, WantsToApproach>, - WriteStorage<'a, Position>, + ReadStorage<'a, Position>, ReadExpect<'a, Map>, - WriteStorage<'a, Viewshed>, - WriteStorage<'a, EntityMoved>, Entities<'a>, + WriteStorage<'a, ApplyMove>, ); fn run(&mut self, data: Self::SystemData) { - let ( - mut turns, - mut want_approach, - mut positions, - map, - mut viewsheds, - mut entity_moved, - entities, - ) = data; + let (mut turns, mut want_approach, positions, map, entities, mut apply_move) = data; let mut turn_done: Vec = Vec::new(); - for (entity, mut pos, approach, mut viewshed, _myturn) in ( - &entities, - &mut positions, - &want_approach, - &mut viewsheds, - &turns, - ) - .join() + for (entity, pos, approach, _myturn) in + (&entities, &positions, &want_approach, &turns).join() { turn_done.push(entity); @@ -48,16 +33,14 @@ impl<'a> System<'a> for ApproachAI { ); if path.success && path.steps.len() > 1 { - let idx = map.xy_idx(pos.x, pos.y); - pos.x = path.steps[1] as i32 % map.width; - pos.y = path.steps[1] as i32 / map.width; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert moved marker"); - - let new_idx = map.xy_idx(pos.x, pos.y); - spatial::move_entity(entity, idx, new_idx); - viewshed.dirty = true; + apply_move + .insert( + entity, + ApplyMove { + dest_idx: path.steps[1], + }, + ) + .expect("Unable to insert intent to move."); } } diff --git a/src/ai/chase_ai_system.rs b/src/ai/chase_ai_system.rs index 376ddab..2650beb 100644 --- a/src/ai/chase_ai_system.rs +++ b/src/ai/chase_ai_system.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use ::specs::prelude::*; -use crate::components::{Chasing, EntityMoved, MyTurn, Position, Viewshed}; -use crate::{spatial, Map}; +use crate::components::{ApplyMove, Chasing, MyTurn, Position}; +use crate::Map; pub struct ChaseAI {} @@ -12,16 +12,14 @@ impl<'a> System<'a> for ChaseAI { type SystemData = ( WriteStorage<'a, MyTurn>, WriteStorage<'a, Chasing>, - WriteStorage<'a, Position>, + ReadStorage<'a, Position>, ReadExpect<'a, Map>, - WriteStorage<'a, Viewshed>, - WriteStorage<'a, EntityMoved>, Entities<'a>, + WriteStorage<'a, ApplyMove>, ); fn run(&mut self, data: Self::SystemData) { - let (mut turns, mut chasing, mut positions, map, mut viewsheds, mut entity_moved, entities) = - data; + let (mut turns, mut chasing, positions, map, entities, mut apply_move) = data; let mut targets: HashMap = HashMap::new(); let mut end_chase: Vec = Vec::new(); @@ -39,9 +37,7 @@ impl<'a> System<'a> for ChaseAI { end_chase.clear(); let mut turn_done: Vec = Vec::new(); - for (entity, mut pos, _chase, mut viewshed, _myturn) in - (&entities, &mut positions, &chasing, &mut viewsheds, &turns).join() - { + for (entity, pos, _chase, _myturn) in (&entities, &positions, &chasing, &turns).join() { turn_done.push(entity); let target_pos = targets[&entity]; let path = ::rltk::a_star_search( @@ -51,16 +47,14 @@ impl<'a> System<'a> for ChaseAI { ); if path.success && path.steps.len() > 1 && path.steps.len() < 15 { - let idx = map.xy_idx(pos.x, pos.y); - pos.x = path.steps[1] as i32 % map.width; - pos.y = path.steps[1] as i32 / map.width; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert movement marker"); - - let new_idx = map.xy_idx(pos.x, pos.y); - viewshed.dirty = true; - spatial::move_entity(entity, idx, new_idx); + apply_move + .insert( + entity, + ApplyMove { + dest_idx: path.steps[1], + }, + ) + .expect("Unable to insert intent to move."); turn_done.push(entity); } else { end_chase.push(entity); diff --git a/src/ai/default_move_system.rs b/src/ai/default_move_system.rs index 8f39725..851acb4 100644 --- a/src/ai/default_move_system.rs +++ b/src/ai/default_move_system.rs @@ -1,7 +1,7 @@ use ::rltk::RandomNumberGenerator; use ::specs::prelude::*; -use crate::components::{EntityMoved, MoveMode, Movement, MyTurn, Position, Viewshed}; +use crate::components::{ApplyMove, MoveMode, Movement, MyTurn, Position}; use crate::{spatial, tile_walkable, Map}; pub struct DefaultMoveAI {} @@ -11,35 +11,19 @@ impl<'a> System<'a> for DefaultMoveAI { type SystemData = ( WriteStorage<'a, MyTurn>, WriteStorage<'a, MoveMode>, - WriteStorage<'a, Position>, + ReadStorage<'a, Position>, ReadExpect<'a, Map>, - WriteStorage<'a, Viewshed>, - WriteStorage<'a, EntityMoved>, WriteExpect<'a, RandomNumberGenerator>, + WriteStorage<'a, ApplyMove>, Entities<'a>, ); fn run(&mut self, data: Self::SystemData) { - let ( - mut turns, - mut move_mode, - mut positions, - map, - mut viewsheds, - mut entity_moved, - mut rng, - entities, - ) = data; + let (mut turns, mut move_mode, positions, map, mut rng, mut apply_move, entities) = data; let mut turn_done: Vec = Vec::new(); - for (entity, mut pos, mut mode, mut viewshed, _myturn) in ( - &entities, - &mut positions, - &mut move_mode, - &mut viewsheds, - &turns, - ) - .join() + for (entity, pos, mut mode, _myturn) in + (&entities, &positions, &mut move_mode, &turns).join() { turn_done.push(entity); @@ -60,33 +44,23 @@ impl<'a> System<'a> for DefaultMoveAI { if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 { let dest_idx = map.xy_idx(x, y); if !spatial::is_blocked(dest_idx) { - let idx = map.xy_idx(pos.x, pos.y); - pos.x = x; - pos.y = y; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert movement marker"); - crate::spatial::move_entity(entity, idx, dest_idx); - viewshed.dirty = true; + apply_move + .insert(entity, ApplyMove { dest_idx }) + .expect("Unable to insert intent to move."); + turn_done.push(entity); } } } Movement::RandomWaypoint { path } => { if let Some(path) = path { // We have a target - go there - let idx = map.xy_idx(pos.x, pos.y); if path.len() > 1 { if !spatial::is_blocked(path[1]) { - pos.x = (path[1] as i32 % map.width) as i32; - pos.y = (path[1] as i32 / map.width) as i32; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert movement marker"); - - let new_idx = map.xy_idx(pos.x, pos.y); - spatial::move_entity(entity, idx, new_idx); - viewshed.dirty = true; + apply_move + .insert(entity, ApplyMove { dest_idx: path[1] }) + .expect("Unable to insert intent to move."); path.remove(0); // Remove the first step in the path + turn_done.push(entity); } } else { // Otherwise we wait a turn to see if the path clears up diff --git a/src/ai/flee_ai_system.rs b/src/ai/flee_ai_system.rs index 592d27b..21899ff 100644 --- a/src/ai/flee_ai_system.rs +++ b/src/ai/flee_ai_system.rs @@ -1,7 +1,7 @@ use ::rltk::DijkstraMap; use ::specs::prelude::*; -use crate::components::{EntityMoved, MyTurn, Position, Viewshed, WantsToFlee}; +use crate::components::{ApplyMove, MyTurn, Position, WantsToFlee}; use crate::{spatial, Map}; pub struct FleeAI {} @@ -13,32 +13,15 @@ impl<'a> System<'a> for FleeAI { WriteStorage<'a, WantsToFlee>, WriteStorage<'a, Position>, WriteExpect<'a, Map>, - WriteStorage<'a, Viewshed>, - WriteStorage<'a, EntityMoved>, Entities<'a>, + WriteStorage<'a, ApplyMove>, ); fn run(&mut self, data: Self::SystemData) { - let ( - mut turns, - mut want_flee, - mut positions, - mut map, - mut viewsheds, - mut entity_moved, - entities, - ) = data; + let (mut turns, mut want_flee, positions, mut map, entities, mut apply_move) = data; let mut turn_done: Vec = Vec::new(); - for (entity, mut pos, flee, mut viewshed, _myturn) in ( - &entities, - &mut positions, - &want_flee, - &mut viewsheds, - &turns, - ) - .join() - { + for (entity, pos, flee, _myturn) in (&entities, &positions, &want_flee, &turns).join() { turn_done.push(entity); let my_idx = map.xy_idx(pos.x, pos.y); @@ -47,13 +30,15 @@ impl<'a> System<'a> for FleeAI { let flee_map = DijkstraMap::new(map.width, map.height, &flee.indices, &*map, 100.0); if let Some(flee_target) = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map) { if !spatial::is_blocked(flee_target) { - spatial::move_entity(entity, my_idx, flee_target); - viewshed.dirty = true; - pos.x = flee_target as i32 % map.width; - pos.y = flee_target as i32 / map.width; - entity_moved - .insert(entity, EntityMoved {}) - .expect("Unable to insert intent to flee"); + apply_move + .insert( + entity, + ApplyMove { + dest_idx: flee_target, + }, + ) + .expect("Unable to insert intention to flee"); + turn_done.push(entity); } } } diff --git a/src/components.rs b/src/components.rs index 0eeb238..02288c1 100644 --- a/src/components.rs +++ b/src/components.rs @@ -354,6 +354,26 @@ pub struct Vendor { pub categories: Vec, } +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct TeleportTo { + pub x: i32, + pub y: i32, + pub depth: i32, + pub player_only: bool, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct ApplyMove { + pub dest_idx: usize, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct ApplyTeleport { + pub dest_x: i32, + pub dest_y: i32, + pub dest_depth: i32, +} + // Serialization helper code. We need to implement ConvertSaveLoad for each type that contains an // Entity. diff --git a/src/components/tags.rs b/src/components/tags.rs index b7b6281..07b7174 100644 --- a/src/components/tags.rs +++ b/src/components/tags.rs @@ -39,3 +39,6 @@ pub struct MyTurn {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct EquipmentChanged {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct TownPortal {} diff --git a/src/inventory_system.rs b/src/inventory_system.rs index bed9f7a..dfc0783 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -84,6 +84,7 @@ impl<'a> System<'a> for ItemUseSystem { ReadStorage<'a, MagicMapper>, WriteExpect<'a, RunState>, WriteStorage<'a, EquipmentChanged>, + ReadStorage<'a, TownPortal>, ); #[allow(clippy::cognitive_complexity)] @@ -112,6 +113,7 @@ impl<'a> System<'a> for ItemUseSystem { magic_mapper, mut runstate, mut dirty, + town_portal, ) = data; for (entity, useitem) in (&entities, &wants_use).join() { @@ -236,6 +238,18 @@ impl<'a> System<'a> for ItemUseSystem { } } + // If it's a town portal... + if town_portal.get(useitem.item).is_some() { + if map.depth == 1 { + gamelog.append("You are already in town, so the scroll does nothing"); + } else { + used_item = true; + gamelog.append("You are teleported back to town!"); + + *runstate = RunState::TownPortal; + } + } + // If the item heals, apply the healing match healing.get(useitem.item) { None => {} diff --git a/src/main.rs b/src/main.rs index fcbb661..9ac699c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod map; pub mod map_builders; mod map_indexing_system; mod melee_combat_system; +mod movement_system; mod particle_system; mod player; pub mod random_table; @@ -41,6 +42,7 @@ use lighting_system::LightingSystem; pub use map::*; use map_indexing_system::MapIndexingSystem; use melee_combat_system::MeleeCombatSystem; +use movement_system::MovementSystem; use particle_system::ParticleSpawnSystem; use player::*; use raws::*; @@ -84,6 +86,7 @@ pub enum RunState { SaveGame, NextLevel, PreviousLevel, + TownPortal, ShowRemoveItem, GameOver, MagicMapReveal { @@ -155,6 +158,9 @@ impl State { 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); @@ -244,6 +250,7 @@ impl GameState for State { newrunstate = match *self.ecs.fetch::() { RunState::AwaitingInput => RunState::AwaitingInput, RunState::MagicMapReveal { .. } => RunState::MagicMapReveal { row: 0 }, + RunState::TownPortal => RunState::TownPortal, _ => RunState::Ticking, }; } @@ -483,6 +490,18 @@ impl GameState for State { } } } + 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; + } } { @@ -556,6 +575,8 @@ fn main() -> ::rltk::BError { register!( gs <- + ApplyMove, + ApplyTeleport, AreaOfEffect, Attributes, BlocksTile, @@ -600,6 +621,8 @@ fn main() -> ::rltk::BError { SingleActivation, Skills, SufferDamage, + TeleportTo, + TownPortal, Vendor, Viewshed, WantsToApproach, diff --git a/src/map_builders/limestone_cavern.rs b/src/map_builders/limestone_cavern.rs index 983566f..eb889f8 100644 --- a/src/map_builders/limestone_cavern.rs +++ b/src/map_builders/limestone_cavern.rs @@ -1,10 +1,11 @@ use ::rltk::RandomNumberGenerator; +use super::prefab_builder::prefab_sections; use super::{ - prefab_builder::prefab_sections, AreaEndingPosition, AreaStartingPosition, BspDungeonBuilder, - BuilderChain, BuilderMap, CellularAutomataBuilder, CullUnreachable, DLABuilder, DistantExit, - DrunkardsWalkBuilder, MetaMapBuilder, NearestCorridors, PrefabBuilder, RoomBasedSpawner, - RoomDrawer, RoomExploder, RoomSort, RoomSorter, VoronoiSpawning, XEnd, XStart, YEnd, YStart, + AreaEndingPosition, AreaStartingPosition, BspDungeonBuilder, BuilderChain, BuilderMap, + CellularAutomataBuilder, CullUnreachable, DLABuilder, DistantExit, DrunkardsWalkBuilder, + MetaMapBuilder, NearestCorridors, PrefabBuilder, RoomBasedSpawner, RoomDrawer, RoomExploder, + RoomSort, RoomSorter, VoronoiSpawning, XEnd, XStart, YEnd, YStart, }; use crate::map::TileType; diff --git a/src/movement_system.rs b/src/movement_system.rs new file mode 100644 index 0000000..4ce7b5c --- /dev/null +++ b/src/movement_system.rs @@ -0,0 +1,89 @@ +use ::specs::prelude::*; + +use crate::components::{ + ApplyMove, ApplyTeleport, BlocksTile, EntityMoved, OtherLevelPosition, Position, Viewshed, +}; +use crate::{spatial, Map}; + +pub struct MovementSystem {} + +impl<'a> System<'a> for MovementSystem { + #[allow(clippy::type_complexity)] + type SystemData = ( + WriteExpect<'a, Map>, + WriteStorage<'a, Position>, + ReadStorage<'a, BlocksTile>, + Entities<'a>, + WriteStorage<'a, ApplyMove>, + WriteStorage<'a, ApplyTeleport>, + WriteStorage<'a, OtherLevelPosition>, + WriteStorage<'a, EntityMoved>, + WriteStorage<'a, Viewshed>, + ReadExpect<'a, Entity>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut map, + mut position, + blockers, + entities, + mut apply_move, + mut apply_teleport, + mut other_level, + mut moved, + mut viewsheds, + player_entity, + ) = data; + + // Apply teleports + for (entity, teleport) in (&entities, &apply_teleport).join() { + if teleport.dest_depth == map.depth { + apply_move + .insert( + entity, + ApplyMove { + dest_idx: map.xy_idx(teleport.dest_x, teleport.dest_y), + }, + ) + .expect("Unable to insert intent to teleport"); + } else if entity == *player_entity { + // it's the player - we have a mess + ::rltk::console::log(format!("Not implemented yet.")) + } else if let Some(pos) = position.get(entity) { + let idx = map.xy_idx(pos.x, pos.y); + let dest_idx = map.xy_idx(teleport.dest_x, teleport.dest_y); + spatial::move_entity(entity, idx, dest_idx); + other_level + .insert( + entity, + OtherLevelPosition { + x: teleport.dest_x, + y: teleport.dest_y, + depth: teleport.dest_depth, + }, + ) + .expect("Unable to insert intent to teleport."); + + position.remove(entity); + } + } + apply_teleport.clear(); + + // Apply broad movement + for (entity, movement, mut pos) in (&entities, &apply_move, &mut position).join() { + let start_idx = map.xy_idx(pos.x, pos.y); + let dest_idx = movement.dest_idx as usize; + spatial::move_entity(entity, start_idx, dest_idx); + pos.x = movement.dest_idx as i32 % map.width; + pos.y = movement.dest_idx as i32 / map.width; + if let Some(vs) = viewsheds.get_mut(entity) { + vs.dirty = true; + } + moved + .insert(entity, EntityMoved {}) + .expect("Unable to insert moved marker"); + } + apply_move.clear(); + } +} diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index d21bc8e..c1ae8f0 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -285,6 +285,7 @@ pub fn spawn_named_item( }) } "magic_mapping" => eb = eb.with(MagicMapper {}), + "town_portal" => eb = eb.with(TownPortal {}), "food" => eb = eb.with(ProvidesFood {}), _ => { rltk::console::log(format!( diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 04ed736..7ee3183 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -61,6 +61,8 @@ pub fn save_game(ecs: &mut World) { ecs, serializer, data, + ApplyMove, + ApplyTeleport, AreaOfEffect, Attributes, BlocksTile, @@ -104,6 +106,8 @@ pub fn save_game(ecs: &mut World) { SingleActivation, Skills, SufferDamage, + TeleportTo, + TownPortal, Vendor, Viewshed, WantsToApproach, @@ -170,6 +174,8 @@ pub fn load_game(ecs: &mut World) { ecs, de, d, + ApplyMove, + ApplyTeleport, AreaOfEffect, Attributes, BlocksTile, @@ -213,6 +219,8 @@ pub fn load_game(ecs: &mut World) { SingleActivation, Skills, SufferDamage, + TeleportTo, + TownPortal, Vendor, Viewshed, WantsToApproach, diff --git a/src/spawner.rs b/src/spawner.rs index 4add02d..e7bf94e 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ::rltk::RandomNumberGenerator; +use ::rltk::{Point, RandomNumberGenerator}; use ::specs::prelude::*; use ::specs::saveload::{MarkedBuilder, SimpleMarker}; @@ -8,7 +8,7 @@ use crate::components::*; use crate::gamesystem::{mana_at_level, player_hp_at_level}; use crate::random_table::RandomTable; use crate::raws::{get_spawn_table_for_depth, spawn_named_entity, SpawnType, RAWS}; -use crate::{colors, Map, Rect, TileType}; +use crate::{colors, Map, MasterDungeonMap, Rect, TileType}; /// Spawns the player and returns their entity object pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { @@ -95,6 +95,12 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { "Old Boots", SpawnType::Equipped { by: player }, ); + spawn_named_entity( + &RAWS.lock().unwrap(), + ecs, + "Town Portal Scroll", + SpawnType::Carried { by: player }, + ); player } @@ -195,3 +201,52 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { spawn.1 )); } + +pub fn spawn_town_portal(ecs: &mut World) { + // Get current position & depth + let map = ecs.fetch::(); + let player_depth = map.depth; + let player_pos = ecs.fetch::(); + let player_x = player_pos.x; + let player_y = player_pos.y; + std::mem::drop(player_pos); + std::mem::drop(map); + + // Find part of the town for the portal + let dm = ecs.fetch::(); + let town_map = dm.get_map(1).unwrap(); + let mut stairs_idx = 0; + for (idx, tt) in town_map.tiles.iter().enumerate() { + if *tt == TileType::DownStairs { + stairs_idx = idx; + } + } + let portal_x = (stairs_idx as i32 % town_map.width) - 2; + let portal_y = stairs_idx as i32 / town_map.width; + + std::mem::drop(dm); + + // Spawn the portal itself + ecs.create_entity() + .with(OtherLevelPosition { + x: portal_x, + y: portal_y, + depth: 1, + }) + .with(Renderable { + glyph: ::rltk::to_cp437('♥'), + fg: colors::CYAN, + bg: colors::BLACK, + render_order: 0, + }) + .with(EntryTrigger {}) + .with(TeleportTo { + x: player_x, + y: player_y, + depth: player_depth, + player_only: true, + }) + .with(Name::from("Town Portal")) + .with(SingleActivation {}) + .build(); +}