Added cursed flame attack to mindflayer

This commit is contained in:
Sam 2021-03-20 13:29:57 -04:00
parent f8f1d19684
commit 0d3795112c
28 changed files with 258 additions and 91 deletions

View File

@ -6,7 +6,7 @@ BasicBeam(
tick_rate: 2.0,
range: 25.0,
max_angle: 1.0,
lifesteal_eff: 0.15,
damage_effect: Some(Lifesteal(0.15)),
energy_regen: 50,
energy_drain: 0,
orientation_behavior: Normal,

View File

@ -5,8 +5,8 @@ BasicBeam(
damage: 50,
tick_rate: 3.0,
range: 20.0,
max_angle: 10.0,
lifesteal_eff: 0.0,
max_angle: 15.0,
damage_effect: None,
energy_regen: 0,
energy_drain: 350,
orientation_behavior: Normal,

View File

@ -5,8 +5,8 @@ BasicBeam(
damage: 50,
tick_rate: 3.0,
range: 20.0,
max_angle: 0.1,
lifesteal_eff: 0.0,
max_angle: 15.0,
damage_effect: None,
energy_regen: 0,
energy_drain: 350,
orientation_behavior: Normal,

View File

@ -0,0 +1,19 @@
BasicBeam(
buildup_duration: 0.50,
recover_duration: 0.50,
beam_duration: 0.5,
damage: 200,
tick_rate: 2.0,
range: 10.0,
max_angle: 15.0,
damage_effect: Some(Buff((
kind: Cursed,
dur_secs: 15.0,
strength: Value(0.5),
chance: 1.0,
))),
energy_regen: 0,
energy_drain: 0,
orientation_behavior: Normal,
specifier: Cultist,
)

View File

@ -0,0 +1 @@
BasicBlock

View File

@ -0,0 +1 @@
BasicBlock

View File

@ -0,0 +1 @@
BasicBlock

View File

@ -6,7 +6,7 @@ BasicBeam(
tick_rate: 2.0,
range: 25.0,
max_angle: 1.0,
lifesteal_eff: 0.15,
damage_effect: Some(Lifesteal(0.15)),
energy_regen: 25,
energy_drain: 0,
orientation_behavior: Normal,

View File

@ -6,7 +6,7 @@ BasicBeam(
tick_rate: 3.0,
range: 15.0,
max_angle: 22.5,
lifesteal_eff: 0.0,
damage_effect: None,
energy_regen: 0,
energy_drain: 0,
orientation_behavior: Normal,

View File

@ -7,7 +7,7 @@ BasicBeam(
tick_rate: 3.0,
range: 15.0,
max_angle: 22.5,
lifesteal_eff: 0.0,
damage_effect: None,
energy_regen: 0,
energy_cost: 0,
energy_drain: 0,

View File

@ -5,8 +5,8 @@ BasicBeam(
damage: 3000,
tick_rate: 3.0,
range: 30.0,
max_angle: 1.0,
lifesteal_eff: 0.0,
max_angle: 15.0,
damage_effect: None,
energy_regen: 0,
energy_drain: 0,
orientation_behavior: Turret,

View File

@ -166,7 +166,7 @@
primary: "common.abilities.unique.quadlowbeam.healingbeam",
secondary: "common.abilities.unique.quadlowbreathe.triplestrike",
abilities: [
(None, "common.abilities.unique.quadlowbreathe.dash"),
(None, "common.abilities.unique.quadlowbreathe.dash"),
],
),
Unique(QuadSmallBasic): (
@ -194,6 +194,14 @@
secondary: "common.abilities.unique.turret.arrows",
abilities: [],
),
Unique(MindflayerStaff): (
primary: "common.abilities.unique.mindflayer.cursedflames",
secondary: "common.abilities.unique.mindflayer.necroticvortex",
abilities: [
(None, "common.abilities.unique.mindflayer.dimensionaldoor"),
(None, "common.abilities.unique.mindflayer.raiseundead"),
],
),
Debug: (
primary: "common.abilities.debug.forwardboost",
secondary: "common.abilities.debug.upboost",

View File

@ -2,15 +2,15 @@ ItemDef(
name: "Mindflayer Staff",
description: "Placeholder",
kind: Tool((
kind: StaffSimple,
kind: Unique(MindflayerStaff),
hands: Two,
stats: Direct((
equip_time_secs: 0.001,
power: 3.0,
equip_time_secs: 0.01,
power: 1.0,
poise_strength: 1.0,
speed: 1.5,
crit_chance: 0.15,
crit_mult: 1.2539682,
speed: 1.0,
crit_chance: 0.0,
crit_mult: 1.0,
)),
)),
quality: Legendary,

View File

@ -61,6 +61,7 @@ const int SNOW = 19;
const int EXPLOSION = 20;
const int ICE = 21;
const int LIFESTEAL_BEAM = 22;
const int CULTIST_FLAME = 23;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -363,8 +364,8 @@ void main() {
} else if (inst_mode == FLAMETHROWER) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (lifetime * 5 + 0.25),
vec3((2.5 * (1 - slow_start(0.3)))),
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((2.5 * (1 - slow_start(0.2)))),
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
@ -392,6 +393,15 @@ void main() {
vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
spin_in_axis(vec3(rand3, rand4, rand5), rand6)
);
} else if (inst_mode == CULTIST_FLAME) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
float purp_color = 0.8 + 0.5 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((2.5 * (1 - slow_start(0.2)))),
vec4(purp_color, 0.0, purp_color, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else {
attr = Attr(
linear_motion(

View File

@ -411,7 +411,7 @@ impl AttackEffect {
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub enum CombatEffect {
Heal(f32),
Buff(CombatBuff),
@ -647,7 +647,7 @@ impl Knockback {
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub struct CombatBuff {
pub kind: BuffKind,
pub dur_secs: f32,
@ -656,7 +656,7 @@ pub struct CombatBuff {
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub enum CombatBuffStrength {
DamageFraction(f32),
Value(f32),

View File

@ -1,6 +1,6 @@
use crate::{
assets::{self, Asset},
combat,
combat::{self, CombatEffect},
comp::{
aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills,
Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
@ -223,7 +223,7 @@ pub enum CharacterAbility {
tick_rate: f32,
range: f32,
max_angle: f32,
lifesteal_eff: f32,
damage_effect: Option<CombatEffect>,
energy_regen: f32,
energy_drain: f32,
orientation_behavior: basic_beam::MovementBehavior,
@ -1008,7 +1008,7 @@ impl CharacterAbility {
ref mut damage,
ref mut range,
ref mut beam_duration,
ref mut lifesteal_eff,
ref mut damage_effect,
ref mut energy_regen,
..
} => {
@ -1024,8 +1024,10 @@ impl CharacterAbility {
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LRegen)) {
*energy_regen *= 1.25_f32.powi(level.into());
}
if let Ok(Some(level)) = skillset.skill_level(Sceptre(LLifesteal)) {
*lifesteal_eff *= 1.3_f32.powi(level.into());
if let (Ok(Some(level)), Some(CombatEffect::Lifesteal(ref mut lifesteal))) =
(skillset.skill_level(Sceptre(LLifesteal)), damage_effect)
{
*lifesteal *= 1.3_f32.powi(level.into());
}
},
HealingBeam {
@ -1479,7 +1481,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
tick_rate,
range,
max_angle,
lifesteal_eff,
damage_effect,
energy_regen,
energy_drain,
orientation_behavior,
@ -1493,7 +1495,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
tick_rate: *tick_rate,
range: *range,
max_angle: *max_angle,
lifesteal_eff: *lifesteal_eff,
damage_effect: *damage_effect,
energy_regen: *energy_regen,
energy_drain: *energy_drain,
ability_info,

View File

@ -51,4 +51,5 @@ pub enum FrontendSpecifier {
Flamethrower,
LifestealBeam,
HealingBeam,
Cultist,
}

View File

@ -20,8 +20,7 @@ pub enum BuffKind {
Saturation,
/// Lowers health over time for some duration
Bleeding,
/// Lower a creature's max health
/// Currently placeholder buff to show other stuff is possible
/// Lower a creature's max health over time
Cursed,
/// Applied when drinking a potion
Potion,
@ -109,6 +108,13 @@ pub enum BuffEffect {
MaxEnergyModifier { value: f32, kind: ModifierKind },
/// Reduces damage after armor is accounted for by this fraction
DamageReduction(f32),
/// Gradually changes an entities max health over time
MaxHealthChangeOverTime {
rate: f32,
accumulated: f32,
kind: ModifierKind,
target_fraction: f32,
},
}
/// Actual de/buff.
@ -191,10 +197,19 @@ impl Buff {
data.duration,
),
BuffKind::Cursed => (
vec![BuffEffect::MaxHealthModifier {
value: -100. * data.strength,
kind: ModifierKind::Additive,
}],
vec![
BuffEffect::MaxHealthChangeOverTime {
rate: -10.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
target_fraction: 1.0 - data.strength,
},
BuffEffect::HealthChangeOverTime {
rate: -10.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
},
],
data.duration,
),
BuffKind::IncreaseMaxEnergy => (

View File

@ -36,10 +36,9 @@ pub enum HealthSource {
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Health {
base_max: u32,
current: u32,
base_max: u32,
maximum: u32,
last_max: u32,
pub last_change: (f64, HealthChange),
pub is_dead: bool,
}
@ -58,9 +57,8 @@ impl Health {
pub fn empty() -> Self {
Health {
current: 0,
maximum: 0,
base_max: 0,
last_max: 0,
maximum: 0,
last_change: (0.0, HealthChange {
amount: 0,
cause: HealthSource::Revive,
@ -73,6 +71,8 @@ impl Health {
pub fn maximum(&self) -> u32 { self.maximum }
pub fn base_max(&self) -> u32 { self.base_max }
#[cfg(not(target_arch = "wasm32"))]
pub fn set_to(&mut self, amount: u32, cause: HealthSource) {
let amount = amount.min(self.maximum);
@ -97,6 +97,12 @@ impl Health {
self.current = self.current.min(self.maximum);
}
// Scales the temporary max health by a modifier.
pub fn scale_maximum(&mut self, scaled: f32) {
let scaled_max = (self.base_max as f32 * scaled) as u32;
self.set_maximum(scaled_max);
}
// This is private because max hp is based on the level
#[cfg(not(target_arch = "wasm32"))]
fn set_base_max(&mut self, amount: u32) {
@ -124,26 +130,6 @@ impl Health {
});
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn with_max_health(mut self, amount: u32) -> Self {
self.maximum = amount;
self.current = amount;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn last_set(&mut self) { self.last_max = self.maximum }
#[cfg(not(target_arch = "wasm32"))]
pub fn reset_max(&mut self) {
self.maximum = self.base_max;
if self.current > self.last_max {
self.current = self.last_max;
self.last_max = self.base_max;
}
}
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -426,4 +426,5 @@ pub enum UniqueKind {
TheropodCharge,
ObjectTurret,
WoodenSpear,
MindflayerStaff,
}

View File

@ -28,6 +28,7 @@ pub struct Stats {
// potentially be updated every tick (especially as more buffs are added)
pub skill_set: SkillSet,
pub damage_reduction: f32,
pub max_health_modifier: f32,
}
impl Stats {
@ -36,6 +37,7 @@ impl Stats {
name,
skill_set: SkillSet::default(),
damage_reduction: 0.0,
max_health_modifier: 1.0,
}
}
@ -46,8 +48,15 @@ impl Stats {
name: "".to_owned(),
skill_set: SkillSet::default(),
damage_reduction: 0.0,
max_health_modifier: 1.0,
}
}
/// Resets temporary modifiers to default values
pub fn reset_temp_modifiers(&mut self) {
self.damage_reduction = 0.0;
self.max_health_modifier = 1.0;
}
}
impl Component for Stats {

View File

@ -32,9 +32,8 @@ pub struct StaticData {
pub range: f32,
/// Max angle (45.0 will give you a 90.0 angle window)
pub max_angle: f32,
/// Lifesteal efficiency (0 gives 0% conversion of damage to health, 1 gives
/// 100% conversion of damage to health)
pub lifesteal_eff: f32,
/// Adds an effect onto the main damage of the attack
pub damage_effect: Option<CombatEffect>,
/// Energy regenerated per tick
pub energy_regen: f32,
/// Energy drained per second
@ -111,15 +110,16 @@ impl CharacterBehavior for Data {
CombatEffect::EnergyReward(self.static_data.energy_regen),
)
.with_requirement(CombatRequirement::AnyDamage);
let lifesteal = CombatEffect::Lifesteal(self.static_data.lifesteal_eff);
let damage = AttackDamage::new(
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Energy,
value: self.static_data.damage,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(lifesteal);
);
if let Some(effect) = self.static_data.damage_effect {
damage = damage.with_effect(effect);
}
let (crit_chance, crit_mult) =
get_crit_data(data, self.static_data.ability_info);
let attack = Attack::default()
@ -138,8 +138,8 @@ impl CharacterBehavior for Data {
};
// Gets offsets
let body_offsets = Vec3::new(
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
(data.body.radius() + 0.2) * data.inputs.look_dir.x,
(data.body.radius() + 0.2) * data.inputs.look_dir.y,
data.body.eye_height() * 0.6,
);
let pos = Pos(data.pos.0 + body_offsets);

View File

@ -103,8 +103,8 @@ impl CharacterBehavior for Data {
};
// Gets offsets
let body_offsets = Vec3::new(
(data.body.radius() + 1.0) * data.inputs.look_dir.x,
(data.body.radius() + 1.0) * data.inputs.look_dir.y,
(data.body.radius() + 0.2) * data.inputs.look_dir.x,
(data.body.radius() + 0.2) * data.inputs.look_dir.y,
data.body.eye_height() * 0.6,
);
let pos = Pos(data.pos.0 + body_offsets);

View File

@ -19,6 +19,7 @@ pub struct ReadData<'a> {
dt: Read<'a, DeltaTime>,
server_bus: Read<'a, EventBus<ServerEvent>>,
inventories: ReadStorage<'a, Inventory>,
healths: ReadStorage<'a, Health>,
}
#[derive(Default)]
@ -26,7 +27,6 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
ReadData<'a>,
WriteStorage<'a, Health>,
WriteStorage<'a, Energy>,
WriteStorage<'a, Buffs>,
WriteStorage<'a, Stats>,
@ -38,22 +38,20 @@ impl<'a> System<'a> for Sys {
fn run(
_job: &mut Job<Self>,
(read_data, mut healths, mut energies, mut buffs, mut stats): Self::SystemData,
(read_data, mut energies, mut buffs, mut stats): Self::SystemData,
) {
let mut server_emitter = read_data.server_bus.emitter();
let dt = read_data.dt.0;
// Set to false to avoid spamming server
buffs.set_event_emission(false);
healths.set_event_emission(false);
energies.set_event_emission(false);
healths.set_event_emission(false);
stats.set_event_emission(false);
for (entity, mut buff_comp, mut health, mut energy, mut stat) in (
for (entity, mut buff_comp, mut energy, mut stat, health) in (
&read_data.entities,
&mut buffs,
&mut healths,
&mut energies,
&mut stats,
&read_data.healths,
)
.join()
{
@ -89,12 +87,10 @@ impl<'a> System<'a> for Sys {
}
}
// Call to reset health and energy to base values
health.last_set();
// Call to reset energy and stats to base values
energy.last_set();
health.reset_max();
energy.reset_max();
stat.damage_reduction = 0.0;
stat.reset_temp_modifiers();
// Iterator over the lists of buffs by kind
let buff_comp = &mut *buff_comp;
@ -117,9 +113,9 @@ impl<'a> System<'a> for Sys {
kind,
} => {
*accumulated += *rate * dt;
// Apply health change only once a second or
// Apply health change only once per second, per health, or
// when a buff is removed
if accumulated.abs() > rate.abs()
if accumulated.abs() > rate.abs().min(10.0)
|| buff.time.map_or(false, |dur| dur == Duration::default())
{
let cause = if *accumulated > 0.0 {
@ -145,14 +141,10 @@ impl<'a> System<'a> for Sys {
},
BuffEffect::MaxHealthModifier { value, kind } => match kind {
ModifierKind::Additive => {
let health = &mut *health;
let buffed_health_max =
(health.maximum() as f32 + *value) as u32;
health.set_maximum(buffed_health_max);
stat.max_health_modifier += *value / (health.maximum() as f32);
},
ModifierKind::Fractional => {
let health = &mut *health;
health.set_maximum((health.maximum() as f32 * *value) as u32);
stat.max_health_modifier *= *value;
},
},
BuffEffect::MaxEnergyModifier { value, kind } => match kind {
@ -168,6 +160,35 @@ impl<'a> System<'a> for Sys {
BuffEffect::DamageReduction(dr) => {
stat.damage_reduction = stat.damage_reduction.max(*dr).min(1.0);
},
BuffEffect::MaxHealthChangeOverTime {
rate,
accumulated,
kind,
target_fraction,
} => {
*accumulated += *rate * dt;
let current_fraction = health.maximum() as f32
/ (health.base_max() as f32 * stat.max_health_modifier);
let progress = (1.0 - current_fraction) / (1.0 - *target_fraction);
if progress > 1.0 {
stat.max_health_modifier *= *target_fraction;
} else if accumulated.abs() > rate.abs() {
match kind {
ModifierKind::Additive => {
stat.max_health_modifier = stat.max_health_modifier
* current_fraction
+ *accumulated / health.maximum() as f32;
},
ModifierKind::Fractional => {
stat.max_health_modifier *=
current_fraction * (1.0 - *accumulated);
},
}
*accumulated = 0.0;
} else {
stat.max_health_modifier *= current_fraction;
}
},
};
}
}
@ -195,7 +216,6 @@ impl<'a> System<'a> for Sys {
}
// Turned back to true
buffs.set_event_emission(true);
healths.set_event_emission(true);
energies.set_event_emission(true);
stats.set_event_emission(true);
}

View File

@ -104,6 +104,18 @@ impl<'a> System<'a> for Sys {
}
let stat = stats.get_unchecked();
let update_max_hp = {
let health = health.get_unchecked();
(stat.max_health_modifier - 1.0).abs() > f32::EPSILON
|| health.base_max() != health.maximum()
};
if update_max_hp {
let mut health = health.get_mut_unchecked();
health.scale_maximum(stat.max_health_modifier);
}
let skills_to_level = stat
.skill_set
.skill_groups

View File

@ -359,7 +359,7 @@ impl SfxMgr {
let file_ref = "voxygen.audio.sfx.abilities.staff_channeling";
audio.play_sfx(file_ref, *pos, None);
},
beam::FrontendSpecifier::Flamethrower => {
beam::FrontendSpecifier::Flamethrower | beam::FrontendSpecifier::Cultist => {
let file_ref = "voxygen.audio.sfx.abilities.flame_thrower";
audio.play_sfx(file_ref, *pos, None);
},

View File

@ -119,6 +119,7 @@ pub enum ParticleMode {
Explosion = 20,
Ice = 21,
LifestealBeam = 22,
CultistFlame = 23,
}
impl ParticleMode {

View File

@ -194,6 +194,7 @@ impl ParticleMgr {
self.maintain_block_particles(scene_data, terrain);
self.maintain_shockwave_particles(scene_data);
self.maintain_aura_particles(scene_data);
self.maintain_buff_particles(scene_data);
} else {
// remove all particle lifespans
self.particles.clear();
@ -477,7 +478,7 @@ impl ParticleMgr {
2.0,
));
self.particles.resize_with(
self.particles.len() + 2 * usize::from(beam_tick_count),
self.particles.len() + usize::from(beam_tick_count) / 2,
|| {
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
@ -497,6 +498,37 @@ impl ParticleMgr {
},
);
},
beam::FrontendSpecifier::Cultist => {
let mut rng = thread_rng();
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
let m = Mat3::<f32>::rotation_from_to_3d(from, to);
// Emit a light when using flames
lights.push(Light::new(
pos.0,
Rgb::new(1.0, 0.0, 1.0).map(|e| e * rng.gen_range(0.5..1.0)),
2.0,
));
self.particles.resize_with(
self.particles.len() + usize::from(beam_tick_count) / 2,
|| {
let phi: f32 = rng.gen_range(0.0..beam.properties.angle);
let theta: f32 = rng.gen_range(0.0..2.0 * PI);
let offset_z = Vec3::new(
phi.sin() * theta.cos(),
phi.sin() * theta.sin(),
phi.cos(),
);
let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0);
Particle::new_directed(
beam.properties.duration,
time,
ParticleMode::CultistFlame,
pos.0,
pos.0 + random_ori * range,
)
},
);
},
beam::FrontendSpecifier::HealingBeam => {
// Emit a light when using healing
lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0));
@ -570,6 +602,54 @@ impl ParticleMgr {
}
}
fn maintain_buff_particles(&mut self, scene_data: &SceneData) {
let state = scene_data.state;
let ecs = state.ecs();
let time = state.get_time();
let mut rng = rand::thread_rng();
for (pos, buffs, body) in (
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<comp::Buffs>(),
&ecs.read_storage::<comp::Body>(),
)
.join()
{
for (buff_kind, _) in buffs.kinds.iter() {
#[allow(clippy::single_match)]
match buff_kind {
buff::BuffKind::Cursed => {
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|| {
let start_pos = pos.0
+ Vec3::unit_z() * body.height() * 0.25
+ Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized()
* 0.25;
let end_pos = start_pos
+ Vec3::unit_z() * body.height()
+ Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized();
Particle::new_directed(
Duration::from_secs(1),
time,
ParticleMode::CultistFlame,
start_pos,
end_pos,
)
},
);
},
_ => {},
}
}
}
}
#[allow(clippy::same_item_push)] // TODO: Pending review in #587
fn maintain_block_particles(
&mut self,