less blocking - targets will try to path to any space around their tar
This commit is contained in:
parent
340aefa9e1
commit
64caf0dc1a
16 changed files with 252 additions and 68 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,44 +89,61 @@ 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;
|
||||||
}
|
}
|
||||||
let mut spread: i32 = target as i32;
|
if map.bloodstains.get(&target).unwrap() == &colour {
|
||||||
let mut attempts: i32 = 0;
|
let mut spread: i32 = target as i32;
|
||||||
// Otherwise, roll to move one tile in any direction.
|
let mut attempts: i32 = 0;
|
||||||
// If this tile isn't bloody, bloody it. If not, loop.
|
// Otherwise, roll to move one tile in any direction.
|
||||||
loop {
|
// If this tile isn't bloody, bloody it. If not, loop.
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
loop {
|
||||||
attempts += 1;
|
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
||||||
spread = match rng.roll_dice(1, 8) {
|
attempts += 1;
|
||||||
1 => spread + 1,
|
spread = match rng.roll_dice(1, 8) {
|
||||||
2 => spread - 1,
|
1 => spread + 1,
|
||||||
3 => spread + 1 + map.width,
|
2 => spread - 1,
|
||||||
4 => spread - 1 + map.width,
|
3 => spread + 1 + map.width,
|
||||||
5 => spread + 1 - map.width,
|
4 => spread - 1 + map.width,
|
||||||
6 => spread - 1 - map.width,
|
5 => spread + 1 - map.width,
|
||||||
7 => spread + map.width,
|
6 => spread - 1 - map.width,
|
||||||
_ => spread - map.width,
|
7 => spread + map.width,
|
||||||
};
|
_ => spread - map.width,
|
||||||
// - If we're in bounds and the tile is unbloodied, bloody it and return.
|
};
|
||||||
// - If we ever leave bounds, return.
|
// - If we're in bounds and the tile is unbloodied, bloody it and return.
|
||||||
// - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread)
|
// - If we ever leave bounds, return.
|
||||||
if spread > 0 && spread < map.height * map.width {
|
// - Roll a dice on each failed attempt, with an increasing change to return (soft-capping max spread)
|
||||||
if !map.bloodstains.contains(&(spread as usize)) {
|
if spread > 0 && spread < map.height * map.width {
|
||||||
map.bloodstains.insert(spread as usize);
|
if !map.bloodstains.contains_key(&(spread as usize)) {
|
||||||
|
map.bloodstains.insert(spread as usize, colour);
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if rng.roll_dice(1, 10 - attempts) == 1 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { .. } => {
|
||||||
|
|
|
||||||
|
|
@ -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,30 +221,37 @@ 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;
|
||||||
if ecs.read_storage::<Destructible>().get(target).is_some() {
|
} 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() {
|
||||||
|
logger = logger
|
||||||
|
.append("The")
|
||||||
|
.colour(renderable_colour(&renderables, target))
|
||||||
|
.append(obfuscate_name_ecs(ecs, target).0)
|
||||||
|
.colour(WHITE)
|
||||||
|
.append(DAMAGE_ITEM_HIT);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
logger = logger
|
logger = logger
|
||||||
.append("The")
|
.append("The")
|
||||||
.colour(renderable_colour(&renderables, target))
|
.colour(renderable_colour(&renderables, target))
|
||||||
.append(obfuscate_name_ecs(ecs, target).0)
|
.append(obfuscate_name_ecs(ecs, target).0)
|
||||||
.colour(WHITE)
|
.colour(WHITE)
|
||||||
.append(DAMAGE_ITEM_HIT);
|
.append(DAMAGE_OTHER_HIT);
|
||||||
}
|
}
|
||||||
} else {
|
event.log = true;
|
||||||
logger = logger
|
|
||||||
.append("The")
|
|
||||||
.colour(renderable_colour(&renderables, target))
|
|
||||||
.append(obfuscate_name_ecs(ecs, target).0)
|
|
||||||
.colour(WHITE)
|
|
||||||
.append(DAMAGE_OTHER_HIT);
|
|
||||||
}
|
}
|
||||||
event.log = true;
|
|
||||||
}
|
}
|
||||||
return (logger, true);
|
return (logger, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
78
src/map_builders/room_themer.rs
Normal file
78
src/map_builders/room_themer.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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() })
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue