Allow multi-tile sized entities
This commit is contained in:
parent
140531c601
commit
830b721548
@ -1,6 +1,6 @@
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{Faction, MyTurn, Position, WantsToMelee};
|
||||
use crate::components::{Faction, MyTurn, Position, TileSize, WantsToMelee};
|
||||
use crate::raws::{self, Reaction, RAWS};
|
||||
use crate::{spatial, Map};
|
||||
|
||||
@ -16,10 +16,11 @@ impl<'a> System<'a> for AdjacentAI {
|
||||
WriteStorage<'a, WantsToMelee>,
|
||||
Entities<'a>,
|
||||
ReadExpect<'a, Entity>,
|
||||
ReadStorage<'a, TileSize>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (mut turns, factions, positions, map, mut want_melee, entities, player) = data;
|
||||
let (mut turns, factions, positions, map, mut want_melee, entities, player, sizes) = data;
|
||||
|
||||
let mut turn_done: Vec<Entity> = Vec::new();
|
||||
for (entity, _turn, my_faction, pos) in (&entities, &turns, &factions, &positions).join() {
|
||||
@ -29,6 +30,27 @@ impl<'a> System<'a> for AdjacentAI {
|
||||
let w = map.width;
|
||||
let h = map.height;
|
||||
|
||||
if let Some(size) = sizes.get(entity) {
|
||||
use crate::rect::Rect;
|
||||
let mob_rect = Rect::new(pos.x, pos.y, size.x, size.y).get_all_tiles();
|
||||
let parent_rect = Rect::new(pos.x - 1, pos.y - 1, size.x + 2, size.y + 2);
|
||||
parent_rect
|
||||
.get_all_tiles()
|
||||
.iter()
|
||||
.filter(|t| !mob_rect.contains(t))
|
||||
.for_each(|t| {
|
||||
if t.0 > 0 && t.0 < w - 1 && t.1 > 0 && t.1 < h - 1 {
|
||||
let target_idx = map.xy_idx(t.0, t.1);
|
||||
evaluate(
|
||||
target_idx,
|
||||
&map,
|
||||
&factions,
|
||||
&my_faction.name,
|
||||
&mut reactions,
|
||||
);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Add possible reactions to adjacents for each direction
|
||||
if pos.x > 0 {
|
||||
evaluate(idx - 1, &map, &factions, &my_faction.name, &mut reactions);
|
||||
@ -90,6 +112,7 @@ impl<'a> System<'a> for AdjacentAI {
|
||||
&mut reactions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut done = false;
|
||||
for reaction in reactions.iter() {
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{ApplyMove, Chasing, MyTurn, Position};
|
||||
use crate::components::{ApplyMove, Chasing, MyTurn, Position, TileSize};
|
||||
use crate::Map;
|
||||
|
||||
pub struct ChaseAI {}
|
||||
@ -16,10 +16,13 @@ impl<'a> System<'a> for ChaseAI {
|
||||
ReadExpect<'a, Map>,
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, ApplyMove>,
|
||||
ReadStorage<'a, TileSize>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (mut turns, mut chasing, positions, map, entities, mut apply_move) = data;
|
||||
use ::rltk::a_star_search;
|
||||
|
||||
let (mut turns, mut chasing, positions, map, entities, mut apply_move, sizes) = data;
|
||||
|
||||
let mut targets: HashMap<Entity, (i32, i32)> = HashMap::new();
|
||||
let mut end_chase: Vec<Entity> = Vec::new();
|
||||
@ -40,11 +43,23 @@ impl<'a> System<'a> for ChaseAI {
|
||||
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(
|
||||
let path;
|
||||
|
||||
if let Some(size) = sizes.get(entity) {
|
||||
let mut map_copy = map.clone();
|
||||
map_copy.populate_blocked_multi(size.x, size.y);
|
||||
path = a_star_search(
|
||||
map_copy.xy_idx(pos.x, pos.y) as i32,
|
||||
map_copy.xy_idx(target_pos.0, target_pos.1) as i32,
|
||||
&map_copy,
|
||||
);
|
||||
} else {
|
||||
path = a_star_search(
|
||||
map.xy_idx(pos.x, pos.y) as i32,
|
||||
map.xy_idx(target_pos.0, target_pos.1) as i32,
|
||||
&*map,
|
||||
);
|
||||
}
|
||||
|
||||
if path.success && path.steps.len() > 1 && path.steps.len() < 15 {
|
||||
apply_move
|
||||
|
@ -2,8 +2,9 @@
|
||||
use ::rltk::{Point, Rltk};
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{Hidden, Position, Renderable, TileSize};
|
||||
use crate::map::tile_glyph;
|
||||
use crate::{colors, Hidden, Map, Position, Renderable};
|
||||
use crate::{colors, Map};
|
||||
|
||||
/// Whether to render an outline of the current map's boundaries
|
||||
const SHOW_BOUNDARIES: bool = false;
|
||||
@ -58,12 +59,40 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
|
||||
let renderables = ecs.read_storage::<Renderable>();
|
||||
let hidden = ecs.read_storage::<Hidden>();
|
||||
let map = ecs.fetch::<Map>();
|
||||
let sizes = ecs.read_storage::<TileSize>();
|
||||
let entities = ecs.entities();
|
||||
|
||||
let mut data = (&positions, &renderables, !&hidden)
|
||||
let mut data = (&positions, &renderables, &entities, !&hidden)
|
||||
.join()
|
||||
.collect::<Vec<_>>();
|
||||
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
|
||||
for (pos, render, _hidden) in data.iter() {
|
||||
for (pos, render, entity, _hidden) in data.iter() {
|
||||
if let Some(size) = sizes.get(*entity) {
|
||||
for cy in 0..size.y {
|
||||
for cx in 0..size.x {
|
||||
let tile_x = cx + pos.x;
|
||||
let tile_y = cy + pos.y;
|
||||
let idx = map.xy_idx(tile_x, tile_y);
|
||||
if map.visible_tiles[idx] {
|
||||
let entity_screen_x = (cx + pos.x) - min_x;
|
||||
let entity_screen_y = (cy + pos.y) - min_y;
|
||||
if entity_screen_x > 0
|
||||
&& entity_screen_x < map_width
|
||||
&& entity_screen_y > 0
|
||||
&& entity_screen_y < map_height
|
||||
{
|
||||
ctx.set(
|
||||
entity_screen_x + 1,
|
||||
entity_screen_y + 1,
|
||||
render.fg,
|
||||
render.bg,
|
||||
render.glyph,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let idx = map.xy_idx(pos.x, pos.y);
|
||||
if map.visible_tiles[idx] {
|
||||
let entity_screen_x = pos.x - min_x;
|
||||
@ -84,6 +113,7 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_debug_map(map: &Map, ctx: &mut Rltk) {
|
||||
let player_pos = Point::new(map.width / 2, map.height / 2);
|
||||
|
@ -515,3 +515,9 @@ pub struct SpecialAbility {
|
||||
pub struct SpecialAbilities {
|
||||
pub abilities: Vec<SpecialAbility>,
|
||||
}
|
||||
|
||||
#[derive(Component, ConvertSaveload, Clone)]
|
||||
pub struct TileSize {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ mod particles;
|
||||
mod targeting;
|
||||
mod triggers;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use ::rltk::{FontCharType, RGB};
|
||||
@ -85,6 +85,7 @@ pub struct EffectSpawner {
|
||||
pub creator: Option<Entity>,
|
||||
pub effect_type: EffectType,
|
||||
pub targets: Targets,
|
||||
dedupe: HashSet<Entity>,
|
||||
}
|
||||
|
||||
/// Adds an effect to the queue
|
||||
@ -93,21 +94,22 @@ pub fn add_effect(creator: Option<Entity>, effect_type: EffectType, targets: Tar
|
||||
creator,
|
||||
effect_type,
|
||||
targets,
|
||||
dedupe: HashSet::new(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn run_effects_queue(ecs: &mut World) {
|
||||
loop {
|
||||
let effect = EFFECT_QUEUE.lock().unwrap().pop_front();
|
||||
if let Some(effect) = effect {
|
||||
target_applicator(ecs, &effect);
|
||||
if let Some(mut effect) = effect {
|
||||
target_applicator(ecs, &mut effect);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
|
||||
fn target_applicator(ecs: &mut World, effect: &mut EffectSpawner) {
|
||||
if let EffectType::ItemUse { item } = effect.effect_type {
|
||||
triggers::item_trigger(effect.creator, item, &effect.targets, ecs);
|
||||
} else if let EffectType::SpellUse { spell } = effect.effect_type {
|
||||
@ -115,7 +117,7 @@ fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
|
||||
} else if let EffectType::TriggerFire { trigger } = effect.effect_type {
|
||||
triggers::trigger(effect.creator, trigger, &effect.targets, ecs);
|
||||
} else {
|
||||
match &effect.targets {
|
||||
match &effect.targets.clone() {
|
||||
Targets::Tile { tile_idx } => affect_tile(ecs, effect, *tile_idx),
|
||||
Targets::Tiles { tiles } => tiles
|
||||
.iter()
|
||||
@ -143,7 +145,7 @@ fn tile_effect_hits_entities(effect: &EffectType) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn affect_tile(ecs: &mut World, effect: &EffectSpawner, tile_idx: i32) {
|
||||
fn affect_tile(ecs: &mut World, effect: &mut EffectSpawner, tile_idx: i32) {
|
||||
if tile_effect_hits_entities(&effect.effect_type) {
|
||||
spatial::for_each_tile_content(tile_idx as usize, |entity| {
|
||||
affect_entity(ecs, effect, entity)
|
||||
@ -157,7 +159,11 @@ fn affect_tile(ecs: &mut World, effect: &EffectSpawner, tile_idx: i32) {
|
||||
}
|
||||
}
|
||||
|
||||
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||
fn affect_entity(ecs: &mut World, effect: &mut EffectSpawner, target: Entity) {
|
||||
if effect.dedupe.contains(&target) {
|
||||
return;
|
||||
}
|
||||
effect.dedupe.insert(target);
|
||||
match &effect.effect_type {
|
||||
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
|
||||
EffectType::EntityDeath => damage::death(ecs, effect, target),
|
||||
|
@ -13,6 +13,11 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
|
||||
let mut pools = ecs.write_storage::<Pools>();
|
||||
if let Some(pool) = pools.get_mut(target) {
|
||||
if !pool.god_mode {
|
||||
if let Some(creator) = damage.creator {
|
||||
if creator == target {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let EffectType::Damage { amount } = damage.effect_type {
|
||||
pool.hit_points.current -= amount;
|
||||
add_effect(None, EffectType::Bloodstain, Targets::Single { target });
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ::rltk::Rltk;
|
||||
use ::rltk::{Point, Rltk};
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{Attributes, Duration, Hidden, Name, Pools, Position, StatusEffect};
|
||||
use crate::components::{Attributes, Duration, Hidden, Name, Pools, StatusEffect};
|
||||
use crate::{camera, colors, Map};
|
||||
|
||||
struct Tooltip {
|
||||
@ -54,21 +54,23 @@ impl Tooltip {
|
||||
}
|
||||
|
||||
pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
|
||||
use rltk::to_cp437;
|
||||
use ::rltk::{to_cp437, Algorithm2D};
|
||||
|
||||
let (min_x, _max_x, min_y, _max_y) = camera::get_screen_bounds(ecs, ctx);
|
||||
let map = ecs.fetch::<Map>();
|
||||
let positions = ecs.read_storage::<Position>();
|
||||
let hidden = ecs.read_storage::<Hidden>();
|
||||
let attributes = ecs.read_storage::<Attributes>();
|
||||
let pools = ecs.read_storage::<Pools>();
|
||||
let entities = ecs.entities();
|
||||
|
||||
let mouse_pos = ctx.mouse_pos();
|
||||
let mut mouse_map_pos = mouse_pos;
|
||||
mouse_map_pos.0 += min_x;
|
||||
mouse_map_pos.1 += min_y;
|
||||
|
||||
if mouse_pos.0 < 1 || mouse_pos.0 > 49 || mouse_pos.1 < 1 || mouse_pos.1 > 40 {
|
||||
return;
|
||||
}
|
||||
|
||||
if mouse_map_pos.0 >= map.width - 1
|
||||
|| mouse_map_pos.1 >= map.height - 1
|
||||
|| mouse_map_pos.0 < 1
|
||||
@ -77,13 +79,20 @@ pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !map.visible_tiles[map.xy_idx(mouse_map_pos.0, mouse_map_pos.1)] {
|
||||
if !map.in_bounds(Point::new(mouse_map_pos.0, mouse_map_pos.1)) {
|
||||
return;
|
||||
}
|
||||
let mouse_idx = map.xy_idx(mouse_map_pos.0, mouse_map_pos.1);
|
||||
if !map.visible_tiles[mouse_idx] {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tip_boxes: Vec<Tooltip> = Vec::new();
|
||||
for (entity, position, _hidden) in (&entities, &positions, !&hidden).join() {
|
||||
if position.x == mouse_map_pos.0 && position.y == mouse_map_pos.1 {
|
||||
crate::spatial::for_each_tile_content(mouse_idx, |entity| {
|
||||
if hidden.get(entity).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tip = Tooltip::new();
|
||||
tip.add(super::get_item_display_name(ecs, entity));
|
||||
|
||||
@ -137,8 +146,7 @@ pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
|
||||
}
|
||||
|
||||
tip_boxes.push(tip);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if tip_boxes.is_empty() {
|
||||
return;
|
||||
|
@ -145,6 +145,7 @@ fn init_state() -> State {
|
||||
StatusEffect,
|
||||
TeachesSpell,
|
||||
TeleportTo,
|
||||
TileSize,
|
||||
TownPortal,
|
||||
Vendor,
|
||||
Viewshed,
|
||||
|
25
src/map.rs
25
src/map.rs
@ -50,6 +50,31 @@ impl Map {
|
||||
spatial::populate_blocked_from_map(self);
|
||||
}
|
||||
|
||||
pub fn populate_blocked_multi(&mut self, width: i32, height: i32) {
|
||||
self.populate_blocked();
|
||||
for y in 1..self.height - 1 {
|
||||
for x in 1..self.width - 1 {
|
||||
let idx = self.xy_idx(x, y);
|
||||
if !spatial::is_blocked(idx) {
|
||||
for cy in 0..height {
|
||||
for cx in 0..width {
|
||||
let tx = x + cx;
|
||||
let ty = y + cy;
|
||||
if tx < self.width - 1 && ty < self.height - 1 {
|
||||
let tidx = self.xy_idx(tx, ty);
|
||||
if spatial::is_blocked(tidx) {
|
||||
spatial::set_blocked(idx, true);
|
||||
}
|
||||
} else {
|
||||
spatial::set_blocked(idx, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_content_index(&mut self) {
|
||||
spatial::clear();
|
||||
}
|
||||
|
@ -1,21 +1,23 @@
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{BlocksTile, Pools, Position};
|
||||
use crate::components::{BlocksTile, Pools, Position, TileSize};
|
||||
use crate::{spatial, Map};
|
||||
|
||||
pub struct MapIndexingSystem {}
|
||||
|
||||
impl<'a> System<'a> for MapIndexingSystem {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
ReadExpect<'a, Map>,
|
||||
ReadStorage<'a, Position>,
|
||||
ReadStorage<'a, BlocksTile>,
|
||||
ReadStorage<'a, Pools>,
|
||||
ReadStorage<'a, TileSize>,
|
||||
Entities<'a>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
let (map, position, blockers, pools, entities) = data;
|
||||
let (map, position, blockers, pools, sizes, entities) = data;
|
||||
|
||||
spatial::clear();
|
||||
spatial::populate_blocked_from_map(&*map);
|
||||
@ -29,9 +31,22 @@ impl<'a> System<'a> for MapIndexingSystem {
|
||||
}
|
||||
|
||||
if alive {
|
||||
if let Some(size) = sizes.get(entity) {
|
||||
// Multi-tile
|
||||
for y in position.y..position.y + size.y {
|
||||
for x in position.x..position.x + size.x {
|
||||
if x > 0 && x < map.width - 1 && y > 0 && y < map.height - 1 {
|
||||
let idx = map.xy_idx(x, y);
|
||||
spatial::index_entity(entity, idx, blockers.get(entity).is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single tile
|
||||
let idx = map.xy_idx(position.x, position.y);
|
||||
spatial::index_entity(entity, idx, blockers.get(entity).is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ pub struct Renderable {
|
||||
pub fg: String,
|
||||
pub bg: String,
|
||||
pub order: i32,
|
||||
pub x_size: Option<i32>,
|
||||
pub y_size: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -497,6 +497,12 @@ pub fn spawn_named_mob(
|
||||
// Renderable
|
||||
if let Some(renderable) = &mob_template.renderable {
|
||||
eb = eb.with(get_renderable_component(renderable));
|
||||
if renderable.x_size.is_some() || renderable.y_size.is_some() {
|
||||
eb = eb.with(TileSize {
|
||||
x: renderable.x_size.unwrap_or(1),
|
||||
y: renderable.y_size.unwrap_or(1),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
eb = eb.with(Name::from(&mob_template.name));
|
||||
|
14
src/rect.rs
14
src/rect.rs
@ -1,4 +1,6 @@
|
||||
//! Four points, one plane, 90° angles
|
||||
use std::collections::HashSet;
|
||||
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
@ -27,4 +29,16 @@ impl Rect {
|
||||
pub fn center(&self) -> (i32, i32) {
|
||||
((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
|
||||
}
|
||||
|
||||
pub fn get_all_tiles(&self) -> HashSet<(i32, i32)> {
|
||||
let mut result = HashSet::new();
|
||||
|
||||
for y in self.y1..self.y2 {
|
||||
for x in self.x1..self.x2 {
|
||||
result.insert((x, y));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ pub fn save_game(ecs: &mut World) {
|
||||
StatusEffect,
|
||||
TeachesSpell,
|
||||
TeleportTo,
|
||||
TileSize,
|
||||
TownPortal,
|
||||
Vendor,
|
||||
Viewshed,
|
||||
@ -254,6 +255,7 @@ pub fn load_game(ecs: &mut World) {
|
||||
StatusEffect,
|
||||
TeachesSpell,
|
||||
TeleportTo,
|
||||
TileSize,
|
||||
TownPortal,
|
||||
Vendor,
|
||||
Viewshed,
|
||||
|
@ -60,6 +60,11 @@ pub fn is_blocked(idx: usize) -> bool {
|
||||
lock.blocked[idx].0 || lock.blocked[idx].1
|
||||
}
|
||||
|
||||
pub fn set_blocked(idx: usize, blocked: bool) {
|
||||
let mut lock = SPATIAL_MAP.lock().unwrap();
|
||||
lock.blocked[idx] = (lock.blocked[idx].0, blocked);
|
||||
}
|
||||
|
||||
pub fn for_each_tile_content<F>(idx: usize, mut f: F)
|
||||
where
|
||||
F: FnMut(Entity),
|
||||
@ -84,6 +89,12 @@ where
|
||||
RunState::AwaitingInput
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_tile_content_clone(idx: usize) -> Vec<Entity> {
|
||||
let lock = SPATIAL_MAP.lock().unwrap();
|
||||
lock.tile_content[idx].iter().map(|(e, _)| *e).collect()
|
||||
}
|
||||
|
||||
/// Move an entity on the map from `moving_from` index to the `moving_to` index.
|
||||
/// This also recalculates if these two tiles are blocked based on the entities
|
||||
/// within these tiles.
|
||||
|
Loading…
Reference in New Issue
Block a user