Merge branch 'knightresspaladin/healing-aura' into 'master'

Knightresspaladin/healing aura

See merge request veloren/veloren!2587
This commit is contained in:
Samuel Keiffer 2021-07-16 21:32:03 +00:00
commit f38cde9d40
31 changed files with 196 additions and 353 deletions

View File

@ -79,7 +79,7 @@
), ),
Tool(Sceptre): ( Tool(Sceptre): (
primary: "common.abilities.sceptre.lifestealbeam", primary: "common.abilities.sceptre.lifestealbeam",
secondary: "common.abilities.sceptre.healingbeam", secondary: "common.abilities.sceptre.healingaura",
abilities: [ abilities: [
(Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"), (Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"),
], ],
@ -191,7 +191,7 @@
abilities: [], abilities: [],
), ),
Custom("Quad Low Beam"): ( Custom("Quad Low Beam"): (
primary: "common.abilities.custom.quadlowbeam.healingbeam", primary: "common.abilities.custom.quadlowbeam.lifestealbeam",
secondary: "common.abilities.custom.quadlowbreathe.triplestrike", secondary: "common.abilities.custom.quadlowbreathe.triplestrike",
abilities: [ abilities: [
(None, "common.abilities.custom.quadlowbreathe.dash"), (None, "common.abilities.custom.quadlowbreathe.dash"),

View File

@ -11,5 +11,5 @@ BasicBeam(
energy_drain: 0, energy_drain: 0,
orientation_behavior: Normal, orientation_behavior: Normal,
ori_rate: 0.3, ori_rate: 0.3,
specifier: HealingBeam, specifier: LifestealBeam,
) )

View File

@ -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,
)

View File

@ -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,
)

View File

@ -12,4 +12,6 @@ BasicAura(
aura_duration: 1.0, aura_duration: 1.0,
range: 25.0, range: 25.0,
energy_cost: 400, energy_cost: 400,
) scales_with_combo: false,
specifier: WardingAura,
)

View File

@ -64,8 +64,9 @@
Sceptre(LLifesteal): Some(3), Sceptre(LLifesteal): Some(3),
Sceptre(LRegen): Some(2), Sceptre(LRegen): Some(2),
Sceptre(HHeal): Some(3), Sceptre(HHeal): Some(3),
Sceptre(HCost): Some(2),
Sceptre(HRange): Some(2), Sceptre(HRange): Some(2),
Sceptre(HDuration): Some(2),
Sceptre(HCost): Some(2),
Sceptre(AStrength): Some(2), Sceptre(AStrength): Some(2),
Sceptre(ADuration): Some(2), Sceptre(ADuration): Some(2),
Sceptre(ARange): Some(2), Sceptre(ARange): Some(2),

View File

@ -101,8 +101,9 @@
Sceptre(LLifesteal), Sceptre(LLifesteal),
Sceptre(LRegen), Sceptre(LRegen),
Sceptre(HHeal), Sceptre(HHeal),
Sceptre(HCost),
Sceptre(HRange), Sceptre(HRange),
Sceptre(HDuration),
Sceptre(HCost),
Sceptre(UnlockAura), Sceptre(UnlockAura),
Sceptre(AStrength), Sceptre(AStrength),
Sceptre(ADuration), Sceptre(ADuration),

View File

@ -9,9 +9,9 @@
// Heal // Heal
Skill((Sceptre(HHeal), Some(1))), Skill((Sceptre(HHeal), Some(1))),
Skill((Sceptre(HCost), Some(1))), Skill((Sceptre(HDuration), Some(1))),
Skill((Sceptre(HRange), Some(1))), Skill((Sceptre(HRange), Some(1))),
Skill((Sceptre(HCost), Some(1))),
// Ward // Ward
Skill((Sceptre(UnlockAura), None)), Skill((Sceptre(UnlockAura), None)),
Skill((Sceptre(AStrength), Some(1))), Skill((Sceptre(AStrength), Some(1))),

View File

