diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron index 8b6b235b6f..90a23b9b8d 100644 --- a/assets/common/abilities/sceptre/healingbeam.ron +++ b/assets/common/abilities/sceptre/healingbeam.ron @@ -2,13 +2,13 @@ BasicBeam( buildup_duration: 0.25, recover_duration: 0.25, beam_duration: 1.0, - base_hps: 60, - base_dps: 60, + base_hps: 80, + base_dps: 0, tick_rate: 2.0, range: 25.0, max_angle: 1.0, - lifesteal_eff: 0.15, - energy_regen: 25, + lifesteal_eff: 0.0, + energy_regen: 0, energy_cost: 50, energy_drain: 0, orientation_behavior: Normal, diff --git a/assets/common/abilities/sceptre/healingbomb.ron b/assets/common/abilities/sceptre/healingbomb.ron deleted file mode 100644 index a7d0be6de3..0000000000 --- a/assets/common/abilities/sceptre/healingbomb.ron +++ /dev/null @@ -1,19 +0,0 @@ -BasicRanged( - energy_cost: 450, - buildup_duration: 0.8, - recover_duration: 0.05, - projectile: Heal( - heal: 80.0, - damage: 60.0, - poise_damage: 0, - radius: 6.0, - ), - projectile_body: Object(BoltNature), - /*projectile_light: Some(LightEmitter { - col: (0.0, 1.0, 0.0).into(), - ..Default::default() - }),*/ - projectile_gravity: Some(Gravity(0.5)), - projectile_speed: 40.0, - can_continue: false, -) diff --git a/assets/common/abilities/sceptre/lifestealbeam.ron b/assets/common/abilities/sceptre/lifestealbeam.ron new file mode 100644 index 0000000000..5f403d85d9 --- /dev/null +++ b/assets/common/abilities/sceptre/lifestealbeam.ron @@ -0,0 +1,15 @@ +BasicBeam( + buildup_duration: 0.25, + recover_duration: 0.25, + beam_duration: 1.0, + base_hps: 0, + base_dps: 80, + tick_rate: 2.0, + range: 25.0, + max_angle: 1.0, + lifesteal_eff: 0.15, + energy_regen: 25, + energy_cost: 0, + energy_drain: 0, + orientation_behavior: Normal, +) \ No newline at end of file diff --git a/assets/common/abilities/sceptre/wardingaura.ron b/assets/common/abilities/sceptre/wardingaura.ron new file mode 100644 index 0000000000..0fb1815e7c --- /dev/null +++ b/assets/common/abilities/sceptre/wardingaura.ron @@ -0,0 +1,14 @@ +CastAura( + buildup_duration: 0.25, + cast_duration: 1.5, + recover_duration: 0.25, + targets: InGroup, + aura: ( + kind: Invulnerability, + strength: 1.0, + duration: Some(0.5), + category: Magical, + ), + range: 25.0, + energy_cost: 400, +) \ No newline at end of file diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index 7d13b63a8f..2c9f072a6c 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -70,9 +70,11 @@ ], ), Sceptre: ( - primary: "common.abilities.sceptre.healingbeam", - secondary: "common.abilities.sceptre.healingbomb", - abilities: [], + primary: "common.abilities.sceptre.lifestealbeam", + secondary: "common.abilities.sceptre.healingbeam", + abilities: [ + (None, "common.abilities.sceptre.wardingaura"), + ], ), Dagger: ( primary: "common.abilities.dagger.tempbasic", diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 0100612828..c91a0003a5 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1,7 +1,8 @@ use crate::{ assets::{self, Asset}, + combat, comp::{ - inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, Body, + aura, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills, Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate, }, states::{ @@ -227,6 +228,15 @@ pub enum CharacterAbility { energy_drain: f32, orientation_behavior: basic_beam::MovementBehavior, }, + CastAura { + buildup_duration: f32, + cast_duration: f32, + recover_duration: f32, + targets: combat::GroupTarget, + aura: aura::AuraBuffConstructor, + range: f32, + energy_cost: f32, + }, } impl Default for CharacterAbility { @@ -264,34 +274,14 @@ impl CharacterAbility { .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok() }, - CharacterAbility::DashMelee { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), - CharacterAbility::BasicMelee { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), - CharacterAbility::BasicRanged { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), - CharacterAbility::LeapMelee { energy_cost, .. } => { - update.vel.0.z >= 0.0 - && update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok() - }, - CharacterAbility::SpinMelee { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), - CharacterAbility::ChargedRanged { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), - CharacterAbility::ChargedMelee { energy_cost, .. } => update + CharacterAbility::DashMelee { energy_cost, .. } + | CharacterAbility::BasicMelee { energy_cost, .. } + | CharacterAbility::BasicRanged { energy_cost, .. } + | CharacterAbility::SpinMelee { energy_cost, .. } + | CharacterAbility::ChargedRanged { energy_cost, .. } + | CharacterAbility::ChargedMelee { energy_cost, .. } + | CharacterAbility::Shockwave { energy_cost, .. } + | CharacterAbility::CastAura { energy_cost, .. } => update .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), @@ -304,10 +294,13 @@ impl CharacterAbility { .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok() }, - CharacterAbility::Shockwave { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), + CharacterAbility::LeapMelee { energy_cost, .. } => { + update.vel.0.z >= 0.0 + && update + .energy + .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .is_ok() + }, _ => true, } } @@ -500,6 +493,17 @@ impl CharacterAbility { *base_dps *= power * speed; *tick_rate *= speed; }, + CastAura { + ref mut buildup_duration, + ref mut recover_duration, + // cast_duration explicitly not affected by speed + ref mut aura, + .. + } => { + *buildup_duration /= speed; + *recover_duration /= speed; + aura.strength *= power; + }, } self } @@ -517,7 +521,8 @@ impl CharacterAbility { | ChargedMelee { energy_cost, .. } | ChargedRanged { energy_cost, .. } | Shockwave { energy_cost, .. } - | BasicBeam { energy_cost, .. } => *energy_cost as u32, + | BasicBeam { energy_cost, .. } + | CastAura { energy_cost, .. } => *energy_cost as u32, BasicBlock | Boost { .. } | ComboMelee { .. } => 0, } } @@ -1467,6 +1472,27 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { stage_section: StageSection::Buildup, offset: Vec3::zero(), }), + CharacterAbility::CastAura { + buildup_duration, + cast_duration, + recover_duration, + targets, + aura, + range, + energy_cost: _, + } => CharacterState::CastAura(cast_aura::Data { + static_data: cast_aura::StaticData { + buildup_duration: Duration::from_secs_f32(*buildup_duration), + cast_duration: Duration::from_secs_f32(*cast_duration), + recover_duration: Duration::from_secs_f32(*recover_duration), + targets: *targets, + aura: *aura, + range: *range, + ability_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + }), } } } diff --git a/common/src/comp/aura.rs b/common/src/comp/aura.rs index eb82775cdb..e172878d4c 100644 --- a/common/src/comp/aura.rs +++ b/common/src/comp/aura.rs @@ -1,4 +1,5 @@ use crate::{ + combat::GroupTarget, comp::buff::{BuffCategory, BuffData, BuffKind, BuffSource}, uid::Uid, }; @@ -62,11 +63,24 @@ pub enum AuraTarget { /// Targets the group of the entity specified by the `Uid`. This is useful /// for auras which should only affect a player's party. GroupOf(Uid), - + /// Targets everyone not in the group of the entity specified by the `Uid`. + /// This is useful for auras which should only affect a player's + /// enemies. + NotGroupOf(Uid), /// Targets all entities. This is for auras which are global or neutral. All, } +impl From<(Option, Option<&Uid>)> for AuraTarget { + fn from((target, uid): (Option, Option<&Uid>)) -> Self { + match (target, uid) { + (Some(GroupTarget::InGroup), Some(uid)) => Self::GroupOf(*uid), + (Some(GroupTarget::OutOfGroup), Some(uid)) => Self::NotGroupOf(*uid), + _ => Self::All, + } + } +} + impl Aura { /// Creates a new Aura to be assigned to an entity pub fn new( @@ -104,6 +118,35 @@ impl Auras { pub fn remove(&mut self, key: AuraKey) { self.auras.remove(key); } } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct AuraBuffConstructor { + pub kind: BuffKind, + pub strength: f32, + pub duration: Option, + pub category: BuffCategory, +} + +impl AuraBuffConstructor { + pub fn to_aura( + self, + uid: &Uid, + radius: f32, + duration: Option, + target: AuraTarget, + ) -> Aura { + let aura_kind = AuraKind::Buff { + kind: self.kind, + data: BuffData { + strength: self.strength, + duration: self.duration.map(Duration::from_secs_f32), + }, + category: self.category, + source: BuffSource::Character { by: *uid }, + }; + Aura::new(aura_kind, radius, duration, target) + } +} + impl Component for Auras { type Storage = DerefFlaggedStorage>; } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 74e39f2bbf..27f21f5fb7 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -81,6 +81,8 @@ pub enum CharacterState { /// A continuous attack that affects all creatures in a cone originating /// from the source BasicBeam(basic_beam::Data), + /// Creates an aura that persists as long as you are actively casting + CastAura(cast_aura::Data), } impl CharacterState { @@ -100,6 +102,7 @@ impl CharacterState { | CharacterState::RepeaterRanged(_) | CharacterState::Shockwave(_) | CharacterState::BasicBeam(_) + | CharacterState::CastAura(_) ) } @@ -121,6 +124,7 @@ impl CharacterState { | CharacterState::RepeaterRanged(_) | CharacterState::Shockwave(_) | CharacterState::BasicBeam(_) + | CharacterState::CastAura(_) ) } diff --git a/common/src/states/cast_aura.rs b/common/src/states/cast_aura.rs new file mode 100644 index 0000000000..4582698e94 --- /dev/null +++ b/common/src/states/cast_aura.rs @@ -0,0 +1,135 @@ +use crate::{ + combat::GroupTarget, + comp::{ + aura::{AuraBuffConstructor, AuraChange, AuraTarget}, + CharacterState, StateUpdate, + }, + event::ServerEvent, + states::{ + behavior::{CharacterBehavior, JoinData}, + utils::*, + }, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should create the aura + pub buildup_duration: Duration, + /// How long the state is creating an aura + pub cast_duration: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// Determines how the aura selects its targets + pub targets: GroupTarget, + /// Has information used to construct the aura + pub aura: AuraBuffConstructor, + /// Radius of aura + pub range: f32, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} + +#[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_move(data, &mut update, 0.6); + handle_jump(data, &mut update); + if !ability_key_is_pressed(data, self.static_data.ability_info.key) { + handle_interrupt(data, &mut update, false); + match update.character { + CharacterState::CastAura(_) => {}, + _ => { + return update; + }, + } + } + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::CastAura(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Creates aura + let targets = + AuraTarget::from((Some(self.static_data.targets), Some(data.uid))); + let aura = self.static_data.aura.to_aura( + data.uid, + self.static_data.range, + Some(self.static_data.cast_duration), + targets, + ); + update.server_events.push_front(ServerEvent::Aura { + entity: data.entity, + aura_change: AuraChange::Add(aura), + }); + // Build up + update.character = CharacterState::CastAura(Data { + timer: Duration::default(), + stage_section: StageSection::Cast, + ..*self + }); + } + }, + StageSection::Cast => { + if self.timer < self.static_data.cast_duration { + // Cast + update.character = CharacterState::CastAura(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + update.character = CharacterState::CastAura(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + update.character = CharacterState::CastAura(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + }, + } + + update + } +} diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index bd505fbbe0..60bdb2a477 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -4,6 +4,7 @@ pub mod basic_melee; pub mod basic_ranged; pub mod behavior; pub mod boost; +pub mod cast_aura; pub mod charged_melee; pub mod charged_ranged; pub mod climb; diff --git a/common/sys/src/aura.rs b/common/sys/src/aura.rs index bf6c23875d..95074e9ea9 100644 --- a/common/sys/src/aura.rs +++ b/common/sys/src/aura.rs @@ -7,7 +7,7 @@ use common::{ }, event::{EventBus, ServerEvent}, resources::DeltaTime, - uid::UidAllocator, + uid::{Uid, UidAllocator}, }; use common_ecs::{Job, Origin, Phase, System}; use specs::{ @@ -26,6 +26,7 @@ pub struct ReadData<'a> { char_states: ReadStorage<'a, CharacterState>, healths: ReadStorage<'a, Health>, groups: ReadStorage<'a, Group>, + uids: ReadStorage<'a, Uid>, } #[derive(Default)] @@ -79,11 +80,12 @@ impl<'a> System<'a> for Sys { expired_auras.push(key); } } - for (target, target_pos, mut target_buffs, health) in ( + for (target, target_pos, mut target_buffs, health, target_uid) in ( &read_data.entities, &read_data.positions, &mut buffs, &read_data.healths, + &read_data.uids, ) .join() { @@ -96,7 +98,8 @@ impl<'a> System<'a> for Sys { .and_then(|e| read_data.groups.get(e)) .map_or(false, |owner_group| { Some(owner_group) == read_data.groups.get(target) - }); + }) + || *target_uid == uid; if !same_group { continue; diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index 1dcc7f6b9a..fe00e76512 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -303,6 +303,7 @@ impl<'a> System<'a> for Sys { CharacterState::RepeaterRanged(data) => data.handle_event(&j, action), CharacterState::Shockwave(data) => data.handle_event(&j, action), CharacterState::BasicBeam(data) => data.handle_event(&j, action), + CharacterState::CastAura(data) => data.handle_event(&j, action), }; local_emitter.append(&mut state_update.local_events); server_emitter.append(&mut state_update.server_events); @@ -342,6 +343,7 @@ impl<'a> System<'a> for Sys { CharacterState::RepeaterRanged(data) => data.behavior(&j), CharacterState::Shockwave(data) => data.behavior(&j), CharacterState::BasicBeam(data) => data.behavior(&j), + CharacterState::CastAura(data) => data.behavior(&j), }; local_emitter.append(&mut state_update.local_events); diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index bc7fbe02a8..638c15a115 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -232,7 +232,8 @@ impl<'a> System<'a> for Sys { | CharacterState::ChargedRanged { .. } | CharacterState::RepeaterRanged { .. } | CharacterState::Shockwave { .. } - | CharacterState::BasicBeam { .. } => { + | CharacterState::BasicBeam { .. } + | CharacterState::CastAura { .. } => { if energy.get_unchecked().regen_rate != 0.0 { energy.get_mut_unchecked().regen_rate = 0.0 } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 6f2c5fc852..379fb920e5 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -184,6 +184,7 @@ impl StateExt for State { .with(inventory) .with(comp::Buffs::default()) .with(comp::Combo::default()) + .with(comp::Auras::default()) } fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder { @@ -275,6 +276,7 @@ impl StateExt for State { comp::Alignment::Owned(self.read_component_copied(entity).unwrap()), ); self.write_component(entity, comp::Buffs::default()); + self.write_component(entity, comp::Auras::default()); self.write_component(entity, comp::Combo::default()); // Make sure physics components are updated