less blocking - targets will try to path to any space around their tar

This commit is contained in:
Llywelwyn 2023-08-30 09:15:45 +01:00
parent 340aefa9e1
commit 64caf0dc1a
16 changed files with 252 additions and 68 deletions

View file

@ -46,5 +46,6 @@ Complex example, with negative AC:
- At worst (AC rolls a 14), the monster must roll less than -1 to hit you. Impossible. - At worst (AC rolls a 14), the monster must roll less than -1 to hit you. Impossible.
- It rolls a 9, and your AC rolls a 2. 9 is less than 11 (10 + 3 - 2), so it hits. - It rolls a 9, and your AC rolls a 2. 9 is less than 11 (10 + 3 - 2), so it hits.
- It rolls 1d8 for damage, and gets a 6. - It rolls 1d8 for damage, and gets a 6.
bloodstains: if starts on bloodied tile, remove blood + heal, gain xp, grow (little dog -> dog), etc.
- You have negative AC, so you roll 1d14 for damage reduction, and get an 8. - You have negative AC, so you roll 1d14 for damage reduction, and get an 8.
- The total damage is 6 - 8 = -2, but damage can't be negative, so you take 1 point of damage. - The total damage is 6 - 8 = -2, but damage can't be negative, so you take 1 point of damage.

View file

