complete spatial indexing refactor - SpatialMap

This commit is contained in:
Llywelwyn 2023-08-16 01:17:38 +01:00
parent 2887bb9736
commit d439ff6d3f
18 changed files with 351 additions and 217 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -96,20 +96,27 @@ fn evaluate(
reactions: &mut Vec<(usize, Reaction, Entity)>,
minds: Option<&ReadStorage<Mind>>,
) {
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 let Some(faction) = factions.get(*other_entity) {
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,
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,
));
}
}
});
}

View file

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

View file

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

View file

@ -36,6 +36,7 @@ mod ai;
mod gamesystem;
mod random_table;
mod rex_assets;
mod spatial;
#[macro_use]
extern crate lazy_static;

View file

@ -51,7 +51,6 @@ impl MasterDungeonMap {
pub fn get_map(&self, id: i32) -> Option<Map> {
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;

View file

@ -75,7 +75,7 @@ fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) {
fn populate_unblocked_nonvisible(map: &Map) -> Vec<usize> {
let mut tiles: Vec<usize> = 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);
}
}

View file

@ -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<bool>,
pub id: i32,
pub name: String,
pub difficulty: i32,
pub bloodstains: HashSet<usize>,
pub view_blocked: HashSet<usize>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub tile_content: Vec<Vec<Entity>>,
}
impl Map {
@ -47,6 +42,7 @@ impl Map {
pub fn new<S: ToString>(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();
}
}

View file

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

View file

@ -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>();
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<Entity> = 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::<ParticleBuilder>();
@ -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::<Position>();
let mut players = ecs.write_storage::<Player>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
@ -278,12 +283,12 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
let factions = ecs.read_storage::<Faction>();
let pools = ecs.read_storage::<Pools>();
let map = ecs.fetch::<Map>();
let entities = ecs.entities();
let mut wants_to_melee = ecs.write_storage::<WantsToMelee>();
let mut doors = ecs.write_storage::<Door>();
let names = ecs.read_storage::<Name>();
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,54 +321,46 @@ 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;
}
}
return true;
}
if map.blocked[destination_idx] {
if swap_entities.len() <= 0 {
if crate::spatial::is_blocked(destination_idx) {
gamelog::Logger::new().append("You can't move there.").log();
return false;
return RunState::AwaitingInput;
}
let hidden = ecs.read_storage::<Hidden>();
// Push every entity name in the pile to a vector of strings
let mut item_names: Vec<String> = 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;
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;
}
}
});
// 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)
@ -378,28 +375,41 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
}
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;
let is_telepath = telepaths.get_mut(entity);
if let Some(telepathy) = is_telepath {
if let Some(telepathy) = telepaths.get_mut(entity) {
telepathy.dirty = true;
}
let mut ppos = ecs.write_resource::<Point>();
ppos.x = pos.x;
ppos.y = pos.y;
entity_moved.insert(entity, EntityMoved {}).expect("Unable to insert marker");
return true;
return RunState::Ticking;
}
}
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::<Point>();
let player_entity = ecs.fetch::<Entity>();
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::<WantsToPickupItem>();
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;
}
}
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::<Entity>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
let worldmap_resource = ecs.fetch::<Map>();
@ -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.

View file

@ -247,7 +247,7 @@ pub fn load_game(ecs: &mut World) {
for (e, h) in (&entities, &helper).join() {
let mut worldmap = ecs.write_resource::<super::map::Map>();
*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() {

149
src/spatial/mod.rs Normal file
View file

@ -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<Vec<(Entity, bool)>>,
}
impl SpatialMap {
fn new() -> Self {
return Self { blocked: Vec::new(), tile_content: Vec::new() };
}
}
lazy_static! {
static ref SPATIAL_MAP: Mutex<SpatialMap> = 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<F>(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<F>(idx: usize, mut f: F) -> RunState
where
F: FnMut(Entity) -> Option<RunState>,
{
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<F>(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;
}

View file

@ -45,42 +45,42 @@ impl<'a> System<'a> for TriggerSystem {
let mut remove_entities: Vec<Entity> = 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() {

View file

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