Create effects module to handle damage, particles, and death more modularly
This commit is contained in:
parent
d6c9341569
commit
6f35a4a933
116
src/effects.rs
Normal file
116
src/effects.rs
Normal file
@ -0,0 +1,116 @@
|
||||
mod damage;
|
||||
mod particles;
|
||||
mod targeting;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use ::rltk::{FontCharType, RGB};
|
||||
use ::specs::prelude::*;
|
||||
pub use targeting::*;
|
||||
|
||||
use crate::effects::particles::particle_to_tile;
|
||||
use crate::spatial;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref EFFECT_QUEUE: Mutex<VecDeque<EffectSpawner>> = Mutex::new(VecDeque::new());
|
||||
}
|
||||
|
||||
pub enum EffectType {
|
||||
Damage {
|
||||
amount: i32,
|
||||
},
|
||||
Bloodstain,
|
||||
Particle {
|
||||
glyph: FontCharType,
|
||||
fg: RGB,
|
||||
bg: RGB,
|
||||
lifespan: f32,
|
||||
},
|
||||
EntityDeath,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Targets {
|
||||
Single { target: Entity },
|
||||
TargetList { targets: Vec<Entity> },
|
||||
Tile { tile_idx: i32 },
|
||||
Tiles { tiles: Vec<i32> },
|
||||
}
|
||||
|
||||
pub struct EffectSpawner {
|
||||
pub creator: Option<Entity>,
|
||||
pub effect_type: EffectType,
|
||||
pub targets: Targets,
|
||||
}
|
||||
|
||||
pub fn add_effect(creator: Option<Entity>, effect_type: EffectType, targets: Targets) {
|
||||
EFFECT_QUEUE.lock().unwrap().push_back(EffectSpawner {
|
||||
creator,
|
||||
effect_type,
|
||||
targets,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn target_applicator(ecs: &mut World, effect: &EffectSpawner) {
|
||||
match &effect.targets {
|
||||
Targets::Tile { tile_idx } => affect_tile(ecs, effect, *tile_idx),
|
||||
Targets::Tiles { tiles } => tiles
|
||||
.iter()
|
||||
.for_each(|tile_idx| affect_tile(ecs, effect, *tile_idx)),
|
||||
Targets::Single { target } => affect_entity(ecs, effect, *target),
|
||||
Targets::TargetList { targets } => targets
|
||||
.iter()
|
||||
.for_each(|entity| affect_entity(ecs, effect, *entity)),
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_effect_hits_entities(effect: &EffectType) -> bool {
|
||||
match effect {
|
||||
EffectType::Damage { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn affect_tile(ecs: &mut World, effect: &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)
|
||||
});
|
||||
}
|
||||
|
||||
match &effect.effect_type {
|
||||
EffectType::Bloodstain => damage::bloodstain(ecs, tile_idx),
|
||||
EffectType::Particle { .. } => particle_to_tile(ecs, tile_idx, effect),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||
match &effect.effect_type {
|
||||
EffectType::Damage { .. } => damage::inflict_damage(ecs, effect, target),
|
||||
EffectType::EntityDeath => damage::death(ecs, effect, target),
|
||||
EffectType::Bloodstain { .. } => {
|
||||
if let Some(pos) = entity_position(ecs, target) {
|
||||
damage::bloodstain(ecs, pos)
|
||||
}
|
||||
}
|
||||
EffectType::Particle { .. } => {
|
||||
if let Some(pos) = entity_position(ecs, target) {
|
||||
particle_to_tile(ecs, pos, effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
src/effects/damage.rs
Normal file
109
src/effects/damage.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use ::rltk::Point;
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use super::{add_effect, entity_position, EffectSpawner, EffectType, Targets};
|
||||
use crate::components::{Attributes, Player, Pools};
|
||||
use crate::gamesystem::{mana_at_level, player_hp_at_level};
|
||||
use crate::{colors, GameLog, Map};
|
||||
|
||||
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 EffectType::Damage { amount } = damage.effect_type {
|
||||
pool.hit_points.current -= amount;
|
||||
add_effect(None, EffectType::Bloodstain, Targets::Single { target });
|
||||
add_effect(
|
||||
None,
|
||||
EffectType::Particle {
|
||||
glyph: rltk::to_cp437('‼'),
|
||||
fg: colors::ORANGE,
|
||||
bg: colors::BLACK,
|
||||
lifespan: 200.0,
|
||||
},
|
||||
Targets::Single { target },
|
||||
);
|
||||
|
||||
if pool.hit_points.current < 1 {
|
||||
add_effect(
|
||||
damage.creator,
|
||||
EffectType::EntityDeath,
|
||||
Targets::Single { target },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bloodstain(ecs: &mut World, tile_idx: i32) {
|
||||
let mut map = ecs.fetch_mut::<Map>();
|
||||
map.bloodstains.insert(tile_idx as usize);
|
||||
}
|
||||
|
||||
pub fn death(ecs: &mut World, effect: &EffectSpawner, target: Entity) {
|
||||
let mut xp_gain = 0;
|
||||
let mut gold_gain = 0.0f32;
|
||||
|
||||
let mut pools = ecs.write_storage::<Pools>();
|
||||
let attributes = ecs.read_storage::<Attributes>();
|
||||
let map = ecs.fetch_mut::<Map>();
|
||||
|
||||
if let Some(pos) = entity_position(ecs, target) {
|
||||
crate::spatial::remove_entity(target, pos as usize);
|
||||
}
|
||||
|
||||
if let Some(source) = effect.creator {
|
||||
if ecs.read_storage::<Player>().get(source).is_some() {
|
||||
if let Some(stats) = pools.get(target) {
|
||||
xp_gain += stats.level * 100;
|
||||
gold_gain += stats.gold;
|
||||
}
|
||||
|
||||
if xp_gain != 0 || gold_gain != 0.0 {
|
||||
let mut log = ecs.fetch_mut::<GameLog>();
|
||||
let mut player_stats = pools.get_mut(source).unwrap();
|
||||
let player_attributes = attributes.get(source).unwrap();
|
||||
player_stats.xp += xp_gain;
|
||||
player_stats.gold += gold_gain;
|
||||
if player_stats.xp >= player_stats.level * 1000 {
|
||||
// We've gone up a level!
|
||||
player_stats.level += 1;
|
||||
log.append(format!(
|
||||
"Congratulations, you are now level {}",
|
||||
player_stats.level
|
||||
));
|
||||
player_stats.hit_points.max = player_hp_at_level(
|
||||
player_attributes.fitness.base + player_attributes.fitness.modifiers,
|
||||
player_stats.level,
|
||||
);
|
||||
player_stats.hit_points.current = player_stats.hit_points.max;
|
||||
player_stats.mana.max = mana_at_level(
|
||||
player_attributes.intelligence.base
|
||||
+ player_attributes.intelligence.modifiers,
|
||||
player_stats.level,
|
||||
);
|
||||
player_stats.mana.current = player_stats.mana.max;
|
||||
|
||||
let player_pos = ecs.fetch::<Point>();
|
||||
for i in 0..10 {
|
||||
if player_pos.y - i > 1 {
|
||||
add_effect(
|
||||
None,
|
||||
EffectType::Particle {
|
||||
glyph: ::rltk::to_cp437('░'),
|
||||
fg: colors::GOLD,
|
||||
bg: colors::BLACK,
|
||||
lifespan: 400.0,
|
||||
},
|
||||
Targets::Tile {
|
||||
tile_idx: map.xy_idx(player_pos.x, player_pos.y - i) as i32,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/effects/particles.rs
Normal file
26
src/effects/particles.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use super::{EffectSpawner, EffectType};
|
||||
use crate::map::Map;
|
||||
use crate::particle_system::ParticleBuilder;
|
||||
|
||||
pub fn particle_to_tile(ecs: &mut World, tile_idx: i32, effect: &EffectSpawner) {
|
||||
if let EffectType::Particle {
|
||||
glyph,
|
||||
fg,
|
||||
bg,
|
||||
lifespan,
|
||||
} = effect.effect_type
|
||||
{
|
||||
let map = ecs.fetch::<Map>();
|
||||
let mut particle_builder = ecs.fetch_mut::<ParticleBuilder>();
|
||||
particle_builder.request(
|
||||
tile_idx % map.width,
|
||||
tile_idx / map.width,
|
||||
fg,
|
||||
bg,
|
||||
glyph,
|
||||
lifespan,
|
||||
);
|
||||
}
|
||||
}
|
14
src/effects/targeting.rs
Normal file
14
src/effects/targeting.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::components::Position;
|
||||
use crate::map::Map;
|
||||
|
||||
pub fn entity_position(ecs: &World, target: Entity) -> Option<i32> {
|
||||
if let Some(pos) = ecs.read_storage::<Position>().get(target) {
|
||||
let map = ecs.fetch::<Map>();
|
||||
|
||||
return Some(map.xy_idx(pos.x, pos.y) as i32);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
@ -2,8 +2,8 @@ use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{
|
||||
AreaOfEffect, Confusion, Consumable, EquipmentChanged, Equippable, Equipped, HungerClock,
|
||||
HungerState, IdentifiedItem, InBackpack, InflictsDamage, MagicMapper, Name, Pools,
|
||||
Position, ProvidesFood, ProvidesHealing, SufferDamage, TownPortal, WantsToUseItem,
|
||||
HungerState, IdentifiedItem, InBackpack, InflictsDamage, MagicMapper, Name, Pools, Position,
|
||||
ProvidesFood, ProvidesHealing, SufferDamage, TownPortal, WantsToUseItem,
|
||||
};
|
||||
use crate::game_log::GameLog;
|
||||
use crate::particle_system::ParticleBuilder;
|
||||
|
13
src/main.rs
13
src/main.rs
@ -1,8 +1,12 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod ai;
|
||||
pub mod camera;
|
||||
mod colors;
|
||||
mod components;
|
||||
mod damage_system;
|
||||
mod effects;
|
||||
mod game_log;
|
||||
mod gamesystem;
|
||||
mod gui;
|
||||
@ -16,20 +20,17 @@ mod melee_combat_system;
|
||||
mod movement_system;
|
||||
mod particle_system;
|
||||
mod player;
|
||||
pub mod random_table;
|
||||
pub mod raws;
|
||||
mod random_table;
|
||||
mod raws;
|
||||
mod rect;
|
||||
mod rex_assets;
|
||||
pub mod saveload_system;
|
||||
mod saveload_system;
|
||||
mod spatial;
|
||||
mod spawner;
|
||||
mod state;
|
||||
mod trigger_system;
|
||||
mod visibility_system;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use ::specs::prelude::*;
|
||||
use ::specs::saveload::{SimpleMarker, SimpleMarkerAllocator};
|
||||
use components::*;
|
||||
|
@ -1,14 +1,14 @@
|
||||
use ::rltk::RandomNumberGenerator;
|
||||
use ::specs::prelude::*;
|
||||
|
||||
use crate::colors;
|
||||
use crate::components::{
|
||||
Attributes, Equipped, HungerClock, HungerState, MeleeWeapon, Name, Pools, Skill, Skills,
|
||||
SufferDamage, WantsToMelee, Wearable,
|
||||
Attributes, EquipmentSlot, Equipped, HungerClock, HungerState, MeleeWeapon, Name,
|
||||
NaturalAttackDefense, Pools, Skill, Skills, WantsToMelee, WeaponAttribute, Wearable,
|
||||
};
|
||||
use crate::effects::{add_effect, EffectType, Targets};
|
||||
use crate::game_log::GameLog;
|
||||
use crate::gamesystem::skill_bonus;
|
||||
use crate::particle_system::ParticleBuilder;
|
||||
use crate::{colors, EquipmentSlot, NaturalAttackDefense, Position, WeaponAttribute};
|
||||
|
||||
pub struct MeleeCombatSystem {}
|
||||
|
||||
@ -21,9 +21,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||
ReadStorage<'a, Name>,
|
||||
ReadStorage<'a, Attributes>,
|
||||
ReadStorage<'a, Skills>,
|
||||
WriteStorage<'a, SufferDamage>,
|
||||
WriteExpect<'a, ParticleBuilder>,
|
||||
ReadStorage<'a, Position>,
|
||||
ReadStorage<'a, HungerClock>,
|
||||
ReadStorage<'a, Pools>,
|
||||
WriteExpect<'a, RandomNumberGenerator>,
|
||||
@ -31,7 +28,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||
ReadStorage<'a, MeleeWeapon>,
|
||||
ReadStorage<'a, Wearable>,
|
||||
ReadStorage<'a, NaturalAttackDefense>,
|
||||
ReadExpect<'a, Entity>,
|
||||
);
|
||||
|
||||
fn run(&mut self, data: Self::SystemData) {
|
||||
@ -42,9 +38,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||
names,
|
||||
attributes,
|
||||
skills,
|
||||
mut inflict_damage,
|
||||
mut particle_builder,
|
||||
positions,
|
||||
hunger_clock,
|
||||
pools,
|
||||
mut rng,
|
||||
@ -52,7 +45,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||
meleeweapons,
|
||||
wearables,
|
||||
natural,
|
||||
player_entity,
|
||||
) = data;
|
||||
|
||||
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in (
|
||||
@ -156,62 +148,56 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||
+ skill_damage_bonus
|
||||
+ weapon_damage_bonus,
|
||||
);
|
||||
SufferDamage::new_damage(
|
||||
&mut inflict_damage,
|
||||
wants_melee.target,
|
||||
damage,
|
||||
entity == *player_entity,
|
||||
add_effect(
|
||||
Some(entity),
|
||||
EffectType::Damage { amount: damage },
|
||||
Targets::Single {
|
||||
target: wants_melee.target,
|
||||
},
|
||||
);
|
||||
log.append(format!(
|
||||
"{} hits {} for {} hp.",
|
||||
&name.name, &target_name.name, damage
|
||||
));
|
||||
|
||||
if let Some(pos) = positions.get(wants_melee.target) {
|
||||
particle_builder.request(
|
||||
pos.x,
|
||||
pos.y,
|
||||
colors::ORANGE,
|
||||
colors::BLACK,
|
||||
rltk::to_cp437('‼'),
|
||||
200.0,
|
||||
);
|
||||
}
|
||||
} else if natural_roll == 1 {
|
||||
// Natural 1 miss
|
||||
log.append(format!(
|
||||
"{} considers attacking {}, but misjudges the timing.",
|
||||
name.name, target_name.name
|
||||
));
|
||||
if let Some(pos) = positions.get(wants_melee.target) {
|
||||
particle_builder.request(
|
||||
pos.x,
|
||||
pos.y,
|
||||
colors::BLUE,
|
||||
colors::BLACK,
|
||||
rltk::to_cp437('‼'),
|
||||
200.0,
|
||||
add_effect(
|
||||
None,
|
||||
EffectType::Particle {
|
||||
glyph: rltk::to_cp437('‼'),
|
||||
fg: colors::BLUE,
|
||||
bg: colors::BLACK,
|
||||
lifespan: 200.0,
|
||||
},
|
||||
Targets::Single {
|
||||
target: wants_melee.target,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Miss
|
||||
log.append(format!(
|
||||
"{} attacks {}, but can't connect",
|
||||
name.name, target_name.name
|
||||
));
|
||||
if let Some(pos) = positions.get(wants_melee.target) {
|
||||
particle_builder.request(
|
||||
pos.x,
|
||||
pos.y,
|
||||
colors::CYAN,
|
||||
colors::BLACK,
|
||||
rltk::to_cp437('‼'),
|
||||
200.0,
|
||||
add_effect(
|
||||
None,
|
||||
EffectType::Particle {
|
||||
glyph: rltk::to_cp437('‼'),
|
||||
fg: colors::CYAN,
|
||||
bg: colors::BLACK,
|
||||
lifespan: 200.0,
|
||||
},
|
||||
Targets::Single {
|
||||
target: wants_melee.target,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wants_melee.clear();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ use crate::player::*;
|
||||
use crate::raws::*;
|
||||
use crate::trigger_system::TriggerSystem;
|
||||
use crate::visibility_system::VisibilitySystem;
|
||||
use crate::{ai, camera, saveload_system, spawner};
|
||||
use crate::{ai, camera, effects, saveload_system, spawner};
|
||||
|
||||
pub const SHOW_MAPGEN_VISUALIZER: bool = false;
|
||||
|
||||
@ -139,6 +139,8 @@ impl State {
|
||||
let mut hunger = HungerSystem {};
|
||||
hunger.run_now(&self.ecs);
|
||||
|
||||
effects::run_effects_queue(&mut self.ecs);
|
||||
|
||||
let mut particles = ParticleSpawnSystem {};
|
||||
particles.run_now(&self.ecs);
|
||||
|
||||
|
@ -2,8 +2,9 @@ use ::specs::prelude::*;
|
||||
|
||||
use crate::components::{
|
||||
ApplyTeleport, EntityMoved, EntryTrigger, Hidden, InflictsDamage, Name, Position,
|
||||
SingleActivation, SufferDamage, TeleportTo,
|
||||
SingleActivation, TeleportTo,
|
||||
};
|
||||
use crate::effects::{add_effect, EffectType, Targets};
|
||||
use crate::game_log::GameLog;
|
||||
use crate::particle_system::ParticleBuilder;
|
||||
use crate::{colors, spatial, Map};
|
||||
@ -23,7 +24,6 @@ impl<'a> System<'a> for TriggerSystem {
|
||||
WriteExpect<'a, GameLog>,
|
||||
ReadStorage<'a, InflictsDamage>,
|
||||
WriteExpect<'a, ParticleBuilder>,
|
||||
WriteStorage<'a, SufferDamage>,
|
||||
ReadStorage<'a, SingleActivation>,
|
||||
ReadStorage<'a, TeleportTo>,
|
||||
WriteStorage<'a, ApplyTeleport>,
|
||||
@ -42,7 +42,6 @@ impl<'a> System<'a> for TriggerSystem {
|
||||
mut log,
|
||||
inflicts_damage,
|
||||
mut particle_builder,
|
||||
mut inflict_damage,
|
||||
single_activation,
|
||||
teleporters,
|
||||
mut apply_teleport,
|
||||
@ -79,12 +78,13 @@ impl<'a> System<'a> for TriggerSystem {
|
||||
200.0,
|
||||
);
|
||||
|
||||
SufferDamage::new_damage(
|
||||
&mut inflict_damage,
|
||||
entity,
|
||||
damage.damage,
|
||||
false,
|
||||
);
|
||||
add_effect(
|
||||
None,
|
||||
EffectType::Damage {
|
||||
amount: damage.damage,
|
||||
},
|
||||
Targets::Single { target: entity },
|
||||
)
|
||||
}
|
||||
|
||||
// If it's a teleporter, then do that
|
||||
|
Loading…
Reference in New Issue
Block a user