@ -414,7 +414,7 @@
{ {
"id": "ogre", "id": "ogre",
"name": "ogre", "name": "ogre",
"renderable": { "glyph": "O", "fg": "#10570d", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "O", "fg": "#10A70d", "bg": "#000000", "order": 1 },
"flags": ["SMALL_GROUP"], "flags": ["SMALL_GROUP"],
"level": 5, "level": 5,
"bac": 5, "bac": 5,
@ -426,11 +426,11 @@
{ {
"id": "treant_small", "id": "treant_small",
"name": "treant sapling", "name": "treant sapling",
"renderable": { "glyph": "♠️", "fg": "#00FF00", "bg": "#000000", "order": 1 }, "renderable": { "glyph": "♠️", "fg": "#10570d", "bg": "#000000", "order": 1 },
"flags": ["LARGE_GROUP"], "flags": ["LARGE_GROUP", "GREEN_BLOOD"],
"level": 2, "level": 2,
"bac": 12, "bac": 12,
"speed": 4, "speed": 12,
"vision_range": 16, "vision_range": 16,
"attacks": [{ "name": "lashes", "hit_bonus": 4, "damage": "1d8" }], "attacks": [{ "name": "lashes", "hit_bonus": 4, "damage": "1d8" }],
"loot": { "table": "scrolls", "chance": 0.05 } "loot": { "table": "scrolls", "chance": 0.05 }

View file

@ -7,6 +7,7 @@ pub struct ApproachAI {}
impl<'a> System<'a> for ApproachAI { impl<'a> System<'a> for ApproachAI {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
WriteExpect<'a, RandomNumberGenerator>,
WriteStorage<'a, TakingTurn>, WriteStorage<'a, TakingTurn>,
WriteStorage<'a, WantsToApproach>, WriteStorage<'a, WantsToApproach>,
WriteStorage<'a, Position>, WriteStorage<'a, Position>,
@ -19,6 +20,7 @@ impl<'a> System<'a> for ApproachAI {
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
let ( let (
mut rng,
mut turns, mut turns,
mut wants_to_approach, mut wants_to_approach,
mut positions, mut positions,
@ -37,11 +39,26 @@ impl<'a> System<'a> for ApproachAI {
&turns, &turns,
).join() { ).join() {
turn_done.push(entity); turn_done.push(entity);
let path = a_star_search( let target_idxs = if let Some(paths) = get_adjacent_unblocked(&map, approach.idx as usize) {
map.xy_idx(pos.x, pos.y) as i32, paths
map.xy_idx(approach.idx % map.width, approach.idx / map.width) as i32, } else {
&mut *map continue;
); };
let mut path: Option<NavigationPath> = None;
let idx = map.xy_idx(pos.x, pos.y);
for tar_idx in target_idxs {
let potential_path = rltk::a_star_search(idx, tar_idx, &mut *map);
if potential_path.success && potential_path.steps.len() > 1 {
if path.is_none() || potential_path.steps.len() < path.as_ref().unwrap().steps.len() {
path = Some(potential_path);
}
}
}
let path = if path.is_some() {
path.unwrap()
} else {
continue;
};
if path.success && path.steps.len() > 1 { if path.success && path.steps.len() > 1 {
let idx = map.xy_idx(pos.x, pos.y); let idx = map.xy_idx(pos.x, pos.y);
pos.x = (path.steps[1] as i32) % map.width; pos.x = (path.steps[1] as i32) % map.width;
@ -61,3 +78,25 @@ impl<'a> System<'a> for ApproachAI {
} }
} }
} }
/// Try to get an unblocked index within one tile of a given idx, or None.
pub fn get_adjacent_unblocked(map: &WriteExpect<Map>, idx: usize) -> Option<Vec<usize>> {
let mut adjacent = Vec::new();
let x = (idx as i32) % map.width;
let y = (idx as i32) / map.width;
for i in -1..2 {
for j in -1..2 {
if i == 0 && j == 0 {
continue;
}
let new_idx = (x + i + (y + j) * map.width) as usize;
if !crate::spatial::is_blocked(new_idx) {
adjacent.push(new_idx);
}
}
}
if adjacent.is_empty() {
return None;
}
return Some(adjacent);
}

View file

@ -2,6 +2,7 @@ use crate::{ Chasing, EntityMoved, Map, Position, TakingTurn, Telepath, Viewshed
use rltk::prelude::*; use rltk::prelude::*;
use specs::prelude::*; use specs::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use super::approach_ai_system::get_adjacent_unblocked;
// If the target is beyond this distance, they're no longer being detected, // If the target is beyond this distance, they're no longer being detected,
// so stop following them. This is essentially a combined value of the sound // so stop following them. This is essentially a combined value of the sound
@ -57,11 +58,27 @@ impl<'a> System<'a> for ChaseAI {
).join() { ).join() {
turn_done.push(entity); turn_done.push(entity);
let target_pos = targets[&entity]; let target_pos = targets[&entity];
let path = a_star_search( let target_idx = map.xy_idx(target_pos.0, target_pos.1);
map.xy_idx(pos.x, pos.y) as i32, let target_idxs = if let Some(paths) = get_adjacent_unblocked(&map, target_idx) {
map.xy_idx(target_pos.0, target_pos.1) as i32, paths
&mut *map } else {
); continue;
};
let mut path: Option<NavigationPath> = None;
let idx = map.xy_idx(pos.x, pos.y);
for tar_idx in target_idxs {
let potential_path = rltk::a_star_search(idx, tar_idx, &mut *map);
if potential_path.success && potential_path.steps.len() > 1 {
if path.is_none() || potential_path.steps.len() < path.as_ref().unwrap().steps.len() {
path = Some(potential_path);
}
}
}
let path = if path.is_some() {
path.unwrap()
} else {
continue;
};
if path.success && path.steps.len() > 1 && path.steps.len() < MAX_CHASE_DISTANCE { if path.success && path.steps.len() > 1 && path.steps.len() < MAX_CHASE_DISTANCE {
let idx = map.xy_idx(pos.x, pos.y); let idx = map.xy_idx(pos.x, pos.y);
pos.x = (path.steps[1] as i32) % map.width; pos.x = (path.steps[1] as i32) % map.width;

View file

@ -46,6 +46,11 @@ pub struct Renderable {
pub render_order: i32, pub render_order: i32,
} }
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Bleeds {
pub colour: RGB,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)] #[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct Player {} pub struct Player {}

View file

@ -13,6 +13,7 @@ use crate::{
Blind, Blind,
HungerClock, HungerClock,
HungerState, HungerState,
Bleeds,
}; };
use crate::gui::with_article; use crate::gui::with_article;
use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME }; use crate::data::visuals::{ DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME };
@ -28,7 +29,10 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) {
if !target_pool.god { if !target_pool.god {
if let EffectType::Damage { amount } = damage.effect_type { if let EffectType::Damage { amount } = damage.effect_type {
target_pool.hit_points.current -= amount; target_pool.hit_points.current -= amount;
add_effect(None, EffectType::Bloodstain, Targets::Entity { target }); let bleeders = ecs.read_storage::<Bleeds>();
if let Some(bleeds) = bleeders.get(target) {
add_effect(None, EffectType::Bloodstain { colour: bleeds.colour }, Targets::Entity { target });
}
add_effect( add_effect(
None, None,
EffectType::Particle { EffectType::Particle {
@ -85,13 +89,14 @@ pub fn add_confusion(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
} }
} }
pub fn bloodstain(ecs: &mut World, target: usize) { pub fn bloodstain(ecs: &mut World, target: usize, colour: RGB) {
let mut map = ecs.fetch_mut::<Map>(); let mut map = ecs.fetch_mut::<Map>();
// If the current tile isn't bloody, bloody it. // If the current tile isn't bloody, bloody it.
if !map.bloodstains.contains(&target) { if !map.bloodstains.contains_key(&target) {
map.bloodstains.insert(target); map.bloodstains.insert(target, colour);
return; return;
} }
if map.bloodstains.get(&target).unwrap() == &colour {
let mut spread: i32 = target as i32; let mut spread: i32 = target as i32;
let mut attempts: i32 = 0; let mut attempts: i32 = 0;
// Otherwise, roll to move one tile in any direction. // Otherwise, roll to move one tile in any direction.
@ -113,17 +118,33 @@ pub fn bloodstain(ecs: &mut World, target: usize) {
// - If we ever leave bounds, return. // - If we ever leave bounds, return.
// - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread) // - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread)
if spread > 0 && spread < map.height * map.width { if spread > 0 && spread < map.height * map.width {
if !map.bloodstains.contains(&(spread as usize)) { if !map.bloodstains.contains_key(&(spread as usize)) {
map.bloodstains.insert(spread as usize); map.bloodstains.insert(spread as usize, colour);
return; return;
} }
// If bloodied with the same colour, return
if map.bloodstains.get(&(spread as usize)).unwrap() == &colour {
if rng.roll_dice(1, 10 - attempts) == 1 { if rng.roll_dice(1, 10 - attempts) == 1 {
return; return;
} }
// If bloodied but a *different* colour, lerp this blood and current blood.
} else {
let new_col = map.bloodstains
.get(&(spread as usize))
.unwrap()
.lerp(colour, 0.5);
map.bloodstains.insert(spread as usize, new_col);
}
} else { } else {
return; return;
} }
} }
} else {
let curr_blood = map.bloodstains.get(&target).unwrap();
let new_colour = curr_blood.lerp(colour, 0.5);
map.bloodstains.insert(target, new_colour);
return;
}
} }
/// Takes a level, and returns the total XP required to reach the next level. /// Takes a level, and returns the total XP required to reach the next level.

View file

@ -32,7 +32,9 @@ pub enum EffectType {
Confusion { Confusion {
turns: i32, turns: i32,
}, },
Bloodstain, Bloodstain {
colour: RGB,
},
Particle { Particle {
glyph: FontCharType, glyph: FontCharType,
fg: RGB, fg: RGB,
@ -134,7 +136,6 @@ fn affect_tile(ecs: &mut World, effect: &EffectSpawner, target: usize) {
} }
match &effect.effect_type { match &effect.effect_type {
EffectType::Bloodstain => damage::bloodstain(ecs, target),
EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect), EffectType::Particle { .. } => particles::particle_to_tile(ecs, target as i32, &effect),
_ => {} _ => {}
} }
@ -158,9 +159,9 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target), EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target), EffectType::Healing { .. } => damage::heal_damage(ecs, effect, target),
EffectType::Confusion { .. } => damage::add_confusion(ecs, effect, target), EffectType::Confusion { .. } => damage::add_confusion(ecs, effect, target),
EffectType::Bloodstain { .. } => { EffectType::Bloodstain { colour } => {
if let Some(pos) = targeting::entity_position(ecs, target) { if let Some(pos) = targeting::entity_position(ecs, target) {
damage::bloodstain(ecs, pos) damage::bloodstain(ecs, pos, *colour);
} }
} }
EffectType::Particle { .. } => { EffectType::Particle { .. } => {

View file

@ -32,6 +32,8 @@ use crate::{
GrantsSpell, GrantsSpell,
KnownSpell, KnownSpell,
KnownSpells, KnownSpells,
Position,
Viewshed,
}; };
use crate::data::messages::*; use crate::data::messages::*;
use rltk::prelude::*; use rltk::prelude::*;
@ -219,13 +221,19 @@ fn handle_damage(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Lo
continue; continue;
} }
let renderables = ecs.read_storage::<Renderable>(); let renderables = ecs.read_storage::<Renderable>();
let positions = ecs.read_storage::<Position>();
let target_pos = positions.get(target).unwrap_or(&(Position { x: 0, y: 0 }));
let viewsheds = ecs.read_storage::<Viewshed>();
let player_viewshed = viewsheds.get(*ecs.fetch::<Entity>()).unwrap();
if ecs.read_storage::<Player>().get(target).is_some() { if ecs.read_storage::<Player>().get(target).is_some() {
logger = logger logger = logger
.colour(renderable_colour(&renderables, target)) .colour(renderable_colour(&renderables, target))
.append("You") .append("You")
.colour(WHITE) .colour(WHITE)
.append(DAMAGE_PLAYER_HIT); .append(DAMAGE_PLAYER_HIT);
} else if ecs.read_storage::<Item>().get(target).is_some() { event.log = true;
} else if player_viewshed.visible_tiles.contains(&Point::new(target_pos.x, target_pos.y)) {
if ecs.read_storage::<Item>().get(target).is_some() {
if ecs.read_storage::<Destructible>().get(target).is_some() { if ecs.read_storage::<Destructible>().get(target).is_some() {
logger = logger logger = logger
.append("The") .append("The")
@ -244,6 +252,7 @@ fn handle_damage(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Lo
} }
event.log = true; event.log = true;
} }
}
return (logger, true); return (logger, true);
} }
return (logger, false); return (logger, false);

View file

@ -729,6 +729,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<ProvidesIdentify>(); gs.ecs.register::<ProvidesIdentify>();
gs.ecs.register::<KnownSpells>(); gs.ecs.register::<KnownSpells>();
gs.ecs.register::<GrantsSpell>(); gs.ecs.register::<GrantsSpell>();
gs.ecs.register::<Bleeds>();
gs.ecs.register::<ParticleLifetime>(); gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<SpawnParticleSimple>(); gs.ecs.register::<SpawnParticleSimple>();
gs.ecs.register::<SpawnParticleBurst>(); gs.ecs.register::<SpawnParticleBurst>();

View file

@ -1,6 +1,6 @@
use rltk::{ Algorithm2D, BaseMap, Point }; use rltk::prelude::*;
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use std::collections::HashSet; use std::collections::{ HashSet, HashMap };
mod tiletype; mod tiletype;
pub use tiletype::{ tile_cost, tile_opaque, tile_walkable, TileType, get_dest, Destination }; pub use tiletype::{ tile_cost, tile_opaque, tile_walkable, TileType, get_dest, Destination };
mod interval_spawning_system; mod interval_spawning_system;
@ -25,13 +25,13 @@ pub struct Map {
pub lit_tiles: Vec<bool>, pub lit_tiles: Vec<bool>,
pub telepath_tiles: Vec<bool>, pub telepath_tiles: Vec<bool>,
pub colour_offset: Vec<((f32, f32, f32), (f32, f32, f32))>, pub colour_offset: Vec<((f32, f32, f32), (f32, f32, f32))>,
pub additional_fg_offset: rltk::RGB, pub additional_fg_offset: RGB,
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub short_name: String, pub short_name: String,
pub depth: i32, pub depth: i32,
pub difficulty: i32, pub difficulty: i32,
pub bloodstains: HashSet<usize>, pub bloodstains: HashMap<usize, RGB>,
pub view_blocked: HashSet<usize>, pub view_blocked: HashSet<usize>,
} }
@ -72,7 +72,7 @@ impl Map {
short_name: short_name.to_string(), short_name: short_name.to_string(),
depth: depth, depth: depth,
difficulty: difficulty, difficulty: difficulty,
bloodstains: HashSet::new(), bloodstains: HashMap::new(),
view_blocked: HashSet::new(), view_blocked: HashSet::new(),
}; };

View file

@ -287,8 +287,8 @@ fn apply_colour_offset(mut rgb: RGB, map: &Map, idx: usize, offset: (i32, i32, i
} }
fn apply_bloodstain_if_necessary(mut bg: RGB, map: &Map, idx: usize) -> RGB { fn apply_bloodstain_if_necessary(mut bg: RGB, map: &Map, idx: usize) -> RGB {
if map.bloodstains.contains(&idx) { if map.bloodstains.contains_key(&idx) {
bg = bg.add(RGB::named(BLOODSTAIN_COLOUR)); bg = bg.add(map.bloodstains[&idx]);
} }
return bg; return bg;
} }

View file

@ -67,6 +67,8 @@ mod forest;
use forest::forest_builder; use forest::forest_builder;
mod foliage; mod foliage;
use foliage::Foliage; use foliage::Foliage;
mod room_themer;
use room_themer::{ Theme, ThemeRooms };
// Shared data to be passed around build chain // Shared data to be passed around build chain
pub struct BuilderMap { pub struct BuilderMap {
@ -284,6 +286,8 @@ fn random_room_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut Buil
1 => builder.with(RoomBasedSpawner::new()), 1 => builder.with(RoomBasedSpawner::new()),
_ => builder.with(VoronoiSpawning::new()), _ => builder.with(VoronoiSpawning::new()),
} }
builder.with(ThemeRooms::grass(10));
} }
fn random_shape_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut BuilderChain, end: bool) -> bool { fn random_shape_builder(rng: &mut rltk::RandomNumberGenerator, builder: &mut BuilderChain, end: bool) -> bool {

View file

@ -0,0 +1,78 @@
use super::{ BuilderMap, MetaMapBuilder, Rect, TileType };
use crate::tile_walkable;
use rltk::RandomNumberGenerator;
pub enum Theme {
Grass,
Forest,
}
pub struct ThemeRooms {
pub theme: Theme,
pub percent: i32,
}
impl MetaMapBuilder for ThemeRooms {
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data: &mut BuilderMap) {
self.build(rng, build_data);
}
}
impl ThemeRooms {
#[allow(dead_code)]
pub fn grass(percent: i32) -> Box<ThemeRooms> {
return Box::new(ThemeRooms { theme: Theme::Grass, percent });
}
pub fn forest(percent: i32) -> Box<ThemeRooms> {
return Box::new(ThemeRooms { theme: Theme::Forest, percent });
}
fn grassify(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap, room: &Rect) {
let (var_x, var_y) = (rng.roll_dice(1, 3), rng.roll_dice(1, 3));
let x1 = if room.x1 - var_x > 0 { room.x1 - var_x } else { room.x1 };
let x2 = if room.x2 + var_x < build_data.map.width - 1 { room.x2 + var_x } else { room.x2 };
let y1 = if room.y1 - var_y > 0 { room.y1 - var_y } else { room.y1 };
let y2 = if room.y2 + var_y < build_data.map.height - 1 { room.y2 + var_y } else { room.y2 };
for x in x1..x2 {
for y in y1..y2 {
let idx = build_data.map.xy_idx(x, y);
if tile_walkable(build_data.map.tiles[idx]) && build_data.map.tiles[idx] != TileType::DownStair {
let tar = if x < room.x1 || x > room.x2 || y < room.y1 || y > room.y2 { 45 } else { 90 };
if rng.roll_dice(1, 100) <= tar {
match rng.roll_dice(1, 6) {
1..=4 => {
build_data.map.tiles[idx] = TileType::Grass;
}
5 => {
build_data.map.tiles[idx] = TileType::Foliage;
}
_ => {
build_data.map.tiles[idx] = TileType::HeavyFoliage;
build_data.spawn_list.push((idx, "treant_small".to_string()));
}
}
}
}
}
}
}
fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) {
let rooms: Vec<Rect>;
if let Some(rooms_builder) = &build_data.rooms {
rooms = rooms_builder.clone();
} else {
panic!("RoomCornerRounding requires a builder with rooms.");
}
for room in rooms.iter() {
if rng.roll_dice(1, 100) < self.percent {
match self.theme {
Theme::Grass => self.grassify(rng, build_data, room),
_ => {}
}
build_data.take_snapshot();
}
}
}
}

View file

@ -33,6 +33,7 @@ use super::{
WantsToPickupItem, WantsToPickupItem,
get_dest, get_dest,
Destination, Destination,
Bleeds,
}; };
use rltk::prelude::*; use rltk::prelude::*;
use rltk::{ Point, RandomNumberGenerator, Rltk, VirtualKeyCode }; use rltk::{ Point, RandomNumberGenerator, Rltk, VirtualKeyCode };

View file

@ -4,6 +4,7 @@ use crate::gamesystem::*;
use crate::gui::Ancestry; use crate::gui::Ancestry;
use crate::random_table::RandomTable; use crate::random_table::RandomTable;
use crate::config::CONFIG; use crate::config::CONFIG;
use crate::data::visuals::BLOODSTAIN_COLOUR;
use regex::Regex; use regex::Regex;
use rltk::prelude::*; use rltk::prelude::*;
use specs::prelude::*; use specs::prelude::*;
@ -91,6 +92,8 @@ macro_rules! apply_flags {
"STATIC" => $eb = $eb.with(MoveMode { mode: Movement::Static }), "STATIC" => $eb = $eb.with(MoveMode { mode: Movement::Static }),
"RANDOM_PATH" => $eb = $eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }), "RANDOM_PATH" => $eb = $eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }),
// --- RANDOM MOB ATTRIBUTES --- // --- RANDOM MOB ATTRIBUTES ---
"GREEN_BLOOD" => $eb = $eb.with(Bleeds { colour: RGB::named((0, 153, 0)) }),
"BLUE_BLOOD" => $eb = $eb.with(Bleeds { colour: RGB::named((0, 0, 153)) }),
"SMALL_GROUP" => {} // These flags are for region spawning, "SMALL_GROUP" => {} // These flags are for region spawning,
"LARGE_GROUP" => {} // and don't need to apply a component. "LARGE_GROUP" => {} // and don't need to apply a component.
"MULTIATTACK" => $eb = $eb.with(MultiAttack {}), "MULTIATTACK" => $eb = $eb.with(MultiAttack {}),
@ -386,6 +389,7 @@ pub fn spawn_named_mob(
eb = eb.with(BlocksTile {}); eb = eb.with(BlocksTile {});
eb = eb.with(Faction { name: "hostile".to_string() }); eb = eb.with(Faction { name: "hostile".to_string() });
eb = eb.with(MoveMode { mode: Movement::Random }); eb = eb.with(MoveMode { mode: Movement::Random });
eb = eb.with(Bleeds { colour: RGB::named(BLOODSTAIN_COLOUR) });
let mut xp_value = 1; let mut xp_value = 1;
let mut has_mind = true; let mut has_mind = true;
if let Some(flags) = &mob_template.flags { if let Some(flags) = &mob_template.flags {

View file

@ -24,8 +24,10 @@ use super::{
TileType, TileType,
Viewshed, Viewshed,
BlocksTile, BlocksTile,
Bleeds,
}; };
use crate::data::entity; use crate::data::entity;
use crate::data::visuals::BLOODSTAIN_COLOUR;
use crate::gamesystem::*; use crate::gamesystem::*;
use rltk::{ RandomNumberGenerator, RGB }; use rltk::{ RandomNumberGenerator, RGB };
use specs::prelude::*; use specs::prelude::*;
@ -51,6 +53,7 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
bg: RGB::named(rltk::BLACK), bg: RGB::named(rltk::BLACK),
render_order: 0, render_order: 0,
}) })
.with(Bleeds { colour: RGB::named(BLOODSTAIN_COLOUR) })
.with(Player {}) .with(Player {})
.with(Mind {}) .with(Mind {})
.with(Faction { name: "player".to_string() }) .with(Faction { name: "player".to_string() })