diff --git a/assets/common/abilities/sceptre/lifestealbeam.ron b/assets/common/abilities/sceptre/lifestealbeam.ron index a1456f1021..62d68c8729 100644 --- a/assets/common/abilities/sceptre/lifestealbeam.ron +++ b/assets/common/abilities/sceptre/lifestealbeam.ron @@ -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, diff --git a/assets/common/abilities/staff/flamethrower.ron b/assets/common/abilities/staff/flamethrower.ron index 060c23f821..9ea6a41c73 100644 --- a/assets/common/abilities/staff/flamethrower.ron +++ b/assets/common/abilities/staff/flamethrower.ron @@ -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, diff --git a/assets/common/abilities/staffsimple/flamethrower.ron b/assets/common/abilities/staffsimple/flamethrower.ron index bbf0ce6783..b6c65fc6d4 100644 --- a/assets/common/abilities/staffsimple/flamethrower.ron +++ b/assets/common/abilities/staffsimple/flamethrower.ron @@ -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, diff --git a/assets/common/abilities/unique/mindflayer/cursedflames.ron b/assets/common/abilities/unique/mindflayer/cursedflames.ron new file mode 100644 index 0000000000..0fdf731d35 --- /dev/null +++ b/assets/common/abilities/unique/mindflayer/cursedflames.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/unique/mindflayer/dimensionaldoor.ron b/assets/common/abilities/unique/mindflayer/dimensionaldoor.ron new file mode 100644 index 0000000000..ca0309ede7 --- /dev/null +++ b/assets/common/abilities/unique/mindflayer/dimensionaldoor.ron @@ -0,0 +1 @@ +BasicBlock \ No newline at end of file diff --git a/assets/common/abilities/unique/mindflayer/necroticvortex.ron b/assets/common/abilities/unique/mindflayer/necroticvortex.ron new file mode 100644 index 0000000000..ca0309ede7 --- /dev/null +++ b/assets/common/abilities/unique/mindflayer/necroticvortex.ron @@ -0,0 +1 @@ +BasicBlock \ No newline at end of file diff --git a/assets/common/abilities/unique/mindflayer/raiseundead.ron b/assets/common/abilities/unique/mindflayer/raiseundead.ron new file mode 100644 index 0000000000..ca0309ede7 --- /dev/null +++ b/assets/common/abilities/unique/mindflayer/raiseundead.ron @@ -0,0 +1 @@ +BasicBlock \ No newline at end of file diff --git a/assets/common/abilities/unique/quadlowbeam/healingbeam.ron b/assets/common/abilities/unique/quadlowbeam/healingbeam.ron index b3b5755ebf..6776a79944 100644 --- a/assets/common/abilities/unique/quadlowbeam/healingbeam.ron +++ b/assets/common/abilities/unique/quadlowbeam/healingbeam.ron @@ -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, diff --git a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron index 8d81986e27..4bf507fa86 100644 --- a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron +++ b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron @@ -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, diff --git a/assets/common/abilities/unique/tidalclaws/bubbles.ron b/assets/common/abilities/unique/tidalclaws/bubbles.ron index f8df4454c5..b51aab85a9 100644 --- a/assets/common/abilities/unique/tidalclaws/bubbles.ron +++ b/assets/common/abilities/unique/tidalclaws/bubbles.ron @@ -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, diff --git a/assets/common/abilities/unique/turret/flamethrower.ron b/assets/common/abilities/unique/turret/flamethrower.ron index 6b865f8bfa..ef422619c1 100644 --- a/assets/common/abilities/unique/turret/flamethrower.ron +++ b/assets/common/abilities/unique/turret/flamethrower.ron @@ -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, diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index 336023513d..d34b8651e0 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -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", diff --git a/assets/common/items/npc_weapons/staff/mindflayer_staff.ron b/assets/common/items/npc_weapons/staff/mindflayer_staff.ron index 64c5c01a1f..de96e3ecd7 100644 --- a/assets/common/items/npc_weapons/staff/mindflayer_staff.ron +++ b/assets/common/items/npc_weapons/staff/mindflayer_staff.ron @@ -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, diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index a7b6dfcd3a..0d747368f4 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -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( diff --git a/common/src/combat.rs b/common/src/combat.rs index 230e09776e..6a4fcf21fa 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -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), diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 038da4ac05..b0da9b5097 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -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, 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, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 2ad006672f..da888d03d0 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -51,4 +51,5 @@ pub enum FrontendSpecifier { Flamethrower, LifestealBeam, HealingBeam, + Cultist, } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 91a103e918..5d5f76a187 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -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 => ( diff --git a/common/src/comp/health.rs b/common/src/comp/health.rs index d32e9b7228..d13e612844 100644 --- a/common/src/comp/health.rs +++ b/common/src/comp/health.rs @@ -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"))] diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index c17c904399..8c4fa2c9f4 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -426,4 +426,5 @@ pub enum UniqueKind { TheropodCharge, ObjectTurret, WoodenSpear, + MindflayerStaff, } diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 365c0d40bf..84893fe7ee 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -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 { diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 567340cf27..766959470f 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -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, /// 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); diff --git a/common/src/states/healing_beam.rs b/common/src/states/healing_beam.rs index 96ca50b7b5..23caaded52 100644 --- a/common/src/states/healing_beam.rs +++ b/common/src/states/healing_beam.rs @@ -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); diff --git a/common/sys/src/buff.rs b/common/sys/src/buff.rs index a701ea8aca..9d379af810 100644 --- a/common/sys/src/buff.rs +++ b/common/sys/src/buff.rs @@ -19,6 +19,7 @@ pub struct ReadData<'a> { dt: Read<'a, DeltaTime>, server_bus: Read<'a, EventBus>, 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, - (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); } diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index b42c7af872..2304691d95 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -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 diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index b86acfa1b6..2900ef4559 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -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); }, diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index d798bde320..d24f8a8a57 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -119,6 +119,7 @@ pub enum ParticleMode { Explosion = 20, Ice = 21, LifestealBeam = 22, + CultistFlame = 23, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 1fa34f5f2b..20c5d6a2dd 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -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::::unit_z(), *ori.look_dir()); + let m = Mat3::::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::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .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::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized() + * 0.25; + let end_pos = start_pos + + Vec3::unit_z() * body.height() + + Vec3::::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,