@ -802,7 +802,7 @@
], ],
threshold: 0.2, threshold: 0.2,
), ),
HealingBeam: ( SceptreBeam: (
files: [ files: [
"voxygen.audio.sfx.abilities.sceptre_channeling", "voxygen.audio.sfx.abilities.sceptre_channeling",
], ],

BIN
assets/voxygen/element/skills/heal_aoe.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -54,14 +54,16 @@
"hud.skill.sc_lifesteal_lifesteal": "Convert an additional {boost}% of damage into health{SP}", "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_title": "Stamina Regen",
"hud.skill.sc_lifesteal_regen": "Replenish your stamina by an additional {boost}%{SP}", "hud.skill.sc_lifesteal_regen": "Replenish your stamina by an additional {boost}%{SP}",
"hud.skill.sc_heal_title": "Healing Beam", "hud.skill.sc_heal_title": "Healing Aura",
"hud.skill.sc_heal": "Heal your allies using the blood of your enemies", "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_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_title": "Stamina Cost",
"hud.skill.sc_heal_cost": "Healing others requires {boost}% less stamina{SP}", "hud.skill.sc_heal_cost": "Healing requires {boost}% less stamina{SP}",
"hud.skill.sc_heal_range_title": "Range", "hud.skill.sc_heal_duration_title": "Duration",
"hud.skill.sc_heal_range": "Your beam reachs {boost}% further{SP}", "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_title": "Warding Aura Unlock",
"hud.skill.sc_wardaura_unlock": "Allows you to ward your allies against enemy attacks{SP}", "hud.skill.sc_wardaura_unlock": "Allows you to ward your allies against enemy attacks{SP}",
"hud.skill.sc_wardaura_strength_title": "Strength", "hud.skill.sc_wardaura_strength_title": "Strength",

View File

@ -52,7 +52,7 @@ const int LEAF = 10;
const int FIREFLY = 11; const int FIREFLY = 11;
const int BEE = 12; const int BEE = 12;
const int GROUND_SHOCKWAVE = 13; const int GROUND_SHOCKWAVE = 13;
const int HEALING_BEAM = 14; const int ENERGY_HEALING = 14;
const int ENERGY_NATURE = 15; const int ENERGY_NATURE = 15;
const int FLAMETHROWER = 16; const int FLAMETHROWER = 16;
const int FIRE_SHOCKWAVE = 17; const int FIRE_SHOCKWAVE = 17;
@ -380,13 +380,14 @@ void main() {
spin_in_axis(vec3(1,0,0),0) spin_in_axis(vec3(1,0,0),0)
); );
break; break;
case HEALING_BEAM: case ENERGY_HEALING:
f_reflect = 0.0; f_reflect = 0.0;
float spiral_radius = start_end(1 - pow(abs(rand5), 5), 1) * length(inst_dir);
attr = Attr( 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), spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2),
vec3((1.7 - 0.7 * abs(floor(2 * rand0 - 0.5) + 0.5)) * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 4))), vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)),
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*/), vec4(vec3(0, 1.7, 0.7), 1),
spin_in_axis(inst_dir, tick.z) spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
); );
break; break;
case LIFESTEAL_BEAM: case LIFESTEAL_BEAM:
@ -402,7 +403,7 @@ void main() {
break; break;
case ENERGY_NATURE: case ENERGY_NATURE:
f_reflect = 0.0; 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( attr = Attr(
spiral_motion(vec3(0, 0, rand3 + 1), spiral_radius, lifetime, abs(rand0), rand1 * 2 * PI) + vec3(0, 0, rand2), 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)), vec3(6 * abs(rand4) * (1 - slow_start(2)) * pow(spiral_radius / length(inst_dir), 0.5)),

View File

@ -264,17 +264,8 @@ pub enum CharacterAbility {
aura_duration: f32, aura_duration: f32,
range: f32, range: f32,
energy_cost: f32, energy_cost: f32,
}, scales_with_combo: bool,
HealingBeam { specifier: aura::Specifier,
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,
}, },
Blink { Blink {
buildup_duration: f32, buildup_duration: f32,
@ -356,7 +347,6 @@ impl CharacterAbility {
| CharacterAbility::ChargedRanged { energy_cost, .. } | CharacterAbility::ChargedRanged { energy_cost, .. }
| CharacterAbility::ChargedMelee { energy_cost, .. } | CharacterAbility::ChargedMelee { energy_cost, .. }
| CharacterAbility::Shockwave { energy_cost, .. } | CharacterAbility::Shockwave { energy_cost, .. }
| CharacterAbility::BasicAura { energy_cost, .. }
| CharacterAbility::BasicBlock { energy_cost, .. } | CharacterAbility::BasicBlock { energy_cost, .. }
| CharacterAbility::SelfBuff { energy_cost, .. } => update | CharacterAbility::SelfBuff { energy_cost, .. } => update
.energy .energy
@ -373,7 +363,17 @@ impl CharacterAbility {
.try_change_by(-(*energy_cost as i32), EnergySource::Ability) .try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok() .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::ComboMelee { .. }
| CharacterAbility::Boost { .. } | CharacterAbility::Boost { .. }
| CharacterAbility::BasicBeam { .. } | CharacterAbility::BasicBeam { .. }
@ -782,6 +782,8 @@ impl CharacterAbility {
aura_duration: _, aura_duration: _,
ref mut range, ref mut range,
ref mut energy_cost, ref mut energy_cost,
scales_with_combo: _,
specifier: _,
} => { } => {
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
*cast_duration /= stats.speed; *cast_duration /= stats.speed;
@ -792,26 +794,6 @@ impl CharacterAbility {
*range *= stats.range; *range *= stats.range;
*energy_cost /= stats.energy_efficiency; *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 { Blink {
ref mut buildup_duration, ref mut buildup_duration,
ref mut recover_duration, ref mut recover_duration,
@ -886,7 +868,6 @@ impl CharacterAbility {
| ChargedMelee { energy_cost, .. } | ChargedMelee { energy_cost, .. }
| ChargedRanged { energy_cost, .. } | ChargedRanged { energy_cost, .. }
| Shockwave { energy_cost, .. } | Shockwave { energy_cost, .. }
| HealingBeam { energy_cost, .. }
| BasicAura { energy_cost, .. } | BasicAura { energy_cost, .. }
| BasicBlock { energy_cost, .. } | BasicBlock { energy_cost, .. }
| SelfBuff { energy_cost, .. } => *energy_cost as u32, | SelfBuff { energy_cost, .. } => *energy_cost as u32,
@ -1381,45 +1362,39 @@ impl CharacterAbility {
*lifesteal *= 1.15_f32.powi(level.into()); *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 { BasicAura {
ref mut aura, ref mut aura,
ref mut range, ref mut range,
ref mut energy_cost, ref mut energy_cost,
ref specifier,
.. ..
} => { } => {
if let Ok(Some(level)) = skillset.skill_level(Sceptre(AStrength)) { if matches!(*specifier, aura::Specifier::WardingAura) {
aura.strength *= 1.15_f32.powi(level.into()); 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 { if let Ok(Some(level)) = skillset.skill_level(Sceptre(ADuration)) {
*duration *= 1.2_f32.powi(level.into()); 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, aura_duration,
range, range,
energy_cost: _, energy_cost: _,
scales_with_combo,
specifier,
} => CharacterState::BasicAura(basic_aura::Data { } => CharacterState::BasicAura(basic_aura::Data {
static_data: basic_aura::StaticData { static_data: basic_aura::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration), 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), aura_duration: Duration::from_secs_f32(*aura_duration),
range: *range, range: *range,
ability_info, ability_info,
}, scales_with_combo: *scales_with_combo,
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,
specifier: *specifier, specifier: *specifier,
}, },
timer: Duration::default(), timer: Duration::default(),

View File

@ -71,6 +71,12 @@ pub enum AuraTarget {
All, All,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Specifier {
WardingAura,
HealingAura,
}
impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget { impl From<(Option<GroupTarget>, Option<&Uid>)> for AuraTarget {
fn from((target, uid): (Option<GroupTarget>, Option<&Uid>)) -> Self { fn from((target, uid): (Option<GroupTarget>, Option<&Uid>)) -> Self {
match (target, uid) { match (target, uid) {

View File

@ -50,7 +50,6 @@ impl Component for Beam {
pub enum FrontendSpecifier { pub enum FrontendSpecifier {
Flamethrower, Flamethrower,
LifestealBeam, LifestealBeam,
HealingBeam,
Cultist, Cultist,
ClayGolem, ClayGolem,
Bubbles, Bubbles,

View File

@ -93,11 +93,6 @@ pub enum CharacterState {
BasicBeam(basic_beam::Data), BasicBeam(basic_beam::Data),
/// Creates an aura that persists as long as you are actively casting /// Creates an aura that persists as long as you are actively casting
BasicAura(basic_aura::Data), 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 /// A short teleport that targets either a position or entity
Blink(blink::Data), Blink(blink::Data),
/// Summons creatures that fight for the caster /// Summons creatures that fight for the caster
@ -128,7 +123,6 @@ impl CharacterState {
| CharacterState::Shockwave(_) | CharacterState::Shockwave(_)
| CharacterState::BasicBeam(_) | CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_) | CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_) | CharacterState::SelfBuff(_)
| CharacterState::Blink(_) | CharacterState::Blink(_)
| CharacterState::BasicSummon(_) | CharacterState::BasicSummon(_)
@ -155,7 +149,6 @@ impl CharacterState {
| CharacterState::Shockwave(_) | CharacterState::Shockwave(_)
| CharacterState::BasicBeam(_) | CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_) | CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_) | CharacterState::SelfBuff(_)
| CharacterState::Blink(_) | CharacterState::Blink(_)
| CharacterState::BasicSummon(_) | CharacterState::BasicSummon(_)
@ -181,7 +174,6 @@ impl CharacterState {
| CharacterState::UseItem(_) | CharacterState::UseItem(_)
| CharacterState::Wielding | CharacterState::Wielding
| CharacterState::Talk | CharacterState::Talk
| CharacterState::HealingBeam(_)
) )
} }

View File

@ -478,8 +478,9 @@ pub enum SceptreSkill {
LRegen, LRegen,
// Healing beam upgrades // Healing beam upgrades
HHeal, HHeal,
HCost,
HRange, HRange,
HDuration,
HCost,
// Warding aura upgrades // Warding aura upgrades
UnlockAura, UnlockAura,
AStrength, AStrength,
@ -499,6 +500,7 @@ impl Boost for SceptreSkill {
// Healing beam upgrades // Healing beam upgrades
Self::HHeal => 20.into(), Self::HHeal => 20.into(),
Self::HRange => 20.into(), Self::HRange => 20.into(),
Self::HDuration => 20.into(),
Self::HCost => (-20_i16).into(), Self::HCost => (-20_i16).into(),
// Warding aura upgrades // Warding aura upgrades
Self::AStrength => 15.into(), Self::AStrength => 15.into(),

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
combat::GroupTarget, combat::GroupTarget,
comp::{ comp::{
aura::{AuraBuffConstructor, AuraChange, AuraTarget}, aura::{AuraBuffConstructor, AuraChange, AuraKind, AuraTarget, Specifier},
CharacterState, StateUpdate, CharacterState, StateUpdate,
}, },
event::ServerEvent, event::ServerEvent,
@ -32,6 +32,10 @@ pub struct StaticData {
pub range: f32, pub range: f32,
/// What key is used to press ability /// What key is used to press ability
pub ability_info: AbilityInfo, 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)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -65,12 +69,29 @@ impl CharacterBehavior for Data {
// Creates aura // Creates aura
let targets = let targets =
AuraTarget::from((Some(self.static_data.targets), Some(data.uid))); 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, data.uid,
self.static_data.range, self.static_data.range,
Some(self.static_data.aura_duration), Some(self.static_data.aura_duration),
targets, 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 { update.server_events.push_front(ServerEvent::Aura {
entity: data.entity, entity: data.entity,
aura_change: AuraChange::Add(aura), aura_change: AuraChange::Add(aura),

View File

@ -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::<Uid>::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::<beam::Beam>(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::<beam::Beam>(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
}
}

View File

@ -16,7 +16,6 @@ pub mod dash_melee;
pub mod equipping; pub mod equipping;
pub mod glide; pub mod glide;
pub mod glide_wield; pub mod glide_wield;
pub mod healing_beam;
pub mod idle; pub mod idle;
pub mod leap_melee; pub mod leap_melee;
pub mod repeater_ranged; pub mod repeater_ranged;

View File

@ -339,7 +339,6 @@ impl<'a> System<'a> for Sys {
CharacterState::Shockwave(data) => data.handle_event(&j, action), CharacterState::Shockwave(data) => data.handle_event(&j, action),
CharacterState::BasicBeam(data) => data.handle_event(&j, action), CharacterState::BasicBeam(data) => data.handle_event(&j, action),
CharacterState::BasicAura(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::Blink(data) => data.handle_event(&j, action),
CharacterState::BasicSummon(data) => data.handle_event(&j, action), CharacterState::BasicSummon(data) => data.handle_event(&j, action),
CharacterState::SelfBuff(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::Shockwave(data) => data.behavior(&j),
CharacterState::BasicBeam(data) => data.behavior(&j), CharacterState::BasicBeam(data) => data.behavior(&j),
CharacterState::BasicAura(data) => data.behavior(&j), CharacterState::BasicAura(data) => data.behavior(&j),
CharacterState::HealingBeam(data) => data.behavior(&j),
CharacterState::Blink(data) => data.behavior(&j), CharacterState::Blink(data) => data.behavior(&j),
CharacterState::BasicSummon(data) => data.behavior(&j), CharacterState::BasicSummon(data) => data.behavior(&j),
CharacterState::SelfBuff(data) => data.behavior(&j), CharacterState::SelfBuff(data) => data.behavior(&j),

View File

@ -276,7 +276,6 @@ impl<'a> System<'a> for Sys {
| CharacterState::Shockwave { .. } | CharacterState::Shockwave { .. }
| CharacterState::BasicBeam { .. } | CharacterState::BasicBeam { .. }
| CharacterState::BasicAura { .. } | CharacterState::BasicAura { .. }
| CharacterState::HealingBeam { .. }
| CharacterState::Blink { .. } | CharacterState::Blink { .. }
| CharacterState::BasicSummon { .. } | CharacterState::BasicSummon { .. }
| CharacterState::SelfBuff { .. } | CharacterState::SelfBuff { .. }

View File

@ -122,8 +122,9 @@ pub fn skill_to_db_string(skill: comp::skills::Skill) -> String {
Sceptre(SceptreSkill::LLifesteal) => "Sceptre LLifesteal", Sceptre(SceptreSkill::LLifesteal) => "Sceptre LLifesteal",
Sceptre(SceptreSkill::LRegen) => "Sceptre LRegen", Sceptre(SceptreSkill::LRegen) => "Sceptre LRegen",
Sceptre(SceptreSkill::HHeal) => "Sceptre HHeal", Sceptre(SceptreSkill::HHeal) => "Sceptre HHeal",
Sceptre(SceptreSkill::HCost) => "Sceptre HCost", Sceptre(SceptreSkill::HDuration) => "Sceptre HDuration",
Sceptre(SceptreSkill::HRange) => "Sceptre HRange", Sceptre(SceptreSkill::HRange) => "Sceptre HRange",
Sceptre(SceptreSkill::HCost) => "Sceptre HCost",
Sceptre(SceptreSkill::UnlockAura) => "Sceptre UnlockAura", Sceptre(SceptreSkill::UnlockAura) => "Sceptre UnlockAura",
Sceptre(SceptreSkill::AStrength) => "Sceptre AStrength", Sceptre(SceptreSkill::AStrength) => "Sceptre AStrength",
Sceptre(SceptreSkill::ADuration) => "Sceptre ADuration", 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 LLifesteal" => Sceptre(SceptreSkill::LLifesteal),
"Sceptre LRegen" => Sceptre(SceptreSkill::LRegen), "Sceptre LRegen" => Sceptre(SceptreSkill::LRegen),
"Sceptre HHeal" => Sceptre(SceptreSkill::HHeal), "Sceptre HHeal" => Sceptre(SceptreSkill::HHeal),
"Sceptre HCost" => Sceptre(SceptreSkill::HCost), "Sceptre HDuration" => Sceptre(SceptreSkill::HDuration),
"Sceptre HRange" => Sceptre(SceptreSkill::HRange), "Sceptre HRange" => Sceptre(SceptreSkill::HRange),
"Sceptre HCost" => Sceptre(SceptreSkill::HCost),
"Sceptre UnlockAura" => Sceptre(SceptreSkill::UnlockAura), "Sceptre UnlockAura" => Sceptre(SceptreSkill::UnlockAura),
"Sceptre AStrength" => Sceptre(SceptreSkill::AStrength), "Sceptre AStrength" => Sceptre(SceptreSkill::AStrength),
"Sceptre ADuration" => Sceptre(SceptreSkill::ADuration), "Sceptre ADuration" => Sceptre(SceptreSkill::ADuration),

View File

@ -18,9 +18,9 @@ use common::{
}, },
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility, Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterAbility,
CharacterState, ControlAction, ControlEvent, Controller, Energy, Health, HealthChange, CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy, Health,
InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori,
Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel, PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel,
}, },
consts::GRAVITY, consts::GRAVITY,
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
@ -160,6 +160,7 @@ pub struct ReadData<'a> {
world: ReadExpect<'a, Arc<world::World>>, world: ReadExpect<'a, Arc<world::World>>,
rtsim_entities: ReadStorage<'a, RtSimEntity>, rtsim_entities: ReadStorage<'a, RtSimEntity>,
buffs: ReadStorage<'a, Buffs>, buffs: ReadStorage<'a, Buffs>,
combos: ReadStorage<'a, Combo>,
} }
// This is 3.1 to last longer than the last damage timer (3.0 seconds) // 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, read_data: &ReadData,
) { ) {
const DESIRED_ENERGY_LEVEL: u32 = 500; const DESIRED_ENERGY_LEVEL: u32 = 500;
const DESIRED_COMBO_LEVEL: u32 = 8;
// Logic to use abilities // Logic to use abilities
if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2) if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
&& can_see_tgt( && 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 // If far enough away, and can see target, check which skill is appropriate to
// use // 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 .skill_set
.has_skill(Skill::Sceptre(SceptreSkill::UnlockAura)) .has_skill(Skill::Sceptre(SceptreSkill::UnlockAura))
&& self.energy.current() > DESIRED_ENERGY_LEVEL && self.energy.current() > DESIRED_ENERGY_LEVEL

View File

@ -175,7 +175,7 @@ pub enum SfxEvent {
Parry, Parry,
Block, Block,
BreakBlock, BreakBlock,
HealingBeam, SceptreBeam,
SkillPointGain, SkillPointGain,
ArrowHit, ArrowHit,
ArrowMiss, ArrowMiss,
@ -479,9 +479,9 @@ impl SfxMgr {
audio.emit_sfx(sfx_trigger_item, *pos, None, false); audio.emit_sfx(sfx_trigger_item, *pos, None, false);
}, },
Outcome::Beam { pos, specifier } => match specifier { Outcome::Beam { pos, specifier } => match specifier {
beam::FrontendSpecifier::LifestealBeam | beam::FrontendSpecifier::HealingBeam => { beam::FrontendSpecifier::LifestealBeam => {
if thread_rng().gen_bool(0.5) { 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); audio.emit_sfx(sfx_trigger_item, *pos, None, false);
}; };
}, },

View File

@ -144,6 +144,7 @@ widget_ids! {
skill_sceptre_heal_1, skill_sceptre_heal_1,
skill_sceptre_heal_2, skill_sceptre_heal_2,
skill_sceptre_heal_3, skill_sceptre_heal_3,
skill_sceptre_heal_4,
skill_sceptre_aura_0, skill_sceptre_aura_0,
skill_sceptre_aura_1, skill_sceptre_aura_1,
skill_sceptre_aura_2, skill_sceptre_aura_2,
@ -551,7 +552,7 @@ impl<'a> Widget for Diary<'a> {
SelectedSkillTree::Weapon(ToolKind::Hammer) => 5, SelectedSkillTree::Weapon(ToolKind::Hammer) => 5,
SelectedSkillTree::Weapon(ToolKind::Bow) => 4, SelectedSkillTree::Weapon(ToolKind::Bow) => 4,
SelectedSkillTree::Weapon(ToolKind::Staff) => 5, SelectedSkillTree::Weapon(ToolKind::Staff) => 5,
SelectedSkillTree::Weapon(ToolKind::Sceptre) => 4, SelectedSkillTree::Weapon(ToolKind::Sceptre) => 5,
_ => 0, _ => 0,
}; };
let skills_bot_l = match sel_tab { let skills_bot_l = match sel_tab {
@ -1912,10 +1913,10 @@ impl<'a> Widget for Diary<'a> {
&diary_tooltip, &diary_tooltip,
); );
self.create_unlock_skill_button( self.create_unlock_skill_button(
Skill::Sceptre(HCost), Skill::Sceptre(HDuration),
self.imgs.heal_cost_skill, self.imgs.heal_duration_skill,
state.skills_top_r[2], state.skills_top_r[2],
"sc_heal_cost", "sc_heal_duration",
state.skill_sceptre_heal_2, state.skill_sceptre_heal_2,
ui, ui,
&mut events, &mut events,
@ -1923,7 +1924,7 @@ impl<'a> Widget for Diary<'a> {
); );
self.create_unlock_skill_button( self.create_unlock_skill_button(
Skill::Sceptre(HRange), Skill::Sceptre(HRange),
self.imgs.heal_distance_skill, self.imgs.heal_radius_skill,
state.skills_top_r[3], state.skills_top_r[3],
"sc_heal_range", "sc_heal_range",
state.skill_sceptre_heal_3, state.skill_sceptre_heal_3,
@ -1931,6 +1932,16 @@ impl<'a> Widget for Diary<'a> {
&mut events, &mut events,
&diary_tooltip, &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 // Bottom left skills
self.create_unlock_skill_button( self.create_unlock_skill_button(
Skill::Sceptre(UnlockAura), Skill::Sceptre(UnlockAura),

View File

@ -494,7 +494,7 @@ image_ids! {
snake_arrow_0: "voxygen.element.skills.snake", snake_arrow_0: "voxygen.element.skills.snake",
skill_sceptre_lifesteal: "voxygen.element.skills.lifesteal", skill_sceptre_lifesteal: "voxygen.element.skills.lifesteal",
sword_whirlwind: "voxygen.element.skills.sword_whirlwind", 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", hammerleap: "voxygen.element.skills.skill_hammerleap",
skill_axe_leap_slash: "voxygen.element.skills.skill_axe_leap_slash", skill_axe_leap_slash: "voxygen.element.skills.skill_axe_leap_slash",
skill_bow_jump_burst: "voxygen.element.skills.skill_bow_jump_burst", skill_bow_jump_burst: "voxygen.element.skills.skill_bow_jump_burst",

View File

@ -64,7 +64,7 @@ pub enum ParticleMode {
Firefly = 11, Firefly = 11,
Bee = 12, Bee = 12,
GroundShockwave = 13, GroundShockwave = 13,
HealingBeam = 14, EnergyHealing = 14,
EnergyNature = 15, EnergyNature = 15,
FlameThrower = 16, FlameThrower = 16,
FireShockwave = 17, FireShockwave = 17,

View File

@ -1302,32 +1302,6 @@ impl FigureMgr {
skeleton_attr, 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) => { CharacterState::ComboMelee(s) => {
let stage_index = (s.stage - 1) as usize; let stage_index = (s.stage - 1) as usize;
let stage_time = s.timer.as_secs_f32(); let stage_time = s.timer.as_secs_f32();

View File

@ -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 => { beam::FrontendSpecifier::LifestealBeam => {
// Emit a light when using lifesteal beam // Emit a light when using lifesteal beam
lights.push(Light::new(pos.0, Rgb::new(0.8, 1.0, 0.5), 1.0)); 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::<f32>().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,
)
},
);
},
_ => {}, _ => {},
} }
} }