1
0
Fork 0

Allow multi-tile sized entities

This commit is contained in:
Timothy Warren 2022-01-28 11:48:25 -05:00
parent 140531c601
commit 830b721548
15 changed files with 330 additions and 161 deletions

View File

@ -1,6 +1,6 @@
use ::specs::prelude::*; 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::raws::{self, Reaction, RAWS};
use crate::{spatial, Map}; use crate::{spatial, Map};
@ -16,10 +16,11 @@ impl<'a> System<'a> for AdjacentAI {
WriteStorage<'a, WantsToMelee>, WriteStorage<'a, WantsToMelee>,
Entities<'a>, Entities<'a>,
ReadExpect<'a, Entity>, ReadExpect<'a, Entity>,
ReadStorage<'a, TileSize>,
); );
fn run(&mut self, data: Self::SystemData) { 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(); let mut turn_done: Vec<Entity> = Vec::new();
for (entity, _turn, my_faction, pos) in (&entities, &turns, &factions, &positions).join() { for (entity, _turn, my_faction, pos) in (&entities, &turns, &factions, &positions).join() {
@ -29,66 +30,88 @@ impl<'a> System<'a> for AdjacentAI {
let w = map.width; let w = map.width;
let h = map.height; let h = map.height;
// Add possible reactions to adjacents for each direction if let Some(size) = sizes.get(entity) {
if pos.x > 0 { use crate::rect::Rect;
evaluate(idx - 1, &map, &factions, &my_faction.name, &mut reactions); 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);
if pos.x < w - 1 { parent_rect
evaluate(idx + 1, &map, &factions, &my_faction.name, &mut reactions); .get_all_tiles()
} .iter()
if pos.y > 0 { .filter(|t| !mob_rect.contains(t))
evaluate( .for_each(|t| {
idx - w as usize, if t.0 > 0 && t.0 < w - 1 && t.1 > 0 && t.1 < h - 1 {
&map, let target_idx = map.xy_idx(t.0, t.1);
&factions, evaluate(
&my_faction.name, target_idx,
&mut reactions, &map,
); &factions,
} &my_faction.name,
if pos.y < h - 1 { &mut reactions,
evaluate( );
idx + w as usize, }
&map, })
&factions, } else {
&my_faction.name, // Add possible reactions to adjacents for each direction
&mut reactions, if pos.x > 0 {
); evaluate(idx - 1, &map, &factions, &my_faction.name, &mut reactions);
} }
if pos.y > 0 && pos.x > 0 { if pos.x < w - 1 {
evaluate( evaluate(idx + 1, &map, &factions, &my_faction.name, &mut reactions);
(idx - w as usize) - 1, }
&map, if pos.y > 0 {
&factions, evaluate(
&my_faction.name, idx - w as usize,
&mut reactions, &map,
); &factions,
} &my_faction.name,
if pos.y > 0 && pos.x < w - 1 { &mut reactions,
evaluate( );
(idx - w as usize) + 1, }
&map, if pos.y < h - 1 {
&factions, evaluate(
&my_faction.name, idx + w as usize,
&mut reactions, &map,
); &factions,
} &my_faction.name,
if pos.y < h - 1 && pos.x > 0 { &mut reactions,
evaluate( );
(idx + w as usize) - 1, }
&map, if pos.y > 0 && pos.x > 0 {
&factions, evaluate(
&my_faction.name, (idx - w as usize) - 1,
&mut reactions, &map,
); &factions,
} &my_faction.name,
if pos.y < h - 1 && pos.x < w - 1 { &mut reactions,
evaluate( );
(idx + w as usize) + 1, }
&map, if pos.y > 0 && pos.x < w - 1 {
&factions, evaluate(
&my_faction.name, (idx - w as usize) + 1,
&mut reactions, &map,
); &factions,
&my_faction.name,
&mut reactions,
);
}
if pos.y < h - 1 && pos.x > 0 {
evaluate(
(idx + w as usize) - 1,
&map,
&factions,
&my_faction.name,
&mut reactions,
);
}
if pos.y < h - 1 && pos.x < w - 1 {
evaluate(
(idx + w as usize) + 1,
&map,
&factions,
&my_faction.name,
&mut reactions,
);
}
} }
let mut done = false; let mut done = false;

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use ::specs::prelude::*; use ::specs::prelude::*;
use crate::components::{ApplyMove, Chasing, MyTurn, Position}; use crate::components::{ApplyMove, Chasing, MyTurn, Position, TileSize};
use crate::Map; use crate::Map;
pub struct ChaseAI {} pub struct ChaseAI {}
@ -16,10 +16,13 @@ impl<'a> System<'a> for ChaseAI {
ReadExpect<'a, Map>, ReadExpect<'a, Map>,
Entities<'a>, Entities<'a>,
WriteStorage<'a, ApplyMove>, WriteStorage<'a, ApplyMove>,
ReadStorage<'a, TileSize>,
); );
fn run(&mut self, data: Self::SystemData) { 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 targets: HashMap<Entity, (i32, i32)> = HashMap::new();
let mut end_chase: Vec<Entity> = Vec::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() { for (entity, pos, _chase, _myturn) in (&entities, &positions, &chasing, &turns).join() {
turn_done.push(entity); turn_done.push(entity);
let target_pos = targets[&entity]; let target_pos = targets[&entity];
let path = ::rltk::a_star_search( let path;
map.xy_idx(pos.x, pos.y) as i32,
map.xy_idx(target_pos.0, target_pos.1) as i32, if let Some(size) = sizes.get(entity) {
&*map, 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 { if path.success && path.steps.len() > 1 && path.steps.len() < 15 {
apply_move apply_move

View File

@ -2,8 +2,9 @@
use ::rltk::{Point, Rltk}; use ::rltk::{Point, Rltk};
use ::specs::prelude::*; use ::specs::prelude::*;
use crate::components::{Hidden, Position, Renderable, TileSize};
use crate::map::tile_glyph; 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 /// Whether to render an outline of the current map's boundaries
const SHOW_BOUNDARIES: bool = false; const SHOW_BOUNDARIES: bool = false;
@ -58,28 +59,57 @@ pub fn render_camera(ecs: &World, ctx: &mut Rltk) {
let renderables = ecs.read_storage::<Renderable>(); let renderables = ecs.read_storage::<Renderable>();
let hidden = ecs.read_storage::<Hidden>(); let hidden = ecs.read_storage::<Hidden>();
let map = ecs.fetch::<Map>(); 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() .join()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order)); 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() {
let idx = map.xy_idx(pos.x, pos.y); if let Some(size) = sizes.get(*entity) {
if map.visible_tiles[idx] { for cy in 0..size.y {
let entity_screen_x = pos.x - min_x; for cx in 0..size.x {
let entity_screen_y = pos.y - min_y; let tile_x = cx + pos.x;
if entity_screen_x > 0 let tile_y = cy + pos.y;
&& entity_screen_x < map_width let idx = map.xy_idx(tile_x, tile_y);
&& entity_screen_y > 0 if map.visible_tiles[idx] {
&& entity_screen_y < map_height let entity_screen_x = (cx + pos.x) - min_x;
{ let entity_screen_y = (cy + pos.y) - min_y;
ctx.set( if entity_screen_x > 0
entity_screen_x, && entity_screen_x < map_width
entity_screen_y, && entity_screen_y > 0
render.fg, && entity_screen_y < map_height
render.bg, {
render.glyph, 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;
let entity_screen_y = 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,
entity_screen_y,
render.fg,
render.bg,
render.glyph,
);
}
} }
} }
} }

View File

@ -515,3 +515,9 @@ pub struct SpecialAbility {
pub struct SpecialAbilities { pub struct SpecialAbilities {
pub abilities: Vec<SpecialAbility>, pub abilities: Vec<SpecialAbility>,
} }
#[derive(Component, ConvertSaveload, Clone)]
pub struct TileSize {
pub x: i32,
pub y: i32,
}

View File

@ -5,7 +5,7 @@ mod particles;
mod targeting; mod targeting;
mod triggers; mod triggers;
use std::collections::VecDeque; use std::collections::{HashSet, VecDeque};
use std::sync::Mutex; use std::sync::Mutex;
use ::rltk::{FontCharType, RGB}; use ::rltk::{FontCharType, RGB};
@ -85,6 +85,7 @@ pub struct EffectSpawner {
pub creator: Option<Entity>, pub creator: Option<Entity>,
pub effect_type: EffectType, pub effect_type: EffectType,
pub targets: Targets, pub targets: Targets,
dedupe: HashSet<Entity>,
} }
/// Adds an effect to the queue /// Adds an effect to the queue
@ -93,21 +94,22 @@ pub fn add_effect(creator: Option<Entity>, effect_type: EffectType, targets: Tar
creator, creator,
effect_type, effect_type,
targets, targets,
dedupe: HashSet::new(),
}); });
} }
pub fn run_effects_queue(ecs: &mut World) { pub fn run_effects_queue(ecs: &mut World) {
loop { loop {
let effect = EFFECT_QUEUE.lock().unwrap().pop_front(); let effect = EFFECT_QUEUE.lock().unwrap().pop_front();
if let Some(effect) = effect { if let Some(mut effect) = effect {
target_applicator(ecs, &effect); target_applicator(ecs, &mut effect);
} else { } else {
break; 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 { if let EffectType::ItemUse { item } = effect.effect_type {
triggers::item_trigger(effect.creator, item, &effect.targets, ecs); triggers::item_trigger(effect.creator, item, &effect.targets, ecs);
} else if let EffectType::SpellUse { spell } = effect.effect_type { } 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 { } else if let EffectType::TriggerFire { trigger } = effect.effect_type {
triggers::trigger(effect.creator, trigger, &effect.targets, ecs); triggers::trigger(effect.creator, trigger, &effect.targets, ecs);
} else { } else {
match &effect.targets { match &effect.targets.clone() {
Targets::Tile { tile_idx } => affect_tile(ecs, effect, *tile_idx), Targets::Tile { tile_idx } => affect_tile(ecs, effect, *tile_idx),
Targets::Tiles { tiles } => tiles Targets::Tiles { tiles } => tiles
.iter() .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) { if tile_effect_hits_entities(&effect.effect_type) {
spatial::for_each_tile_content(tile_idx as usize, |entity| { spatial::for_each_tile_content(tile_idx as usize, |entity| {
affect_entity(ecs, effect, 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 { match &effect.effect_type {
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target), EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
EffectType::EntityDeath => damage::death(ecs, effect, target), EffectType::EntityDeath => damage::death(ecs, effect, target),

View File

@ -13,6 +13,11 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
let mut pools = ecs.write_storage::<Pools>(); let mut pools = ecs.write_storage::<Pools>();
if let Some(pool) = pools.get_mut(target) { if let Some(pool) = pools.get_mut(target) {
if !pool.god_mode { if !pool.god_mode {
if let Some(creator) = damage.creator {
if creator == target {
return;
}
}
if let EffectType::Damage { amount } = damage.effect_type { if let EffectType::Damage { amount } = damage.effect_type {
pool.hit_points.current -= amount; pool.hit_points.current -= amount;
add_effect(None, EffectType::Bloodstain, Targets::Single { target }); add_effect(None, EffectType::Bloodstain, Targets::Single { target });

View File

@ -1,7 +1,7 @@
use ::rltk::Rltk; use ::rltk::{Point, Rltk};
use ::specs::prelude::*; 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}; use crate::{camera, colors, Map};
struct Tooltip { struct Tooltip {
@ -54,21 +54,23 @@ impl Tooltip {
} }
pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { 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 (min_x, _max_x, min_y, _max_y) = camera::get_screen_bounds(ecs, ctx);
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let positions = ecs.read_storage::<Position>();
let hidden = ecs.read_storage::<Hidden>(); let hidden = ecs.read_storage::<Hidden>();
let attributes = ecs.read_storage::<Attributes>(); let attributes = ecs.read_storage::<Attributes>();
let pools = ecs.read_storage::<Pools>(); let pools = ecs.read_storage::<Pools>();
let entities = ecs.entities();
let mouse_pos = ctx.mouse_pos(); let mouse_pos = ctx.mouse_pos();
let mut mouse_map_pos = mouse_pos; let mut mouse_map_pos = mouse_pos;
mouse_map_pos.0 += min_x; mouse_map_pos.0 += min_x;
mouse_map_pos.1 += min_y; 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 if mouse_map_pos.0 >= map.width - 1
|| mouse_map_pos.1 >= map.height - 1 || mouse_map_pos.1 >= map.height - 1
|| mouse_map_pos.0 < 1 || mouse_map_pos.0 < 1
@ -77,68 +79,74 @@ pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) {
return; 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; return;
} }
let mut tip_boxes: Vec<Tooltip> = Vec::new(); let mut tip_boxes: Vec<Tooltip> = Vec::new();
for (entity, position, _hidden) in (&entities, &positions, !&hidden).join() { crate::spatial::for_each_tile_content(mouse_idx, |entity| {
if position.x == mouse_map_pos.0 && position.y == mouse_map_pos.1 { if hidden.get(entity).is_some() {
let mut tip = Tooltip::new(); return;
tip.add(super::get_item_display_name(ecs, entity));
// Comment on attributes
if let Some(attr) = attributes.get(entity) {
let mut s = String::new();
if attr.might.bonus < 0 {
s += "Weak. "
}
if attr.might.bonus > 0 {
s += "String. "
}
if attr.quickness.bonus < 0 {
s += "Clumsy. "
}
if attr.quickness.bonus > 0 {
s += "Agile. "
}
if attr.fitness.bonus < 0 {
s += "Unhealthy. "
}
if attr.fitness.bonus > 0 {
s += "Healthy. "
}
if attr.intelligence.bonus < 0 {
s += "Unintelligent. "
}
if attr.intelligence.bonus > 0 {
s += "Smart. "
}
if s.is_empty() {
s = "Quite Average".to_string();
}
tip.add(s);
}
// Comment on pools
if let Some(stat) = pools.get(entity) {
tip.add(format!("Level: {}", stat.level));
}
// Status effects
let statuses = ecs.read_storage::<StatusEffect>();
let durations = ecs.read_storage::<Duration>();
let names = ecs.read_storage::<Name>();
for (status, duration, name) in (&statuses, &durations, &names).join() {
if status.target == entity {
tip.add(format!("{} ({})", name.name, duration.turns));
}
}
tip_boxes.push(tip);
} }
}
let mut tip = Tooltip::new();
tip.add(super::get_item_display_name(ecs, entity));
// Comment on attributes
if let Some(attr) = attributes.get(entity) {
let mut s = String::new();
if attr.might.bonus < 0 {
s += "Weak. "
}
if attr.might.bonus > 0 {
s += "String. "
}
if attr.quickness.bonus < 0 {
s += "Clumsy. "
}
if attr.quickness.bonus > 0 {
s += "Agile. "
}
if attr.fitness.bonus < 0 {
s += "Unhealthy. "
}
if attr.fitness.bonus > 0 {
s += "Healthy. "
}
if attr.intelligence.bonus < 0 {
s += "Unintelligent. "
}
if attr.intelligence.bonus > 0 {
s += "Smart. "
}
if s.is_empty() {
s = "Quite Average".to_string();
}
tip.add(s);
}
// Comment on pools
if let Some(stat) = pools.get(entity) {
tip.add(format!("Level: {}", stat.level));
}
// Status effects
let statuses = ecs.read_storage::<StatusEffect>();
let durations = ecs.read_storage::<Duration>();
let names = ecs.read_storage::<Name>();
for (status, duration, name) in (&statuses, &durations, &names).join() {
if status.target == entity {
tip.add(format!("{} ({})", name.name, duration.turns));
}
}
tip_boxes.push(tip);
});
if tip_boxes.is_empty() { if tip_boxes.is_empty() {
return; return;

View File

@ -145,6 +145,7 @@ fn init_state() -> State {
StatusEffect, StatusEffect,
TeachesSpell, TeachesSpell,
TeleportTo, TeleportTo,
TileSize,
TownPortal, TownPortal,
Vendor, Vendor,
Viewshed, Viewshed,

View File

@ -50,6 +50,31 @@ impl Map {
spatial::populate_blocked_from_map(self); 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) { pub fn clear_content_index(&mut self) {
spatial::clear(); spatial::clear();
} }

View File

@ -1,21 +1,23 @@
use ::specs::prelude::*; use ::specs::prelude::*;
use crate::components::{BlocksTile, Pools, Position}; use crate::components::{BlocksTile, Pools, Position, TileSize};
use crate::{spatial, Map}; use crate::{spatial, Map};
pub struct MapIndexingSystem {} pub struct MapIndexingSystem {}
impl<'a> System<'a> for MapIndexingSystem { impl<'a> System<'a> for MapIndexingSystem {
#[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
ReadExpect<'a, Map>, ReadExpect<'a, Map>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
ReadStorage<'a, BlocksTile>, ReadStorage<'a, BlocksTile>,
ReadStorage<'a, Pools>, ReadStorage<'a, Pools>,
ReadStorage<'a, TileSize>,
Entities<'a>, Entities<'a>,
); );
fn run(&mut self, data: Self::SystemData) { 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::clear();
spatial::populate_blocked_from_map(&*map); spatial::populate_blocked_from_map(&*map);
@ -29,8 +31,21 @@ impl<'a> System<'a> for MapIndexingSystem {
} }
if alive { if alive {
let idx = map.xy_idx(position.x, position.y); if let Some(size) = sizes.get(entity) {
spatial::index_entity(entity, idx, blockers.get(entity).is_some()); // 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());
}
} }
} }
} }

View File

@ -23,6 +23,8 @@ pub struct Renderable {
pub fg: String, pub fg: String,
pub bg: String, pub bg: String,
pub order: i32, pub order: i32,
pub x_size: Option<i32>,
pub y_size: Option<i32>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View File

@ -497,6 +497,12 @@ pub fn spawn_named_mob(
// Renderable // Renderable
if let Some(renderable) = &mob_template.renderable { if let Some(renderable) = &mob_template.renderable {
eb = eb.with(get_renderable_component(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)); eb = eb.with(Name::from(&mob_template.name));

View File

@ -1,4 +1,6 @@
//! Four points, one plane, 90° angles //! Four points, one plane, 90° angles
use std::collections::HashSet;
use ::serde::{Deserialize, Serialize}; use ::serde::{Deserialize, Serialize};
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] #[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
@ -27,4 +29,16 @@ impl Rect {
pub fn center(&self) -> (i32, i32) { pub fn center(&self) -> (i32, i32) {
((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2) ((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
}
} }

View File

@ -123,6 +123,7 @@ pub fn save_game(ecs: &mut World) {
StatusEffect, StatusEffect,
TeachesSpell, TeachesSpell,
TeleportTo, TeleportTo,
TileSize,
TownPortal, TownPortal,
Vendor, Vendor,
Viewshed, Viewshed,
@ -254,6 +255,7 @@ pub fn load_game(ecs: &mut World) {
StatusEffect, StatusEffect,
TeachesSpell, TeachesSpell,
TeleportTo, TeleportTo,
TileSize,
TownPortal, TownPortal,
Vendor, Vendor,
Viewshed, Viewshed,

View File

@ -60,6 +60,11 @@ pub fn is_blocked(idx: usize) -> bool {
lock.blocked[idx].0 || lock.blocked[idx].1 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) pub fn for_each_tile_content<F>(idx: usize, mut f: F)
where where
F: FnMut(Entity), F: FnMut(Entity),
@ -84,6 +89,12 @@ where
RunState::AwaitingInput 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. /// 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 /// This also recalculates if these two tiles are blocked based on the entities
/// within these tiles. /// within these tiles.