diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 41b0c565d9..095b305219 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -79,7 +79,7 @@ ), Tool(Sceptre): ( primary: "common.abilities.sceptre.lifestealbeam", - secondary: "common.abilities.sceptre.healingbeam", + secondary: "common.abilities.sceptre.healingaura", abilities: [ (Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"), ], @@ -191,7 +191,7 @@ abilities: [], ), Custom("Quad Low Beam"): ( - primary: "common.abilities.custom.quadlowbeam.healingbeam", + primary: "common.abilities.custom.quadlowbeam.lifestealbeam", secondary: "common.abilities.custom.quadlowbreathe.triplestrike", abilities: [ (None, "common.abilities.custom.quadlowbreathe.dash"), diff --git a/assets/common/abilities/custom/quadlowbeam/healingbeam.ron b/assets/common/abilities/custom/quadlowbeam/lifestealbeam.ron similarity index 91% rename from assets/common/abilities/custom/quadlowbeam/healingbeam.ron rename to assets/common/abilities/custom/quadlowbeam/lifestealbeam.ron index 12d8b0e646..97262f80f0 100644 --- a/assets/common/abilities/custom/quadlowbeam/healingbeam.ron +++ b/assets/common/abilities/custom/quadlowbeam/lifestealbeam.ron @@ -11,5 +11,5 @@ BasicBeam( energy_drain: 0, orientation_behavior: Normal, ori_rate: 0.3, - specifier: HealingBeam, + specifier: LifestealBeam, ) diff --git a/assets/common/abilities/sceptre/healingaura.ron b/assets/common/abilities/sceptre/healingaura.ron new file mode 100644 index 0000000000..23465bed42 --- /dev/null +++ b/assets/common/abilities/sceptre/healingaura.ron @@ -0,0 +1,17 @@ +BasicAura( + buildup_duration: 0.25, + cast_duration: 0.5, + recover_duration: 0.25, + targets: InGroup, + aura: ( + kind: Regeneration, + strength: 2.0, + duration: Some(10.0), + category: Magical, + ), + aura_duration: 1.0, + range: 25.0, + energy_cost: 200, + scales_with_combo: true, + specifier: HealingAura, +) diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron deleted file mode 100644 index 59960b244f..0000000000 --- a/assets/common/abilities/sceptre/healingbeam.ron +++ /dev/null @@ -1,11 +0,0 @@ -HealingBeam( - buildup_duration: 0.25, - recover_duration: 0.25, - beam_duration: 1.0, - heal: 40, - tick_rate: 2.0, - range: 25.0, - max_angle: 1.0, - energy_cost: 75, - specifier: HealingBeam, -) \ No newline at end of file diff --git a/assets/common/abilities/sceptre/wardingaura.ron b/assets/common/abilities/sceptre/wardingaura.ron index 566cf6ec31..1785bbd971 100644 --- a/assets/common/abilities/sceptre/wardingaura.ron +++ b/assets/common/abilities/sceptre/wardingaura.ron @@ -12,4 +12,6 @@ BasicAura( aura_duration: 1.0, range: 25.0, energy_cost: 400, -) \ No newline at end of file + scales_with_combo: false, + specifier: WardingAura, +) diff --git a/assets/common/skill_trees/skill_max_levels.ron b/assets/common/skill_trees/skill_max_levels.ron index a319299903..4febf73b1e 100644 --- a/assets/common/skill_trees/skill_max_levels.ron +++ b/assets/common/skill_trees/skill_max_levels.ron @@ -64,8 +64,9 @@ Sceptre(LLifesteal): Some(3), Sceptre(LRegen): Some(2), Sceptre(HHeal): Some(3), - Sceptre(HCost): Some(2), Sceptre(HRange): Some(2), + Sceptre(HDuration): Some(2), + Sceptre(HCost): Some(2), Sceptre(AStrength): Some(2), Sceptre(ADuration): Some(2), Sceptre(ARange): Some(2), diff --git a/assets/common/skill_trees/skills_skill-groups_manifest.ron b/assets/common/skill_trees/skills_skill-groups_manifest.ron index 641ba74520..557eb2ea24 100644 --- a/assets/common/skill_trees/skills_skill-groups_manifest.ron +++ b/assets/common/skill_trees/skills_skill-groups_manifest.ron @@ -101,8 +101,9 @@ Sceptre(LLifesteal), Sceptre(LRegen), Sceptre(HHeal), - Sceptre(HCost), Sceptre(HRange), + Sceptre(HDuration), + Sceptre(HCost), Sceptre(UnlockAura), Sceptre(AStrength), Sceptre(ADuration), diff --git a/assets/common/skillset/dungeon/tier-5/sceptre.ron b/assets/common/skillset/dungeon/tier-5/sceptre.ron index 7900d41229..77d06e0ec8 100644 --- a/assets/common/skillset/dungeon/tier-5/sceptre.ron +++ b/assets/common/skillset/dungeon/tier-5/sceptre.ron @@ -9,9 +9,9 @@ // Heal Skill((Sceptre(HHeal), Some(1))), - Skill((Sceptre(HCost), Some(1))), + Skill((Sceptre(HDuration), Some(1))), Skill((Sceptre(HRange), Some(1))), - + Skill((Sceptre(HCost), Some(1))), // Ward Skill((Sceptre(UnlockAura), None)), Skill((Sceptre(AStrength), Some(1))), diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index bdec10a231..4717236d79 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -802,7 +802,7 @@ ], threshold: 0.2, ), - HealingBeam: ( + SceptreBeam: ( files: [ "voxygen.audio.sfx.abilities.sceptre_channeling", ], diff --git a/assets/voxygen/element/skills/heal_aoe.png b/assets/voxygen/element/skills/heal_aoe.png new file mode 100644 index 0000000000..85196ab0ac --- /dev/null +++ b/assets/voxygen/element/skills/heal_aoe.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fe8e4757a4cda7df99ed5c9f086ba2eb5d898eb65d4c948aa1b75859f08cd38 +size 545 diff --git a/assets/voxygen/element/skills/sceptre_protection.png b/assets/voxygen/element/skills/sceptre_protection.png index 76905fd479..a251bb2ff8 100644 --- a/assets/voxygen/element/skills/sceptre_protection.png +++ b/assets/voxygen/element/skills/sceptre_protection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecb86dcb348b7e62e130189854148d84aada28eb98c7d999cbf7cdd48ef10f58 -size 403 +oid sha256:38f2e8048b24edf73f61f1e05d242ca49fc08cf3e080a81b448947e2a172e3b0 +size 714 diff --git a/assets/voxygen/i18n/en/skills.ron b/assets/voxygen/i18n/en/skills.ron index cb4090e9cf..472af3e40e 100644 --- a/assets/voxygen/i18n/en/skills.ron +++ b/assets/voxygen/i18n/en/skills.ron @@ -54,14 +54,16 @@ "hud.skill.sc_lifesteal_lifesteal": "Convert an additional {boost}% of damage into health{SP}", "hud.skill.sc_lifesteal_regen_title": "Stamina Regen", "hud.skill.sc_lifesteal_regen": "Replenish your stamina by an additional {boost}%{SP}", - "hud.skill.sc_heal_title": "Healing Beam", - "hud.skill.sc_heal": "Heal your allies using the blood of your enemies", + "hud.skill.sc_heal_title": "Healing Aura", + "hud.skill.sc_heal": "Heal your allies using the blood of your enemies, requires combo to activate", "hud.skill.sc_heal_heal_title": "Heal", - "hud.skill.sc_heal_heal": "Increases the amount you heal others by {boost}%{SP}", + "hud.skill.sc_heal_heal": "Increases the amount you heal by {boost}%{SP}", "hud.skill.sc_heal_cost_title": "Stamina Cost", - "hud.skill.sc_heal_cost": "Healing others requires {boost}% less stamina{SP}", - "hud.skill.sc_heal_range_title": "Range", - "hud.skill.sc_heal_range": "Your beam reachs {boost}% further{SP}", + "hud.skill.sc_heal_cost": "Healing requires {boost}% less stamina{SP}", + "hud.skill.sc_heal_duration_title": "Duration", + "hud.skill.sc_heal_duration": "The effects of your healing aura last {boost}% longer{SP}", + "hud.skill.sc_heal_range_title": "Radius", + "hud.skill.sc_heal_range": "Your healing aura reachs {boost}% further{SP}", "hud.skill.sc_wardaura_unlock_title": "Warding Aura Unlock", "hud.skill.sc_wardaura_unlock": "Allows you to ward your allies against enemy attacks{SP}", "hud.skill.sc_wardaura_strength_title": "Strength", diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 7a2396d69f..8f2a494079 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -52,7 +52,7 @@ const int LEAF = 10; const int FIREFLY = 11; const int BEE = 12; const int GROUND_SHOCKWAVE = 13; -const int HEALING_BEAM = 14; +const int ENERGY_HEALING = 14; const int ENERGY_NATURE = 15; const int FLAMETHROWER = 16; const int FIRE_SHOCKWAVE = 17; @@ -380,13 +380,14 @@ void main() { spin_in_axis(vec3(1,0,0),0) ); break; - case HEALING_BEAM: + case ENERGY_HEALING: f_reflect = 0.0; + float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir); attr = Attr( - spiral_motion(inst_dir, 0.3 * (floor(2 * rand0 + 0.5) - 0.5) * min(linear_scale(10), 1), lifetime / inst_lifespan, 10.0, inst_time), - vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))), - vec4(vec3(0.4, 1.6 + 0.3 * sin(tick.x * 10 - lifetime * 3 + 4), 1.0 + 0.15 * sin(tick.x * 5 - lifetime * 5)), 1 /*0.3*/), - spin_in_axis(inst_dir, tick.z) + spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2), + vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)), + vec4(vec3(0, 1.7, 0.7), 1), + spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3) ); break; case LIFESTEAL_BEAM: @@ -402,7 +403,7 @@ void main() { break; case ENERGY_NATURE: f_reflect = 0.0; - float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir); + spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir); attr = Attr( spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2), vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)), diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 60293bf8d6..c3cb0be16a 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -264,17 +264,8 @@ pub enum CharacterAbility { aura_duration: f32, range: f32, energy_cost: f32, - }, - HealingBeam { - buildup_duration: f32, - recover_duration: f32, - beam_duration: f32, - heal: f32, - tick_rate: f32, - range: f32, - max_angle: f32, - energy_cost: f32, - specifier: beam::FrontendSpecifier, + scales_with_combo: bool, + specifier: aura::Specifier, }, Blink { buildup_duration: f32, @@ -356,7 +347,6 @@ impl CharacterAbility { | CharacterAbility::ChargedRanged { energy_cost, .. } | CharacterAbility::ChargedMelee { energy_cost, .. } | CharacterAbility::Shockwave { energy_cost, .. } - | CharacterAbility::BasicAura { energy_cost, .. } | CharacterAbility::BasicBlock { energy_cost, .. } | CharacterAbility::SelfBuff { energy_cost, .. } => update .energy @@ -373,7 +363,17 @@ impl CharacterAbility { .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok() }, - CharacterAbility::HealingBeam { .. } => data.combo.counter() > 0, + CharacterAbility::BasicAura { + energy_cost, + scales_with_combo, + .. + } => { + ((*scales_with_combo && data.combo.counter() > 0) | !*scales_with_combo) + && update + .energy + .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .is_ok() + }, CharacterAbility::ComboMelee { .. } | CharacterAbility::Boost { .. } | CharacterAbility::BasicBeam { .. } @@ -782,6 +782,8 @@ impl CharacterAbility { aura_duration: _, ref mut range, ref mut energy_cost, + scales_with_combo: _, + specifier: _, } => { *buildup_duration /= stats.speed; *cast_duration /= stats.speed; @@ -792,26 +794,6 @@ impl CharacterAbility { *range *= stats.range; *energy_cost /= stats.energy_efficiency; }, - HealingBeam { - ref mut buildup_duration, - ref mut recover_duration, - ref mut beam_duration, - ref mut heal, - ref mut tick_rate, - ref mut range, - max_angle: _, - ref mut energy_cost, - specifier: _, - } => { - *buildup_duration /= stats.speed; - *recover_duration /= stats.speed; - *heal *= stats.power; - *tick_rate *= stats.speed; - *range *= stats.range; - // Duration modified to keep velocity constant - *beam_duration *= stats.range; - *energy_cost /= stats.energy_efficiency; - }, Blink { ref mut buildup_duration, ref mut recover_duration, @@ -886,7 +868,6 @@ impl CharacterAbility { | ChargedMelee { energy_cost, .. } | ChargedRanged { energy_cost, .. } | Shockwave { energy_cost, .. } - | HealingBeam { energy_cost, .. } | BasicAura { energy_cost, .. } | BasicBlock { energy_cost, .. } | SelfBuff { energy_cost, .. } => *energy_cost as u32, @@ -1381,45 +1362,39 @@ impl CharacterAbility { *lifesteal *= 1.15_f32.powi(level.into()); } }, - HealingBeam { - ref mut heal, - ref mut energy_cost, - ref mut range, - ref mut beam_duration, - .. - } => { - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HHeal)) { - *heal *= 1.2_f32.powi(level.into()); - } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HRange)) { - let range_mod = 1.2_f32.powi(level.into()); - *range *= range_mod; - // Duration modified to keep velocity constant - *beam_duration *= range_mod; - } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(HCost)) { - *energy_cost *= 0.8_f32.powi(level.into()); - } - }, BasicAura { ref mut aura, ref mut range, ref mut energy_cost, + ref specifier, .. } => { - if let Ok(Some(level)) = skillset.skill_level(Sceptre(AStrength)) { - aura.strength *= 1.15_f32.powi(level.into()); - } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ADuration)) { - if let Some(ref mut duration) = aura.duration { - *duration *= 1.2_f32.powi(level.into()); + if matches!(*specifier, aura::Specifier::WardingAura) { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(AStrength)) { + aura.strength *= 1.15_f32.powi(level.into()); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(ADuration)) { + aura.duration.map(|dur| dur * 1.2_f32.powi(level.into())); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(ARange)) { + *range *= 1.25_f32.powi(level.into()); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(ACost)) { + *energy_cost *= 0.85_f32.powi(level.into()); + } + } else if matches!(*specifier, aura::Specifier::HealingAura) { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(HHeal)) { + aura.strength *= 1.15_f32.powi(level.into()); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(HDuration)) { + aura.duration.map(|dur| dur * 1.2_f32.powi(level.into())); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(HRange)) { + *range *= 1.25_f32.powi(level.into()); + } + if let Ok(Some(level)) = skillset.skill_level(Sceptre(HCost)) { + *energy_cost *= 0.85_f32.powi(level.into()); } - } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ARange)) { - *range *= 1.25_f32.powi(level.into()); - } - if let Ok(Some(level)) = skillset.skill_level(Sceptre(ACost)) { - *energy_cost *= 0.85_f32.powi(level.into()); } }, _ => {}, @@ -1935,6 +1910,8 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { aura_duration, range, energy_cost: _, + scales_with_combo, + specifier, } => CharacterState::BasicAura(basic_aura::Data { static_data: basic_aura::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1945,31 +1922,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { aura_duration: Duration::from_secs_f32(*aura_duration), range: *range, ability_info, - }, - timer: Duration::default(), - stage_section: StageSection::Buildup, - }), - CharacterAbility::HealingBeam { - buildup_duration, - recover_duration, - beam_duration, - heal, - tick_rate, - range, - max_angle, - energy_cost, - specifier, - } => CharacterState::HealingBeam(healing_beam::Data { - static_data: healing_beam::StaticData { - buildup_duration: Duration::from_secs_f32(*buildup_duration), - recover_duration: Duration::from_secs_f32(*recover_duration), - beam_duration: Duration::from_secs_f32(*beam_duration), - heal: *heal, - tick_rate: *tick_rate, - range: *range, - max_angle: *max_angle, - energy_cost: *energy_cost, - ability_info, + scales_with_combo: *scales_with_combo, specifier: *specifier, }, timer: Duration::default(), diff --git a/common/src/comp/aura.rs b/common/src/comp/aura.rs index e172878d4c..821a9c3989 100644 --- a/common/src/comp/aura.rs +++ b/common/src/comp/aura.rs @@ -71,6 +71,12 @@ pub enum AuraTarget { All, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum Specifier { + WardingAura, + HealingAura, +} + impl From<(Option, Option<&Uid>)> for AuraTarget { fn from((target, uid): (Option, Option<&Uid>)) -> Self { match (target, uid) { diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 9111c35453..3788cacf98 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -50,7 +50,6 @@ impl Component for Beam { pub enum FrontendSpecifier { Flamethrower, LifestealBeam, - HealingBeam, Cultist, ClayGolem, Bubbles, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 4bd835593f..8da7e8386c 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -93,11 +93,6 @@ pub enum CharacterState { BasicBeam(basic_beam::Data), /// Creates an aura that persists as long as you are actively casting BasicAura(basic_aura::Data), - /// A directed beam that heals targets in range. This is separate from basic - /// beam as a large amount of functionality needed to be special cased - /// specifically for the healing beam. There was also functionality present - /// on basic beam which was unnecessary for the healing beam. - HealingBeam(healing_beam::Data), /// A short teleport that targets either a position or entity Blink(blink::Data), /// Summons creatures that fight for the caster @@ -128,7 +123,6 @@ impl CharacterState { | CharacterState::Shockwave(_) | CharacterState::BasicBeam(_) | CharacterState::BasicAura(_) - | CharacterState::HealingBeam(_) | CharacterState::SelfBuff(_) | CharacterState::Blink(_) | CharacterState::BasicSummon(_) @@ -155,7 +149,6 @@ impl CharacterState { | CharacterState::Shockwave(_) | CharacterState::BasicBeam(_) | CharacterState::BasicAura(_) - | CharacterState::HealingBeam(_) | CharacterState::SelfBuff(_) | CharacterState::Blink(_) | CharacterState::BasicSummon(_) @@ -181,7 +174,6 @@ impl CharacterState { | CharacterState::UseItem(_) | CharacterState::Wielding | CharacterState::Talk - | CharacterState::HealingBeam(_) ) } diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index c35ce09075..d6ba867c29 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -478,8 +478,9 @@ pub enum SceptreSkill { LRegen, // Healing beam upgrades HHeal, - HCost, HRange, + HDuration, + HCost, // Warding aura upgrades UnlockAura, AStrength, @@ -499,6 +500,7 @@ impl Boost for SceptreSkill { // Healing beam upgrades Self::HHeal => 20.into(), Self::HRange => 20.into(), + Self::HDuration => 20.into(), Self::HCost => (-20_i16).into(), // Warding aura upgrades Self::AStrength => 15.into(), diff --git a/common/src/states/basic_aura.rs b/common/src/states/basic_aura.rs index 1e82d062f8..c9835e6d2e 100644 --- a/common/src/states/basic_aura.rs +++ b/common/src/states/basic_aura.rs @@ -1,7 +1,7 @@ use crate::{ combat::GroupTarget, comp::{ - aura::{AuraBuffConstructor, AuraChange, AuraTarget}, + aura::{AuraBuffConstructor, AuraChange, AuraKind, AuraTarget, Specifier}, CharacterState, StateUpdate, }, event::ServerEvent, @@ -32,6 +32,10 @@ pub struct StaticData { pub range: f32, /// What key is used to press ability pub ability_info: AbilityInfo, + /// Whether the aura's effect scales with the user's current combo + pub scales_with_combo: bool, + /// Used to specify aura to the frontend + pub specifier: Specifier, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -65,12 +69,29 @@ impl CharacterBehavior for Data { // Creates aura let targets = AuraTarget::from((Some(self.static_data.targets), Some(data.uid))); - let aura = self.static_data.aura.to_aura( + let mut aura = self.static_data.aura.to_aura( data.uid, self.static_data.range, Some(self.static_data.aura_duration), targets, ); + if self.static_data.scales_with_combo { + let combo = data.combo.counter(); + match aura.aura_kind { + AuraKind::Buff { + kind: _, + ref mut data, + category: _, + source: _, + } => { + data.strength *= 1.0 + (combo as f32).log(2.0_f32); + }, + } + update.server_events.push_front(ServerEvent::ComboChange { + entity: data.entity, + change: -(combo as i32), + }); + } update.server_events.push_front(ServerEvent::Aura { entity: data.entity, aura_change: AuraChange::Add(aura), diff --git a/common/src/states/healing_beam.rs b/common/src/states/healing_beam.rs deleted file mode 100644 index 14decfe4f7..0000000000 --- a/common/src/states/healing_beam.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::{ - combat::{Attack, AttackEffect, CombatEffect, CombatRequirement, GroupTarget}, - comp::{beam, CharacterState, Ori, Pos, StateUpdate}, - event::ServerEvent, - states::{ - behavior::{CharacterBehavior, JoinData}, - utils::*, - }, - uid::Uid, -}; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use vek::*; - -/// Separated out to condense update portions of character state -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct StaticData { - /// How long until state should deal damage or heal - pub buildup_duration: Duration, - /// How long the state has until exiting - pub recover_duration: Duration, - /// How long each beam segment persists for - pub beam_duration: Duration, - /// Base healing per tick - pub heal: f32, - /// Ticks of healing per second - pub tick_rate: f32, - /// Max range - pub range: f32, - /// Max angle (45.0 will give you a 90.0 angle window) - pub max_angle: f32, - /// Energy consumed per second for heal ticks - pub energy_cost: f32, - /// What key is used to press ability - pub ability_info: AbilityInfo, - /// Used to specify the beam to the frontend - pub specifier: beam::FrontendSpecifier, -} - -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Data { - /// Struct containing data that does not change over the course of the - /// character state - pub static_data: StaticData, - /// Timer for each stage - pub timer: Duration, - /// What section the character stage is in - pub stage_section: StageSection, -} - -impl CharacterBehavior for Data { - fn behavior(&self, data: &JoinData) -> StateUpdate { - let mut update = StateUpdate::from(data); - - handle_orientation(data, &mut update, 0.6); - handle_move(data, &mut update, 0.4); - handle_jump(data, &mut update, 1.0); - - match self.stage_section { - StageSection::Buildup => { - if self.timer < self.static_data.buildup_duration { - // Build up - update.character = CharacterState::HealingBeam(Data { - timer: tick_attack_or_default(data, self.timer, None), - ..*self - }); - } else { - // Creates beam - data.updater.insert(data.entity, beam::Beam { - hit_entities: Vec::::new(), - tick_dur: Duration::from_secs_f32(1.0 / self.static_data.tick_rate), - timer: Duration::default(), - }); - // Build up - update.character = CharacterState::HealingBeam(Data { - timer: Duration::default(), - stage_section: StageSection::Cast, - ..*self - }); - } - }, - StageSection::Cast => { - if input_is_pressed(data, self.static_data.ability_info.input) { - let speed = - self.static_data.range / self.static_data.beam_duration.as_secs_f32(); - let heal = AttackEffect::new( - Some(GroupTarget::InGroup), - CombatEffect::Heal(self.static_data.heal), - ) - .with_requirement(CombatRequirement::Energy(self.static_data.energy_cost)) - .with_requirement(CombatRequirement::Combo(1)); - let attack = Attack::default().with_effect(heal); - - let properties = beam::Properties { - attack, - angle: self.static_data.max_angle.to_radians(), - speed, - duration: self.static_data.beam_duration, - owner: Some(*data.uid), - specifier: self.static_data.specifier, - }; - // Gets offsets - let body_offsets = Vec3::new( - (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); - // Create beam segment - update.server_events.push_front(ServerEvent::BeamSegment { - properties, - pos, - ori: Ori::from(data.inputs.look_dir), - }); - update.character = CharacterState::HealingBeam(Data { - timer: tick_attack_or_default(data, self.timer, None), - ..*self - }); - } else { - update.character = CharacterState::HealingBeam(Data { - timer: Duration::default(), - stage_section: StageSection::Recover, - ..*self - }); - } - }, - StageSection::Recover => { - if self.timer < self.static_data.recover_duration { - update.character = CharacterState::HealingBeam(Data { - timer: tick_attack_or_default(data, self.timer, None), - ..*self - }); - } else { - // Done - update.character = CharacterState::Wielding; - // Make sure attack component is removed - data.updater.remove::(data.entity); - } - }, - _ => { - // If it somehow ends up in an incorrect stage section - update.character = CharacterState::Wielding; - // Make sure attack component is removed - data.updater.remove::(data.entity); - }, - } - - // At end of state logic so an interrupt isn't overwritten - if !input_is_pressed(data, self.static_data.ability_info.input) { - handle_state_interrupt(data, &mut update, false); - } - - update - } -} diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index bc1777bfe8..7fcd03f9c2 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -16,7 +16,6 @@ pub mod dash_melee; pub mod equipping; pub mod glide; pub mod glide_wield; -pub mod healing_beam; pub mod idle; pub mod leap_melee; pub mod repeater_ranged; diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 4b74011b91..db48764235 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -339,7 +339,6 @@ impl<'a> System<'a> for Sys { CharacterState::Shockwave(data) => data.handle_event(&j, action), CharacterState::BasicBeam(data) => data.handle_event(&j, action), CharacterState::BasicAura(data) => data.handle_event(&j, action), - CharacterState::HealingBeam(data) => data.handle_event(&j, action), CharacterState::Blink(data) => data.handle_event(&j, action), CharacterState::BasicSummon(data) => data.handle_event(&j, action), CharacterState::SelfBuff(data) => data.handle_event(&j, action), @@ -396,7 +395,6 @@ impl<'a> System<'a> for Sys { CharacterState::Shockwave(data) => data.behavior(&j), CharacterState::BasicBeam(data) => data.behavior(&j), CharacterState::BasicAura(data) => data.behavior(&j), - CharacterState::HealingBeam(data) => data.behavior(&j), CharacterState::Blink(data) => data.behavior(&j), CharacterState::BasicSummon(data) => data.behavior(&j), CharacterState::SelfBuff(data) => data.behavior(&j), diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index f047902707..a5bfb8aefc 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -276,7 +276,6 @@ impl<'a> System<'a> for Sys { | CharacterState::Shockwave { .. } | CharacterState::BasicBeam { .. } | CharacterState::BasicAura { .. } - | CharacterState::HealingBeam { .. } | CharacterState::Blink { .. } | CharacterState::BasicSummon { .. } | CharacterState::SelfBuff { .. } diff --git a/server/src/persistence/json_models.rs b/server/src/persistence/json_models.rs index d359cc4672..0dba8faf06 100644 --- a/server/src/persistence/json_models.rs +++ b/server/src/persistence/json_models.rs @@ -122,8 +122,9 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String { Sceptre(SceptreSkill::LLifesteal) => "Sceptre LLifesteal", Sceptre(SceptreSkill::LRegen) => "Sceptre LRegen", Sceptre(SceptreSkill::HHeal) => "Sceptre HHeal", - Sceptre(SceptreSkill::HCost) => "Sceptre HCost", + Sceptre(SceptreSkill::HDuration) => "Sceptre HDuration", Sceptre(SceptreSkill::HRange) => "Sceptre HRange", + Sceptre(SceptreSkill::HCost) => "Sceptre HCost", Sceptre(SceptreSkill::UnlockAura) => "Sceptre UnlockAura", Sceptre(SceptreSkill::AStrength) => "Sceptre AStrength", Sceptre(SceptreSkill::ADuration) => "Sceptre ADuration", @@ -245,8 +246,9 @@ pub fn db_string_to_skill(skill_string: &str) -> comp::skills::Skill { "Sceptre LLifesteal" => Sceptre(SceptreSkill::LLifesteal), "Sceptre LRegen" => Sceptre(SceptreSkill::LRegen), "Sceptre HHeal" => Sceptre(SceptreSkill::HHeal), - "Sceptre HCost" => Sceptre(SceptreSkill::HCost), + "Sceptre HDuration" => Sceptre(SceptreSkill::HDuration), "Sceptre HRange" => Sceptre(SceptreSkill::HRange), + "Sceptre HCost" => Sceptre(SceptreSkill::HCost), "Sceptre UnlockAura" => Sceptre(SceptreSkill::UnlockAura), "Sceptre AStrength" => Sceptre(SceptreSkill::AStrength), "Sceptre ADuration" => Sceptre(SceptreSkill::ADuration), diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 11481ea49d..aa7b33818a 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -18,9 +18,9 @@ use common::{ }, skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill}, Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility, - CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange, - InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, - Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel, + CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy, Health, + HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, + PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel, }, consts::GRAVITY, effect::{BuffEffect, Effect}, @@ -160,6 +160,7 @@ pub struct ReadData<'a> { world: ReadExpect<'a, Arc>, rtsim_entities: ReadStorage<'a, RtSimEntity>, buffs: ReadStorage<'a, Buffs>, + combos: ReadStorage<'a, Combo>, } // This is 3.1 to last longer than the last damage timer (3.0 seconds) @@ -2467,6 +2468,7 @@ impl<'a> AgentData<'a> { read_data: &ReadData, ) { const DESIRED_ENERGY_LEVEL: u32 = 500; + const DESIRED_COMBO_LEVEL: u32 = 8; // Logic to use abilities if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2) && can_see_tgt( @@ -2478,7 +2480,23 @@ impl<'a> AgentData<'a> { { // If far enough away, and can see target, check which skill is appropriate to // use - if self + if self.energy.current() > DESIRED_ENERGY_LEVEL + && read_data + .combos + .get(*self.entity) + .map_or(false, |c| c.counter() >= DESIRED_COMBO_LEVEL) + && !read_data.buffs.get(*self.entity).iter().any(|buff| { + buff.iter_kind(BuffKind::Regeneration) + .peekable() + .peek() + .is_some() + }) + { + // If have enough energy and combo to use healing aura, do so + controller + .actions + .push(ControlAction::basic_input(InputKind::Secondary)); + } else if self .skill_set .has_skill(Skill::Sceptre(SceptreSkill::UnlockAura)) && self.energy.current() > DESIRED_ENERGY_LEVEL diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index dfe9c32324..db678a03f9 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -175,7 +175,7 @@ pub enum SfxEvent { Parry, Block, BreakBlock, - HealingBeam, + SceptreBeam, SkillPointGain, ArrowHit, ArrowMiss, @@ -479,9 +479,9 @@ impl SfxMgr { audio.emit_sfx(sfx_trigger_item, *pos, None, false); }, Outcome::Beam { pos, specifier } => match specifier { - beam::FrontendSpecifier::LifestealBeam | beam::FrontendSpecifier::HealingBeam => { + beam::FrontendSpecifier::LifestealBeam => { if thread_rng().gen_bool(0.5) { - let sfx_trigger_item = triggers.get_key_value(&SfxEvent::HealingBeam); + let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SceptreBeam); audio.emit_sfx(sfx_trigger_item, *pos, None, false); }; }, diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 1f1fd98eb9..8112d784d1 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -144,6 +144,7 @@ widget_ids! { skill_sceptre_heal_1, skill_sceptre_heal_2, skill_sceptre_heal_3, + skill_sceptre_heal_4, skill_sceptre_aura_0, skill_sceptre_aura_1, skill_sceptre_aura_2, @@ -551,7 +552,7 @@ impl<'a> Widget for Diary<'a> { SelectedSkillTree::Weapon(ToolKind::Hammer) => 5, SelectedSkillTree::Weapon(ToolKind::Bow) => 4, SelectedSkillTree::Weapon(ToolKind::Staff) => 5, - SelectedSkillTree::Weapon(ToolKind::Sceptre) => 4, + SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5, _ => 0, }; let skills_bot_l = match sel_tab { @@ -1912,10 +1913,10 @@ impl<'a> Widget for Diary<'a> { &diary_tooltip, ); self.create_unlock_skill_button( - Skill::Sceptre(HCost), - self.imgs.heal_cost_skill, + Skill::Sceptre(HDuration), + self.imgs.heal_duration_skill, state.skills_top_r[2], - "sc_heal_cost", + "sc_heal_duration", state.skill_sceptre_heal_2, ui, &mut events, @@ -1923,7 +1924,7 @@ impl<'a> Widget for Diary<'a> { ); self.create_unlock_skill_button( Skill::Sceptre(HRange), - self.imgs.heal_distance_skill, + self.imgs.heal_radius_skill, state.skills_top_r[3], "sc_heal_range", state.skill_sceptre_heal_3, @@ -1931,6 +1932,16 @@ impl<'a> Widget for Diary<'a> { &mut events, &diary_tooltip, ); + self.create_unlock_skill_button( + Skill::Sceptre(HCost), + self.imgs.heal_cost_skill, + state.skills_top_r[4], + "sc_heal_cost", + state.skill_sceptre_heal_4, + ui, + &mut events, + &diary_tooltip, + ); // Bottom left skills self.create_unlock_skill_button( Skill::Sceptre(UnlockAura), diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 7fb4b93e26..55eaa73b5a 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -494,7 +494,7 @@ image_ids! { snake_arrow_0: "voxygen.element.skills.snake", skill_sceptre_lifesteal: "voxygen.element.skills.lifesteal", sword_whirlwind: "voxygen.element.skills.sword_whirlwind", - skill_sceptre_heal: "voxygen.element.skills.heal_0", + skill_sceptre_heal: "voxygen.element.skills.heal_aoe", hammerleap: "voxygen.element.skills.skill_hammerleap", skill_axe_leap_slash: "voxygen.element.skills.skill_axe_leap_slash", skill_bow_jump_burst: "voxygen.element.skills.skill_bow_jump_burst", diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index d064348eea..677eaad62f 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -64,7 +64,7 @@ pub enum ParticleMode { Firefly = 11, Bee = 12, GroundShockwave = 13, - HealingBeam = 14, + EnergyHealing = 14, EnergyNature = 15, FlameThrower = 16, FireShockwave = 17, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 247f92aec6..67d947f3be 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1302,32 +1302,6 @@ impl FigureMgr { skeleton_attr, ) }, - CharacterState::HealingBeam(s) => { - let stage_time = s.timer.as_secs_f32(); - let stage_progress = match s.stage_section { - StageSection::Buildup => { - stage_time / s.static_data.buildup_duration.as_secs_f32() - }, - StageSection::Cast => s.timer.as_secs_f32(), - StageSection::Recover => { - stage_time / s.static_data.recover_duration.as_secs_f32() - }, - _ => 0.0, - }; - anim::character::BeamAnimation::update_skeleton( - &target_base, - ( - Some(s.static_data.ability_info), - hands, - time, - rel_vel.magnitude(), - Some(s.stage_section), - ), - stage_progress, - &mut state_animation_rate, - skeleton_attr, - ) - }, CharacterState::ComboMelee(s) => { let stage_index = (s.stage - 1) as usize; let stage_time = s.timer.as_secs_f32(); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 9465868dc3..7386b78598 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -826,20 +826,6 @@ impl ParticleMgr { }, ); }, - 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)); - self.particles.reserve(beam_tick_count as usize); - for i in 0..beam_tick_count { - self.particles.push(Particle::new_directed( - beam.properties.duration, - time + i as f64 / 1000.0, - ParticleMode::HealingBeam, - pos.0, - pos.0 + *ori.look_dir() * range, - )); - } - }, beam::FrontendSpecifier::LifestealBeam => { // Emit a light when using lifesteal beam lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0)); @@ -956,6 +942,28 @@ impl ParticleMgr { }, ); }, + aura::AuraKind::Buff { + kind: buff::BuffKind::Regeneration, + .. + } => { + let heartbeats = self.scheduler.heartbeats(Duration::from_millis(5)); + self.particles.resize_with( + self.particles.len() + + aura.radius.powi(2) as usize * usize::from(heartbeats) / 300, + || { + let rand_dist = aura.radius * (1.0 - rng.gen::().powi(100)); + let init_pos = Vec3::new(rand_dist, 0_f32, 0_f32); + let max_dur = Duration::from_secs(1); + Particle::new_directed( + aura.duration.map_or(max_dur, |dur| dur.min(max_dur)), + time, + ParticleMode::EnergyHealing, + pos.0, + pos.0 + init_pos, + ) + }, + ); + }, _ => {}, } }