From d439ff6d3fb631dd820ba94f39caed8a245d818a Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Wed, 16 Aug 2023 01:17:38 +0100 Subject: [PATCH] complete spatial indexing refactor - SpatialMap --- src/ai/adjacent_ai_system.rs | 8 +- src/ai/approach_ai_system.rs | 7 +- src/ai/chase_ai_system.rs | 7 +- src/ai/default_move_system.rs | 12 +- src/ai/flee_ai_system.rs | 5 +- src/ai/visible_ai_system.rs | 29 ++-- src/gui/mod.rs | 18 +- src/inventory_system.rs | 8 +- src/main.rs | 1 + src/map/dungeon.rs | 1 - src/map/interval_spawning_system.rs | 2 +- src/map/mod.rs | 18 +- src/map_indexing_system.rs | 15 +- src/player.rs | 256 ++++++++++++++-------------- src/saveload_system.rs | 2 +- src/spatial/mod.rs | 149 ++++++++++++++++ src/trigger_system.rs | 20 +-- src/visibility_system.rs | 10 +- 18 files changed, 351 insertions(+), 217 deletions(-) create mode 100644 src/spatial/mod.rs diff --git a/src/ai/adjacent_ai_system.rs b/src/ai/adjacent_ai_system.rs index 5e27e0d..b06c58d 100644 --- a/src/ai/adjacent_ai_system.rs +++ b/src/ai/adjacent_ai_system.rs @@ -79,12 +79,12 @@ fn evaluate( this_faction: &str, reactions: &mut Vec<(Entity, Reaction)>, ) { - for other_entity in map.tile_content[idx].iter() { - if let Some(faction) = factions.get(*other_entity) { + crate::spatial::for_each_tile_content(idx, |other_entity| { + if let Some(faction) = factions.get(other_entity) { reactions.push(( - *other_entity, + other_entity, crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), )); } - } + }); } diff --git a/src/ai/approach_ai_system.rs b/src/ai/approach_ai_system.rs index 2b54286..33bc9ec 100644 --- a/src/ai/approach_ai_system.rs +++ b/src/ai/approach_ai_system.rs @@ -39,13 +39,12 @@ impl<'a> System<'a> for ApproachAI { &mut *map, ); if path.success && path.steps.len() > 1 { - let mut idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = false; + 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 EntityMoved"); - idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = true; + let new_idx = map.xy_idx(pos.x, pos.y); + crate::spatial::move_entity(entity, idx, new_idx); viewshed.dirty = true; if let Some(telepath) = telepaths.get_mut(entity) { telepath.dirty = true; diff --git a/src/ai/chase_ai_system.rs b/src/ai/chase_ai_system.rs index 93f586f..bf264b2 100644 --- a/src/ai/chase_ai_system.rs +++ b/src/ai/chase_ai_system.rs @@ -59,13 +59,12 @@ impl<'a> System<'a> for ChaseAI { &mut *map, ); if path.success && path.steps.len() > 1 && path.steps.len() < MAX_CHASE_DISTANCE { - let mut idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = false; + 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("Failed to insert EntityMoved"); - idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = true; + let new_idx = map.xy_idx(pos.x, pos.y); + crate::spatial::move_entity(entity, idx, new_idx); viewshed.dirty = true; if let Some(is_telepath) = telepaths.get_mut(entity) { is_telepath.dirty = true; diff --git a/src/ai/default_move_system.rs b/src/ai/default_move_system.rs index 4100834..deca1f1 100644 --- a/src/ai/default_move_system.rs +++ b/src/ai/default_move_system.rs @@ -69,13 +69,12 @@ impl<'a> System<'a> for DefaultAI { if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 { let dest_idx = map.xy_idx(x, y); - if !map.blocked[dest_idx] { + if !crate::spatial::is_blocked(dest_idx) { let idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = false; pos.x = x; pos.y = y; entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved"); - map.blocked[dest_idx] = true; + crate::spatial::move_entity(entity, idx, dest_idx); viewshed.dirty = true; if let Some(is_telepath) = telepaths.get_mut(entity) { is_telepath.dirty = true; @@ -88,13 +87,12 @@ impl<'a> System<'a> for DefaultAI { // We have a path - follow it let mut idx = map.xy_idx(pos.x, pos.y); if path.len() > 1 { - if !map.blocked[path[1] as usize] { - map.blocked[idx] = false; + if !crate::spatial::is_blocked(path[1] as usize) { pos.x = path[1] as i32 % map.width; pos.y = path[1] as i32 / map.width; entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert EntityMoved"); - idx = map.xy_idx(pos.x, pos.y); - map.blocked[idx] = true; + let new_idx = map.xy_idx(pos.x, pos.y); + crate::spatial::move_entity(entity, idx, new_idx); viewshed.dirty = true; if let Some(is_telepath) = telepaths.get_mut(entity) { is_telepath.dirty = true; diff --git a/src/ai/flee_ai_system.rs b/src/ai/flee_ai_system.rs index 21813cf..10c34a3 100644 --- a/src/ai/flee_ai_system.rs +++ b/src/ai/flee_ai_system.rs @@ -38,9 +38,8 @@ impl<'a> System<'a> for FleeAI { let flee_map = DijkstraMap::new(map.width as usize, map.height as usize, &fleeing.indices, &*map, 100.0); let flee_target = DijkstraMap::find_highest_exit(&flee_map, my_idx, &*map); if let Some(flee_target) = flee_target { - if !map.blocked[flee_target as usize] { - map.blocked[my_idx] = false; - map.blocked[flee_target as usize] = true; + if !crate::spatial::is_blocked(flee_target as usize) { + crate::spatial::move_entity(entity, my_idx, flee_target); viewshed.dirty = true; if let Some(is_telepath) = telepaths.get_mut(entity) { is_telepath.dirty = true; diff --git a/src/ai/visible_ai_system.rs b/src/ai/visible_ai_system.rs index 5f83e83..0d2b762 100644 --- a/src/ai/visible_ai_system.rs +++ b/src/ai/visible_ai_system.rs @@ -96,20 +96,27 @@ fn evaluate( reactions: &mut Vec<(usize, Reaction, Entity)>, minds: Option<&ReadStorage>, ) { - for other_entity in map.tile_content[idx].iter() { + crate::spatial::for_each_tile_content(idx, |other_entity| { // If minds are passed, we assume we're using telepathy here, // so if the other entity is mindless, we skip it. if minds.is_some() { - if minds.unwrap().get(*other_entity).is_none() { - continue; + if minds.unwrap().get(other_entity).is_some() { + if let Some(faction) = factions.get(other_entity) { + reactions.push(( + idx, + crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), + other_entity, + )); + } + } + } else { + if let Some(faction) = factions.get(other_entity) { + reactions.push(( + idx, + crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), + other_entity, + )); } } - if let Some(faction) = factions.get(*other_entity) { - reactions.push(( - idx, - crate::raws::faction_reaction(this_faction, &faction.name, &crate::raws::RAWS.lock().unwrap()), - *other_entity, - )); - } - } + }); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index be20daf..b6b52f2 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -200,36 +200,36 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { let mut seen_entities: Vec<(String, RGB, RGB, u16)> = Vec::new(); for tile in viewshed.visible_tiles.iter() { let idx = map.xy_idx(tile.x, tile.y); - for entity in map.tile_content[idx].iter() { + crate::spatial::for_each_tile_content(idx, |entity| { let mut draw = false; - if let Some(_) = names.get(*entity) { + if let Some(_) = names.get(entity) { draw = true; } - let prop = props.get(*entity); + let prop = props.get(entity); if let Some(_) = prop { draw = false; } - let is_hidden = hidden.get(*entity); + let is_hidden = hidden.get(entity); if let Some(_) = is_hidden { draw = false; } - if entity == &*player_entity { + if entity == *player_entity { draw = false; } if draw { - let (render_fg, glyph) = if let Some(renderable) = renderables.get(*entity) { + let (render_fg, glyph) = if let Some(renderable) = renderables.get(entity) { (renderable.fg, renderable.glyph) } else { (RGB::named(rltk::WHITE), rltk::to_cp437('-')) }; seen_entities.push(( - get_item_display_name(ecs, *entity).0, - get_item_colour(ecs, *entity), + get_item_display_name(ecs, entity).0, + get_item_colour(ecs, entity), render_fg, glyph, )); } - } + }); } seen_entities.sort_by(|a, b| b.0.cmp(&a.0)); diff --git a/src/inventory_system.rs b/src/inventory_system.rs index adb555d..235dc12 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -188,9 +188,7 @@ impl<'a> System<'a> for ItemUseSystem { // Single target in a tile let idx = map.xy_idx(target.x, target.y); target_idxs.push(idx); - for mob in map.tile_content[idx].iter() { - targets.push(*mob); - } + crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); } Some(area_effect) => { // If item with a targeted AOE is cursed, get the position @@ -216,9 +214,7 @@ impl<'a> System<'a> for ItemUseSystem { for tile_idx in blast_tiles.iter() { let idx = map.xy_idx(tile_idx.x, tile_idx.y); target_idxs.push(idx); - for mob in map.tile_content[idx].iter() { - targets.push(*mob); - } + crate::spatial::for_each_tile_content(idx, |mob| targets.push(mob)); particle_builder.request( tile_idx.x, tile_idx.y, diff --git a/src/main.rs b/src/main.rs index f13c472..8c4e882 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ mod ai; mod gamesystem; mod random_table; mod rex_assets; +mod spatial; #[macro_use] extern crate lazy_static; diff --git a/src/map/dungeon.rs b/src/map/dungeon.rs index 5968ae7..6e3e0e9 100644 --- a/src/map/dungeon.rs +++ b/src/map/dungeon.rs @@ -51,7 +51,6 @@ impl MasterDungeonMap { pub fn get_map(&self, id: i32) -> Option { if self.maps.contains_key(&id) { let mut result = self.maps[&id].clone(); - result.tile_content = vec![Vec::new(); (result.width * result.height) as usize]; return Some(result); } else { return None; diff --git a/src/map/interval_spawning_system.rs b/src/map/interval_spawning_system.rs index 3ff2e36..bea5ea8 100644 --- a/src/map/interval_spawning_system.rs +++ b/src/map/interval_spawning_system.rs @@ -75,7 +75,7 @@ fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) { fn populate_unblocked_nonvisible(map: &Map) -> Vec { let mut tiles: Vec = Vec::new(); for (i, _tile) in map.tiles.iter().enumerate() { - if !map.blocked[i] && !map.visible_tiles[i] { + if !crate::spatial::is_blocked(i) && !map.visible_tiles[i] { tiles.push(i); } } diff --git a/src/map/mod.rs b/src/map/mod.rs index f4f66f2..b943988 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -28,16 +28,11 @@ pub struct Map { // Combine these offsets into one Vec<(u8, u8, u8)> pub colour_offset: Vec<(f32, f32, f32)>, pub additional_fg_offset: rltk::RGB, - pub blocked: Vec, pub id: i32, pub name: String, pub difficulty: i32, pub bloodstains: HashSet, pub view_blocked: HashSet, - - #[serde(skip_serializing)] - #[serde(skip_deserializing)] - pub tile_content: Vec>, } impl Map { @@ -47,6 +42,7 @@ impl Map { pub fn new(new_id: i32, width: i32, height: i32, difficulty: i32, name: S) -> Map { let map_tile_count = (width * height) as usize; + crate::spatial::set_size(map_tile_count); let mut map = Map { tiles: vec![TileType::Wall; map_tile_count], width: width, @@ -57,13 +53,11 @@ impl Map { telepath_tiles: vec![false; map_tile_count], colour_offset: vec![(1.0, 1.0, 1.0); map_tile_count], additional_fg_offset: rltk::RGB::from_u8(OFFSET_PERCENT as u8, OFFSET_PERCENT as u8, OFFSET_PERCENT as u8), - blocked: vec![false; map_tile_count], id: new_id, name: name.to_string(), difficulty: difficulty, bloodstains: HashSet::new(), view_blocked: HashSet::new(), - tile_content: vec![Vec::new(); map_tile_count], }; const OFFSET_PERCENT: i32 = 10; @@ -86,19 +80,15 @@ impl Map { return false; } let idx = self.xy_idx(x, y); - !self.blocked[idx] + return !crate::spatial::is_blocked(idx); } pub fn populate_blocked(&mut self) { - for (i, tile) in self.tiles.iter_mut().enumerate() { - self.blocked[i] = !tile_walkable(*tile); - } + crate::spatial::populate_blocked_from_map(self); } pub fn clear_content_index(&mut self) { - for content in self.tile_content.iter_mut() { - content.clear(); - } + crate::spatial::clear(); } } diff --git a/src/map_indexing_system.rs b/src/map_indexing_system.rs index f49b28c..d4ddf44 100644 --- a/src/map_indexing_system.rs +++ b/src/map_indexing_system.rs @@ -1,4 +1,4 @@ -use super::{BlocksTile, Map, Position}; +use super::{spatial, BlocksTile, Map, Position}; use specs::prelude::*; pub struct MapIndexingSystem {} @@ -9,18 +9,11 @@ impl<'a> System<'a> for MapIndexingSystem { fn run(&mut self, data: Self::SystemData) { let (mut map, position, blockers, entities) = data; - map.populate_blocked(); - map.clear_content_index(); + spatial::clear(); + spatial::populate_blocked_from_map(&*map); for (entity, position) in (&entities, &position).join() { let idx = map.xy_idx(position.x, position.y); - - let _p: Option<&BlocksTile> = blockers.get(entity); - if let Some(_p) = _p { - map.blocked[idx] = true; - } - - // Push the entity to the appropriate index slot. - map.tile_content[idx].push(entity); + spatial::index_entity(entity, idx, blockers.get(entity).is_some()); } } } diff --git a/src/player.rs b/src/player.rs index 017f5b4..70b1a77 100644 --- a/src/player.rs +++ b/src/player.rs @@ -36,31 +36,35 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); - if map.tile_content[destination_idx].len() == 0 { + if !crate::spatial::has_tile_content(destination_idx) { gamelog::Logger::new().append("You see no door there.").log(); } - for potential_target in map.tile_content[destination_idx].iter() { - let door = doors.get_mut(*potential_target); + let mut multiple_tile_content = false; + if crate::spatial::length(destination_idx) > 1 { + multiple_tile_content = true; + } + crate::spatial::for_each_tile_content(destination_idx, |potential_target| { + let door = doors.get_mut(potential_target); if let Some(door) = door { if door.open == true { - if map.tile_content[destination_idx].len() > 1 { - if let Some(name) = names.get(*potential_target) { + if multiple_tile_content { + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("The").item_name(&name.name).append("is blocked.").log(); } } else if rng.roll_dice(1, 6) + attributes.strength.bonus < 2 { - if let Some(name) = names.get(*potential_target) { + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log(); } } else { door.open = false; blocks_visibility - .insert(*potential_target, BlocksVisibility {}) + .insert(potential_target, BlocksVisibility {}) .expect("Unable to insert BlocksVisibility."); blocks_movement - .insert(*potential_target, BlocksTile {}) + .insert(potential_target, BlocksTile {}) .expect("Unable to insert BlocksTile."); - let render_data = renderables.get_mut(*potential_target).unwrap(); - if let Some(name) = names.get(*potential_target) { + let render_data = renderables.get_mut(potential_target).unwrap(); + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("You close the").item_name_n(&name.name).period().log(); } render_data.glyph = rltk::to_cp437('+'); // Nethack open door, maybe just use '/' instead. @@ -71,7 +75,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { gamelog::Logger::new().append("It's already closed.").log(); } } - } + }); } } @@ -117,23 +121,23 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); - if map.tile_content[destination_idx].len() == 0 { + if !crate::spatial::has_tile_content(destination_idx) { gamelog::Logger::new().append("You see no door there.").log(); } - for potential_target in map.tile_content[destination_idx].iter() { - let door = doors.get_mut(*potential_target); + crate::spatial::for_each_tile_content(destination_idx, |potential_target| { + let door = doors.get_mut(potential_target); if let Some(door) = door { if door.open == false { if rng.roll_dice(1, 6) + attributes.strength.bonus < 2 { - if let Some(name) = names.get(*potential_target) { + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log(); } } else { door.open = true; - blocks_visibility.remove(*potential_target); - blocks_movement.remove(*potential_target); - let render_data = renderables.get_mut(*potential_target).unwrap(); - if let Some(name) = names.get(*potential_target) { + blocks_visibility.remove(potential_target); + blocks_movement.remove(potential_target); + let render_data = renderables.get_mut(potential_target).unwrap(); + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("You open the").item_name_n(&name.name).period().log(); } render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. @@ -144,7 +148,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { gamelog::Logger::new().append("It's already open.").log(); } } - } + }); } } @@ -187,7 +191,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { { let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); - if map.tile_content[destination_idx].len() == 0 { + if !crate::spatial::has_tile_content(destination_idx) { if rng.roll_dice(1, 20) == 20 { let mut suffer_damage = ecs.write_storage::(); SufferDamage::new_damage(&mut suffer_damage, entity, 1, false); @@ -201,13 +205,13 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { } else { let mut last_non_door_target: Option = None; let mut target_name = "thing"; - for potential_target in map.tile_content[destination_idx].iter() { - if let Some(name) = names.get(*potential_target) { + crate::spatial::for_each_tile_content_with_bool(destination_idx, |potential_target| { + if let Some(name) = names.get(potential_target) { target_name = &name.name; } // If it's a door, - let door = doors.get_mut(*potential_target); + let door = doors.get_mut(potential_target); if let Some(door) = door { // If the door is closed, if door.open == false { @@ -220,10 +224,10 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { .item_name_n(target_name) .append(", it crashes open!") .log(); - something_was_destroyed = Some(*potential_target); + something_was_destroyed = Some(potential_target); destroyed_pos = Some(Point::new(pos.x + delta_x, pos.y + delta_y)); gamelog::record_event("broken_doors", 1); - break; + return false; // 66% chance of just kicking it. } else { gamelog::Logger::new() @@ -231,18 +235,19 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { .item_name_n(target_name) .period() .log(); - break; + return false; } // If the door is open and there's nothing else on the tile, - } else if map.tile_content[destination_idx].len() == 1 { + } else if crate::spatial::length(destination_idx) == 1 { // Just kick the air. gamelog::Logger::new().append("You kick the open air.").log(); - break; + return false; } } else { - last_non_door_target = Some(*potential_target); + last_non_door_target = Some(potential_target); } - } + return true; + }); if let Some(_) = last_non_door_target { gamelog::Logger::new().append("You kick the").item_name_n(target_name).period().log(); let mut particle_builder = ecs.write_resource::(); @@ -269,7 +274,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { return RunState::Ticking; } -pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { +pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState { let mut positions = ecs.write_storage::(); let mut players = ecs.write_storage::(); let mut viewsheds = ecs.write_storage::(); @@ -278,12 +283,12 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { let factions = ecs.read_storage::(); let pools = ecs.read_storage::(); let map = ecs.fetch::(); - let entities = ecs.entities(); let mut wants_to_melee = ecs.write_storage::(); let mut doors = ecs.write_storage::(); let names = ecs.read_storage::(); let mut swap_entities: Vec<(Entity, i32, i32)> = Vec::new(); + let mut result = RunState::AwaitingInput; for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { if pos.x + delta_x < 0 @@ -291,14 +296,14 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { || pos.y + delta_y < 0 || pos.y + delta_y > map.height - 1 { - return false; + return result; } let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); - for potential_target in map.tile_content[destination_idx].iter() { + result = crate::spatial::for_each_tile_content_with_runstate(destination_idx, |potential_target| { let mut hostile = true; - if pools.get(*potential_target).is_some() { - if let Some(faction) = factions.get(*potential_target) { + if pools.get(potential_target).is_some() { + if let Some(faction) = factions.get(potential_target) { let reaction = crate::raws::faction_reaction(&faction.name, "player", &crate::raws::RAWS.lock().unwrap()); if reaction != Reaction::Attack { @@ -307,7 +312,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { } } if !hostile { - swap_entities.push((*potential_target, pos.x, pos.y)); + swap_entities.push((potential_target, pos.x, pos.y)); pos.x = min(map.width - 1, max(0, pos.x + delta_x)); pos.y = min(map.height - 1, max(0, pos.y + delta_y)); entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); @@ -316,90 +321,95 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { ppos.x = pos.x; ppos.y = pos.y; } else { - let target = pools.get(*potential_target); + let target = pools.get(potential_target); if let Some(_target) = target { wants_to_melee - .insert(entity, WantsToMelee { target: *potential_target }) + .insert(entity, WantsToMelee { target: potential_target }) .expect("Add target failed."); - return true; + return Some(RunState::Ticking); } } - let door = doors.get_mut(*potential_target); + let door = doors.get_mut(potential_target); if let Some(door) = door { if door.open == false { - if let Some(name) = names.get(*potential_target) { + if let Some(name) = names.get(potential_target) { gamelog::Logger::new().append("The").item_name(&name.name).append("is in your way.").log(); } - return false; + return None; } } + return None; + }); + + if result == RunState::Ticking { + return result; } - if swap_entities.len() > 0 { - for m in swap_entities.iter() { - let their_pos = positions.get_mut(m.0); - if let Some(name) = names.get(m.0) { - gamelog::Logger::new().append("You swap places with the").npc_name_n(&name.name).period().log(); - } - if let Some(their_pos) = their_pos { - their_pos.x = m.1; - their_pos.y = m.2; - } + if swap_entities.len() <= 0 { + if crate::spatial::is_blocked(destination_idx) { + gamelog::Logger::new().append("You can't move there.").log(); + return RunState::AwaitingInput; } - - return true; - } - if map.blocked[destination_idx] { - gamelog::Logger::new().append("You can't move there.").log(); - return false; - } - let hidden = ecs.read_storage::(); - // Push every entity name in the pile to a vector of strings - let mut item_names: Vec = Vec::new(); - let mut some = false; - for entity in map.tile_content[destination_idx].iter() { - if !hidden.get(*entity).is_some() && names.get(*entity).is_some() { - let item_name = get_item_display_name(ecs, *entity).0; - item_names.push(item_name); - some = true; - } - } - // If some names were found, append. Logger = logger is necessary - // makes logger called a mutable self. It's not the most efficient - // but it happens infrequently enough (once per player turn at most) - // that it shouldn't matter. - if some { - let mut logger = gamelog::Logger::new().append("You see a"); - for i in 0..item_names.len() { - if i > 0 && i < item_names.len() { - logger = logger.append(", a"); + let hidden = ecs.read_storage::(); + // Push every entity name in the pile to a vector of strings + let mut item_names: Vec = Vec::new(); + let mut some = false; + crate::spatial::for_each_tile_content(destination_idx, |entity| { + if !hidden.get(entity).is_some() && names.get(entity).is_some() { + let item_name = get_item_display_name(ecs, entity).0; + item_names.push(item_name); + some = true; } - logger = logger.item_name_n(&item_names[i]); + }); + // If some names were found, append. Logger = logger is necessary + // makes logger called a mutable self. It's not the most efficient + // but it happens infrequently enough (once per player turn at most) + // that it shouldn't matter. + if some { + let mut logger = gamelog::Logger::new().append("You see a"); + for i in 0..item_names.len() { + if i > 0 && i < item_names.len() { + logger = logger.append(", a"); + } + logger = logger.item_name_n(&item_names[i]); + } + logger.period().log(); } - logger.period().log(); + let old_idx = map.xy_idx(pos.x, pos.y); + pos.x = min(map.width - 1, max(0, pos.x + delta_x)); + pos.y = min(map.height - 1, max(0, pos.y + delta_y)); + let new_idx = map.xy_idx(pos.x, pos.y); + entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); + crate::spatial::move_entity(entity, old_idx, new_idx); + // Dirty viewsheds, and check only now if telepath viewshed exists + viewshed.dirty = true; + if let Some(telepathy) = telepaths.get_mut(entity) { + telepathy.dirty = true; + } + let mut ppos = ecs.write_resource::(); + ppos.x = pos.x; + ppos.y = pos.y; + return RunState::Ticking; } - pos.x = min(map.width - 1, max(0, pos.x + delta_x)); - pos.y = min(map.height - 1, max(0, pos.y + delta_y)); - - // Dirty viewsheds, and check only now if telepath viewshed exists - viewshed.dirty = true; - - let is_telepath = telepaths.get_mut(entity); - if let Some(telepathy) = is_telepath { - telepathy.dirty = true; - } - let mut ppos = ecs.write_resource::(); - ppos.x = pos.x; - ppos.y = pos.y; - entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker"); - - return true; } - return false; + for m in swap_entities.iter() { + if let Some(name) = names.get(m.0) { + gamelog::Logger::new().append("You swap places with the").npc_name_n(&name.name).period().log(); + } + if let Some(their_pos) = positions.get_mut(m.0) { + let old_idx = map.xy_idx(their_pos.x, their_pos.y); + their_pos.x = m.1; + their_pos.y = m.2; + let new_idx = map.xy_idx(their_pos.x, their_pos.y); + crate::spatial::move_entity(m.0, old_idx, new_idx); + return RunState::Ticking; + } + } + return result; } -fn get_item(ecs: &mut World) -> bool { +fn get_item(ecs: &mut World) -> RunState { let player_pos = ecs.fetch::(); let player_entity = ecs.fetch::(); let entities = ecs.entities(); @@ -416,42 +426,40 @@ fn get_item(ecs: &mut World) -> bool { match target_item { None => { gamelog::Logger::new().append("There is nothing to pick up.").log(); - return false; + return RunState::AwaitingInput; } Some(item) => { let mut pickup = ecs.write_storage::(); pickup .insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item }) .expect("Unable to insert want to pickup item."); - return true; + return RunState::Ticking; } } } pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { - // Player movement - let mut result = false; match ctx.key { None => return RunState::AwaitingInput, Some(key) => match key { // Cardinals VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => { - result = try_move_player(-1, 0, &mut gs.ecs); + return try_move_player(-1, 0, &mut gs.ecs) } VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => { - result = try_move_player(1, 0, &mut gs.ecs); + return try_move_player(1, 0, &mut gs.ecs); } VirtualKeyCode::Up | VirtualKeyCode::Numpad8 | VirtualKeyCode::K => { - result = try_move_player(0, -1, &mut gs.ecs); + return try_move_player(0, -1, &mut gs.ecs); } VirtualKeyCode::Down | VirtualKeyCode::Numpad2 | VirtualKeyCode::J => { - result = try_move_player(0, 1, &mut gs.ecs); + return try_move_player(0, 1, &mut gs.ecs); } // Diagonals - VirtualKeyCode::Numpad9 | VirtualKeyCode::U => result = try_move_player(1, -1, &mut gs.ecs), - VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => result = try_move_player(-1, -1, &mut gs.ecs), - VirtualKeyCode::Numpad3 | VirtualKeyCode::N => result = try_move_player(1, 1, &mut gs.ecs), - VirtualKeyCode::Numpad1 | VirtualKeyCode::B => result = try_move_player(-1, 1, &mut gs.ecs), + VirtualKeyCode::Numpad9 | VirtualKeyCode::U => return try_move_player(1, -1, &mut gs.ecs), + VirtualKeyCode::Numpad7 | VirtualKeyCode::Y => return try_move_player(-1, -1, &mut gs.ecs), + VirtualKeyCode::Numpad3 | VirtualKeyCode::N => return try_move_player(1, 1, &mut gs.ecs), + VirtualKeyCode::Numpad1 | VirtualKeyCode::B => return try_move_player(-1, 1, &mut gs.ecs), // id VirtualKeyCode::Period => { if ctx.shift { @@ -460,7 +468,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { } return RunState::NextLevel; // > to descend } else { - result = skip_turn(&mut gs.ecs); // (Wait a turn) + return skip_turn(&mut gs.ecs); // (Wait a turn) } } VirtualKeyCode::Comma => { @@ -477,7 +485,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { } } VirtualKeyCode::NumpadDecimal => { - result = skip_turn(&mut gs.ecs); + return skip_turn(&mut gs.ecs); } // Items @@ -485,7 +493,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { VirtualKeyCode::O => return RunState::ActionWithDirection { function: open }, VirtualKeyCode::F => return RunState::ActionWithDirection { function: kick }, VirtualKeyCode::G => { - result = get_item(&mut gs.ecs); + return get_item(&mut gs.ecs); } VirtualKeyCode::I => return RunState::ShowInventory, VirtualKeyCode::D => return RunState::ShowDropItem, @@ -498,11 +506,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { } }, } - if result { - return RunState::Ticking; - } else { - return RunState::AwaitingInput; - } + return RunState::AwaitingInput; } pub fn try_next_level(ecs: &mut World) -> bool { @@ -529,7 +533,7 @@ pub fn try_previous_level(ecs: &mut World) -> bool { } } -fn skip_turn(ecs: &mut World) -> bool { +fn skip_turn(ecs: &mut World) -> RunState { let player_entity = ecs.fetch::(); let mut viewsheds = ecs.write_storage::(); let worldmap_resource = ecs.fetch::(); @@ -543,8 +547,8 @@ fn skip_turn(ecs: &mut World) -> bool { let viewshed = viewsheds.get_mut(*player_entity).unwrap(); for tile in viewshed.visible_tiles.iter() { let idx = worldmap_resource.xy_idx(tile.x, tile.y); - for entity_id in worldmap_resource.tile_content[idx].iter() { - let faction = factions.get(*entity_id); + crate::spatial::for_each_tile_content(idx, |entity_id| { + let faction = factions.get(entity_id); match faction { None => {} Some(faction) => { @@ -555,7 +559,7 @@ fn skip_turn(ecs: &mut World) -> bool { } } } - } + }); } // Dirty viewshed (so we search for hidden tiles whenever we wait) viewshed.dirty = true; @@ -583,7 +587,7 @@ fn skip_turn(ecs: &mut World) -> bool { gamelog::Logger::new().append("You wait a turn.").log(); - return true; + return RunState::Ticking; } /* Playing around with autoexplore, without having read how to do it. diff --git a/src/saveload_system.rs b/src/saveload_system.rs index daf1541..0b7d70d 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -247,7 +247,7 @@ pub fn load_game(ecs: &mut World) { for (e, h) in (&entities, &helper).join() { let mut worldmap = ecs.write_resource::(); *worldmap = h.map.clone(); - worldmap.tile_content = vec![Vec::new(); (worldmap.width * worldmap.height) as usize]; + crate::spatial::set_size((worldmap.width * worldmap.height) as usize); deleteme = Some(e); } for (e, h) in (&entities, &helper2).join() { diff --git a/src/spatial/mod.rs b/src/spatial/mod.rs new file mode 100644 index 0000000..5708a8a --- /dev/null +++ b/src/spatial/mod.rs @@ -0,0 +1,149 @@ +use crate::{tile_walkable, Map, RunState}; +use specs::prelude::*; +use std::sync::Mutex; + +struct SpatialMap { + blocked: Vec<(bool, bool)>, + tile_content: Vec>, +} + +impl SpatialMap { + fn new() -> Self { + return Self { blocked: Vec::new(), tile_content: Vec::new() }; + } +} + +lazy_static! { + static ref SPATIAL_MAP: Mutex = Mutex::new(SpatialMap::new()); +} + +/// Sets the size of the SpatialMap. +pub fn set_size(map_tile_count: usize) { + let mut lock = SPATIAL_MAP.lock().unwrap(); + lock.blocked = vec![(false, false); map_tile_count]; + lock.tile_content = vec![Vec::new(); map_tile_count]; +} + +/// Clears the SpatialMap. Blocked is set to (false, false), +/// and all tile content is cleared. +pub fn clear() { + let mut lock = SPATIAL_MAP.lock().unwrap(); + lock.blocked.iter_mut().for_each(|b| { + b.0 = false; + b.1 = false; + }); + for content in lock.tile_content.iter_mut() { + content.clear(); + } +} + +/// Iterates through every tile in the map, setting the SpatialMap's +/// blocked-by-map tuple entry to true wherever a tile is impassable. +pub fn populate_blocked_from_map(map: &Map) { + let mut lock = SPATIAL_MAP.lock().unwrap(); + for (i, tile) in map.tiles.iter().enumerate() { + lock.blocked[i].0 = !tile_walkable(*tile); + } +} + +/// Indexes a new entity within the SpatialMap, storing the entity +/// and their BlocksTile status. +pub fn index_entity(entity: Entity, idx: usize, blocks_tile: bool) { + let mut lock = SPATIAL_MAP.lock().unwrap(); + lock.tile_content[idx].push((entity, blocks_tile)); + if blocks_tile { + lock.blocked[idx].1 = true; + } +} + +/// Returns is_empty on a given tile content idx. +pub fn has_tile_content(idx: usize) -> bool { + let lock = SPATIAL_MAP.lock().unwrap(); + if lock.tile_content[idx].is_empty() { + return false; + } + return true; +} + +/// Returns the number of entries on a given index. +pub fn length(idx: usize) -> usize { + let lock = SPATIAL_MAP.lock().unwrap(); + return lock.tile_content[idx].len(); +} + +/// Returns true if the idx is blocked by either a map tile or an entity. +pub fn is_blocked(idx: usize) -> bool { + let lock = SPATIAL_MAP.lock().unwrap(); + return lock.blocked[idx].0 || lock.blocked[idx].1; +} + +/// Calls a function on every entity within a given tile idx. +pub fn for_each_tile_content(idx: usize, mut f: F) +where + F: FnMut(Entity), +{ + let lock = SPATIAL_MAP.lock().unwrap(); + for entity in lock.tile_content[idx].iter() { + f(entity.0); + } +} + +/// Calls a function on every entity within a given tile idx, with the +/// added ability to return a RunState mid-calc. +pub fn for_each_tile_content_with_runstate(idx: usize, mut f: F) -> RunState +where + F: FnMut(Entity) -> Option, +{ + let lock = SPATIAL_MAP.lock().unwrap(); + for entity in lock.tile_content[idx].iter() { + if let Some(rs) = f(entity.0) { + return rs; + } + } + return RunState::AwaitingInput; +} + +/// Calls a function on every entity within a given tile idx, breaking if +/// the closure ever returns false. +pub fn for_each_tile_content_with_bool(idx: usize, mut f: F) +where + F: FnMut(Entity) -> bool, +{ + let lock = SPATIAL_MAP.lock().unwrap(); + for entity in lock.tile_content[idx].iter() { + if !f(entity.0) { + break; + } + } +} + +/// Moves an entity from one index to another in the SpatialMap, and +/// recalculates blocks for both affected tiles. +pub fn move_entity(entity: Entity, moving_from: usize, moving_to: usize) { + let mut lock = SPATIAL_MAP.lock().unwrap(); + let mut entity_blocks = false; + lock.tile_content[moving_from].retain(|(e, blocks)| { + if *e == entity { + entity_blocks = *blocks; + return false; + } else { + return true; + } + }); + lock.tile_content[moving_to].push((entity, entity_blocks)); + // Recalculate blocks + let mut from_blocked = false; + let mut to_blocked = false; + lock.tile_content[moving_from].iter().for_each(|(_, blocks)| { + if *blocks { + from_blocked = true; + } + }); + lock.tile_content[moving_to].iter().for_each(|(_, blocks)| { + if *blocks { + from_blocked = true; + } + }); + lock.blocked[moving_from].1 = from_blocked; + lock.blocked[moving_to].1 = to_blocked; +} diff --git a/src/trigger_system.rs b/src/trigger_system.rs index e951b1d..0b0782f 100644 --- a/src/trigger_system.rs +++ b/src/trigger_system.rs @@ -45,42 +45,42 @@ impl<'a> System<'a> for TriggerSystem { let mut remove_entities: Vec = Vec::new(); for (entity, mut _entity_moved, pos) in (&entities, &mut entity_moved, &position).join() { let idx = map.xy_idx(pos.x, pos.y); - for entity_id in map.tile_content[idx].iter() { - if entity != *entity_id { - let maybe_trigger = entry_trigger.get(*entity_id); + crate::spatial::for_each_tile_content(idx, |entity_id| { + if entity != entity_id { + let maybe_trigger = entry_trigger.get(entity_id); match maybe_trigger { None => {} Some(_trigger) => { // Something on this pos had a trigger - let name = names.get(*entity_id); - hidden.remove(*entity_id); + let name = names.get(entity_id); + hidden.remove(entity_id); if let Some(name) = name { particle_builder.trap_triggered(pos.x, pos.y); gamelog::Logger::new().item_name(&name.name).append("triggers!").log(); } - let damage = inflicts_damage.get(*entity_id); + let damage = inflicts_damage.get(entity_id); if let Some(damage) = damage { let damage_roll = rng.roll_dice(damage.n_dice, damage.sides) + damage.modifier; particle_builder.damage_taken(pos.x, pos.y); SufferDamage::new_damage(&mut inflict_damage, entity, damage_roll, false); } - let confuses = confusion.get(*entity_id); + let confuses = confusion.get(entity_id); if let Some(confuses) = confuses { confusion .insert(entity, Confusion { turns: confuses.turns }) .expect("Unable to insert confusion"); } - let sa = single_activation.get(*entity_id); + let sa = single_activation.get(entity_id); if let Some(_sa) = sa { - remove_entities.push(*entity_id); + remove_entities.push(entity_id); } } } } - } + }); } for trap in remove_entities.iter() { diff --git a/src/visibility_system.rs b/src/visibility_system.rs index f313240..c662a43 100644 --- a/src/visibility_system.rs +++ b/src/visibility_system.rs @@ -54,11 +54,11 @@ impl<'a> System<'a> for VisibilitySystem { map.visible_tiles[idx] = true; // Reveal hidden things - for thing in map.tile_content[idx].iter() { - let is_hidden = hidden.get(*thing); + crate::spatial::for_each_tile_content(idx, |e| { + let is_hidden = hidden.get(e); if let Some(_is_hidden) = is_hidden { if rng.roll_dice(1, 12) == 1 { - let name = names.get(*thing); + let name = names.get(e); if let Some(name) = name { gamelog::Logger::new() .append("You spot a") @@ -66,10 +66,10 @@ impl<'a> System<'a> for VisibilitySystem { .period() .log(); } - hidden.remove(*thing); + hidden.remove(e); } } - } + }); } } }