1
0
Fork 0

Refactor movement system, partially implement town portals

This commit is contained in:
Timothy Warren 2022-01-18 11:00:13 -05:00
parent 9132b6ae2f
commit 878ee5b480
13 changed files with 276 additions and 126 deletions

View File

@ -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<Entity> = 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.");
}
}

View File

@ -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<Entity, (i32, i32)> = HashMap::new();
let mut end_chase: Vec<Entity> = Vec::new();
@ -39,9 +37,7 @@ impl<'a> System<'a> for ChaseAI {
end_chase.clear();
let mut turn_done: Vec<Entity> = 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);

View File

@ -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<Entity> = 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

View File

@ -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<Entity> = 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);
}
}
}

View File

@ -354,6 +354,26 @@ pub struct Vendor {
pub categories: Vec<String>,
}
#[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.

View File

@ -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 {}

View File

@ -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 => {}

View File

@ -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>() {
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::<Map>().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,

View File

@ -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;

89
src/movement_system.rs Normal file
View File

@ -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();
}
}

View File

@ -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!(

View File

@ -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,

View File

@ -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::<Map>();
let player_depth = map.depth;
let player_pos = ecs.fetch::<Point>();
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::<MasterDungeonMap>();
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();
}