From 7daa9a29ebfe1d6717da95e2673be9adafc388ff Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 27 Mar 2024 19:45:27 -0400 Subject: [PATCH] Rampart --- .../common/abilities/ability_set_manifest.ron | 2 +- assets/common/abilities/hammer/rampart.ron | 23 ++ .../voxygen/element/skills/hammer/rampart.png | 3 + assets/voxygen/i18n/en/hud/ability.ftl | 3 + assets/voxygen/voxel/sprite_manifest.ron | 15 ++ common/src/comp/ability.rs | 97 ++++++++- common/src/comp/character_state.rs | 18 ++ common/src/event.rs | 7 + common/src/states/mod.rs | 1 + common/src/states/sprite_summon.rs | 201 ++++++++++-------- common/src/states/static_aura.rs | 169 +++++++++++++++ common/src/terrain/sprite.rs | 1 + common/systems/src/stats.rs | 3 +- server/src/events/entity_creation.rs | 19 +- server/src/events/mod.rs | 8 +- voxygen/anim/src/character/shockwave.rs | 23 ++ voxygen/src/hud/img_ids.rs | 1 + voxygen/src/hud/util.rs | 1 + voxygen/src/scene/figure/mod.rs | 23 ++ 19 files changed, 515 insertions(+), 103 deletions(-) create mode 100644 assets/common/abilities/hammer/rampart.ron create mode 100644 assets/voxygen/element/skills/hammer/rampart.png create mode 100644 common/src/states/static_aura.rs diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index b4d1b67708..b727046327 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -235,7 +235,7 @@ Simple(Hammer(PileDriver), "common.abilities.hammer.pile_driver"), Simple(Hammer(LungPummel), "common.abilities.hammer.lung_pummel"), Simple(Hammer(HelmCrusher), "common.abilities.hammer.helm_crusher"), - // Simple(Hammer(Rampart), "common.abilities.hammer.rampart"), + Simple(Hammer(Rampart), "common.abilities.hammer.rampart"), // Simple(Hammer(Tenacity), "common.abilities.hammer.tenacity"), // Simple(Hammer(Earthshaker), "common.abilities.hammer.earthshaker"), // Simple(Hammer(Judgement), "common.abilities.hammer.judgement"), diff --git a/assets/common/abilities/hammer/rampart.ron b/assets/common/abilities/hammer/rampart.ron new file mode 100644 index 0000000000..890c81c3d1 --- /dev/null +++ b/assets/common/abilities/hammer/rampart.ron @@ -0,0 +1,23 @@ +StaticAura( + buildup_duration: 0.4, + cast_duration: 0.3, + recover_duration: 0.4, + energy_cost: 20, + targets: InGroup, + auras: [ + ( + kind: ProtectingWard, + strength: 0.3, + duration: Some(1), + category: Magical, + ), + ], + aura_duration: Some(20), + range: 10.0, + sprite_info: Some(( + sprite: Stones2, + del_timeout: Some((19, 3)), + summon_distance: (7, 10), + sparseness: 0.97, + )), +) \ No newline at end of file diff --git a/assets/voxygen/element/skills/hammer/rampart.png b/assets/voxygen/element/skills/hammer/rampart.png new file mode 100644 index 0000000000..edc8a7a49a --- /dev/null +++ b/assets/voxygen/element/skills/hammer/rampart.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50ac2cd5fbfc23e7d4ac69eb1a692635c9928cc31c62a1bc9398c464c67a076d +size 1018 diff --git a/assets/voxygen/i18n/en/hud/ability.ftl b/assets/voxygen/i18n/en/hud/ability.ftl index 82a56c9e20..64968155b6 100644 --- a/assets/voxygen/i18n/en/hud/ability.ftl +++ b/assets/voxygen/i18n/en/hud/ability.ftl @@ -434,3 +434,6 @@ common-abilities-hammer-upheaval = Upheaval common-abilities-hammer-dual_upheaval = Upheaval .desc = Slam your hammers into your foes, knocking them into the air and leaving them vulnerable to staggers. +common-abilities-hammer-rampart = Rampart + .desc = + Strike the ground, causing very mild tectonic uplift which protects your allies from attacks. diff --git a/assets/voxygen/voxel/sprite_manifest.ron b/assets/voxygen/voxel/sprite_manifest.ron index 8b76d9f6dc..1ed8b4d9da 100644 --- a/assets/voxygen/voxel/sprite_manifest.ron +++ b/assets/voxygen/voxel/sprite_manifest.ron @@ -2953,6 +2953,21 @@ ], wind_sway: 0.0, ), +(Stones2, ()): ( + variations: [ + ( + model: "voxygen.voxel.sprite.rocks.rock-0", + offset: (-3.0, -3.5, 0.0), + lod_axes: (1.0, 1.0, 1.0), + ), + ( + model: "voxygen.voxel.sprite.rocks.rock-2", + offset: (-4.5, -4.5, 0.0), + lod_axes: (1.0, 1.0, 1.0), + ), + ], + wind_sway: 0.0, +), // Twigs (Twigs, ()): ( variations: [ diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 54bca19c7c..e1aed62a54 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -735,7 +735,8 @@ impl From<&CharacterState> for CharacterAbilityType { | CharacterState::SpriteInteract(_) | CharacterState::Skate(_) | CharacterState::Transform(_) - | CharacterState::Wallrun(_) => Self::Other, + | CharacterState::Wallrun(_) + | CharacterState::StaticAura(_) => Self::Other, } } } @@ -974,6 +975,19 @@ pub enum CharacterAbility { #[serde(default)] meta: AbilityMeta, }, + StaticAura { + buildup_duration: f32, + cast_duration: f32, + recover_duration: f32, + energy_cost: f32, + targets: combat::GroupTarget, + auras: Vec, + aura_duration: Option, + range: f32, + sprite_info: Option, + #[serde(default)] + meta: AbilityMeta, + }, Blink { buildup_duration: f32, recover_duration: f32, @@ -1146,7 +1160,12 @@ impl CharacterAbility { }; from_meta && match self { - CharacterAbility::Roll { energy_cost, .. } => { + CharacterAbility::Roll { energy_cost, .. } + | CharacterAbility::StaticAura { + energy_cost, + sprite_info: Some(_), + .. + } => { data.physics.on_ground.is_some() && update.energy.try_change_by(-*energy_cost).is_ok() }, @@ -1161,6 +1180,11 @@ impl CharacterAbility { | CharacterAbility::ComboMelee2 { energy_cost_per_strike: energy_cost, .. + } + | CharacterAbility::StaticAura { + energy_cost, + sprite_info: None, + .. } => update.energy.try_change_by(-*energy_cost).is_ok(), // Consumes energy within state, so value only checked before entering state CharacterAbility::RepeaterRanged { energy_cost, .. } => { @@ -1610,6 +1634,39 @@ impl CharacterAbility { *range *= stats.range; *energy_cost /= stats.energy_efficiency; }, + StaticAura { + ref mut buildup_duration, + ref mut cast_duration, + ref mut recover_duration, + targets: _, + ref mut auras, + aura_duration: _, + ref mut range, + ref mut energy_cost, + ref mut sprite_info, + meta: _, + } => { + *buildup_duration /= stats.speed; + *cast_duration /= stats.speed; + *recover_duration /= stats.speed; + auras.iter_mut().for_each( + |aura::AuraBuffConstructor { + kind: _, + ref mut strength, + duration: _, + category: _, + }| { + *strength *= stats.diminished_buff_strength(); + }, + ); + *range *= stats.range; + *energy_cost /= stats.energy_efficiency; + *sprite_info = sprite_info.map(|mut si| { + si.summon_distance.0 *= stats.range; + si.summon_distance.1 *= stats.range; + si + }); + }, Blink { ref mut buildup_duration, ref mut recover_duration, @@ -1793,7 +1850,8 @@ impl CharacterAbility { } | DiveMelee { energy_cost, .. } | RiposteMelee { energy_cost, .. } - | RapidMelee { energy_cost, .. } => *energy_cost, + | RapidMelee { energy_cost, .. } + | StaticAura { energy_cost, .. } => *energy_cost, BasicBeam { energy_drain, .. } => { if *energy_drain > f32::EPSILON { 1.0 @@ -1856,7 +1914,8 @@ impl CharacterAbility { | Music { .. } | BasicSummon { .. } | SpriteSummon { .. } - | Transform { .. } => 0, + | Transform { .. } + | StaticAura { .. } => 0, } } @@ -1889,7 +1948,8 @@ impl CharacterAbility { | DiveMelee { meta, .. } | RiposteMelee { meta, .. } | RapidMelee { meta, .. } - | Transform { meta, .. } => *meta, + | Transform { meta, .. } + | StaticAura { meta, .. } => *meta, } } @@ -2675,6 +2735,33 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { timer: Duration::default(), stage_section: StageSection::Buildup, }), + CharacterAbility::StaticAura { + buildup_duration, + cast_duration, + recover_duration, + targets, + auras, + aura_duration, + range, + energy_cost: _, + sprite_info, + meta: _, + } => CharacterState::StaticAura(static_aura::Data { + static_data: static_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, + auras: auras.clone(), + aura_duration: *aura_duration, + range: *range, + ability_info, + sprite_info: *sprite_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + achieved_radius: sprite_info.map(|si| si.summon_distance.0.floor() as i32 - 1), + }), CharacterAbility::Blink { buildup_duration, recover_duration, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 3b59261a68..20eebcb340 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -52,6 +52,7 @@ event_emitters! { knockback: event::KnockbackEvent, sprite_light: event::ToggleSpriteLightEvent, transform: event::TransformEvent, + create_aura_entity: event::CreateAuraEntityEvent, } } @@ -142,6 +143,9 @@ pub enum CharacterState { BasicBeam(basic_beam::Data), /// Creates an aura that persists as long as you are actively casting BasicAura(basic_aura::Data), + /// Creates an aura that is attached to a pseudo entity, so it doesn't move + /// with you Optionally allows for sprites to be created as well + StaticAura(static_aura::Data), /// A short teleport that targets either a position or entity Blink(blink::Data), /// Summons creatures that fight for the caster @@ -211,6 +215,7 @@ impl CharacterState { | CharacterState::DiveMelee(_) | CharacterState::RiposteMelee(_) | CharacterState::RapidMelee(_) + | CharacterState::StaticAura(_) ) } @@ -276,6 +281,7 @@ impl CharacterState { | CharacterState::DiveMelee(_) | CharacterState::RiposteMelee(_) | CharacterState::RapidMelee(_) + | CharacterState::StaticAura(_) ) } @@ -532,6 +538,7 @@ impl CharacterState { CharacterState::RiposteMelee(data) => data.behavior(j, output_events), CharacterState::RapidMelee(data) => data.behavior(j, output_events), CharacterState::Transform(data) => data.behavior(j, output_events), + CharacterState::StaticAura(data) => data.behavior(j, output_events), } } @@ -586,6 +593,7 @@ impl CharacterState { CharacterState::RiposteMelee(data) => data.handle_event(j, output_events, action), CharacterState::RapidMelee(data) => data.handle_event(j, output_events, action), CharacterState::Transform(data) => data.handle_event(j, output_events, action), + CharacterState::StaticAura(data) => data.handle_event(j, output_events, action), } } @@ -639,6 +647,7 @@ impl CharacterState { CharacterState::RiposteMelee(data) => Some(data.static_data.ability_info), CharacterState::RapidMelee(data) => Some(data.static_data.ability_info), CharacterState::Transform(data) => Some(data.static_data.ability_info), + CharacterState::StaticAura(data) => Some(data.static_data.ability_info), } } @@ -684,6 +693,7 @@ impl CharacterState { CharacterState::RiposteMelee(data) => Some(data.stage_section), CharacterState::RapidMelee(data) => Some(data.stage_section), CharacterState::Transform(data) => Some(data.stage_section), + CharacterState::StaticAura(data) => Some(data.stage_section), } } @@ -868,6 +878,12 @@ impl CharacterState { recover: Some(data.static_data.recover_duration), ..Default::default() }), + CharacterState::StaticAura(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.cast_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), } } @@ -913,6 +929,7 @@ impl CharacterState { CharacterState::RiposteMelee(data) => Some(data.timer), CharacterState::RapidMelee(data) => Some(data.timer), CharacterState::Transform(data) => Some(data.timer), + CharacterState::StaticAura(data) => Some(data.timer), } } @@ -973,6 +990,7 @@ impl CharacterState { CharacterState::RiposteMelee(_) => Some(AttackSource::Melee), CharacterState::RapidMelee(_) => Some(AttackSource::Melee), CharacterState::Transform(_) => None, + CharacterState::StaticAura(_) => None, } } } diff --git a/common/src/event.rs b/common/src/event.rs index baf65bff1b..e056e378cb 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -433,6 +433,12 @@ pub struct RequestPluginsEvent { pub plugins: Vec, } +pub struct CreateAuraEntityEvent { + pub auras: comp::Auras, + pub pos: Pos, + pub creator_uid: Uid, +} + pub struct EventBus { queue: Mutex>, } @@ -556,6 +562,7 @@ pub fn register_event_busses(ecs: &mut World) { ecs.insert(EventBus::::default()); ecs.insert(EventBus::::default()); ecs.insert(EventBus::::default()); + ecs.insert(EventBus::::default()); } /// Define ecs read data for event busses. And a way to convert them all to diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 4e95af5f3f..5fa3602468 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -33,6 +33,7 @@ pub mod sit; pub mod skate; pub mod sprite_interact; pub mod sprite_summon; +pub mod static_aura; pub mod stunned; pub mod talk; pub mod transform; diff --git a/common/src/states/sprite_summon.rs b/common/src/states/sprite_summon.rs index c52a50120c..1ef7ff7911 100644 --- a/common/src/states/sprite_summon.rs +++ b/common/src/states/sprite_summon.rs @@ -117,99 +117,29 @@ impl CharacterBehavior for Data { let timer_frac = self.timer.as_secs_f32() / self.static_data.cast_duration.as_secs_f32(); - // Determines distance from summoner sprites should be created. Goes outward - // with time. - let summon_distance = timer_frac - * (self.static_data.summon_distance.1 - self.static_data.summon_distance.0) - + self.static_data.summon_distance.0; - let summon_distance = summon_distance.round() as i32; - - // Only summons sprites if summon distance is greater than achieved radius - for radius in self.achieved_radius..=summon_distance { - // 1 added to make range correct, too lazy to add 1 to both variables above - let radius = radius + 1; - // Creates a spiral iterator for the newly achieved radius - let spiral = Spiral2d::with_edge_radius(radius); - for point in spiral { - // If square is in the angle and is not sparse, generate sprite - if data - .ori - .look_vec() - .xy() - .angle_between(point.as_()) - .to_degrees() - <= (self.static_data.angle / 2.0) - && !thread_rng().gen_bool(self.static_data.sparseness) - { - let anchor_pos = match self.static_data.anchor { - SpriteSummonAnchor::Summoner => data.pos.0, - // Use the selected target position, falling back to the - // summoner position - SpriteSummonAnchor::Target => { - target_pos().unwrap_or(data.pos.0) - }, - }; - // The coordinates of where the sprite is created - let sprite_pos = Vec3::new( - anchor_pos.x.floor() as i32 + point.x, - anchor_pos.y.floor() as i32 + point.y, - anchor_pos.z.floor() as i32, - ); - - // Check for collision in z up to 10 blocks up or down - let (obstacle_z, obstale_z_result) = data - .terrain - .ray( - sprite_pos.map(|x| x as f32 + 0.5) + Vec3::unit_z() * 10.0, - sprite_pos.map(|x| x as f32 + 0.5) - Vec3::unit_z() * 10.0, - ) - .until(|b| { - // Until reaching a solid block that is not the created - // sprite - Block::is_solid(b) - && b.get_sprite() != Some(self.static_data.sprite) - }) - .cast(); - - let z = match self.static_data.sprite { - // z height - 1 to delete sprite layer below caster - SpriteKind::Empty => { - sprite_pos.z + (10.5 - obstacle_z).ceil() as i32 - 1 - }, - _ => { - sprite_pos.z - + if let (SpriteSummonAnchor::Target, Ok(None)) = - (&self.static_data.anchor, obstale_z_result) - { - 0 - } else { - (10.5 - obstacle_z).ceil() as i32 - } - }, - }; - - // Location sprite will be created - let sprite_pos = Vec3::new(sprite_pos.x, sprite_pos.y, z); - // Layers of sprites - let layers = match self.static_data.sprite { - SpriteKind::SeaUrchin => 2, - _ => 1, - }; - for i in 0..layers { - // Send server event to create sprite - output_events.emit_server(CreateSpriteEvent { - pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i), - sprite: self.static_data.sprite, - del_timeout: self.static_data.del_timeout, - }); - } - } - } - } + let anchor_pos = match self.static_data.anchor { + SpriteSummonAnchor::Summoner => data.pos.0, + // Use the selected target position, falling back to the + // summoner position + SpriteSummonAnchor::Target => target_pos().unwrap_or(data.pos.0), + }; + let achieved_radius = create_sprites( + data, + output_events, + self.static_data.sprite, + timer_frac, + self.static_data.summon_distance, + self.achieved_radius, + self.static_data.angle, + self.static_data.sparseness, + anchor_pos, + matches!(self.static_data.anchor, SpriteSummonAnchor::Target), + self.static_data.del_timeout, + ); update.character = CharacterState::SpriteSummon(Data { timer: tick_attack_or_default(data, self.timer, None), - achieved_radius: summon_distance, + achieved_radius, ..*self }); // Send local event used for frontend shenanigans @@ -270,3 +200,94 @@ impl CharacterBehavior for Data { update } } + +/// Returns achieved radius +pub fn create_sprites( + data: &JoinData, + output_events: &mut OutputEvents, + sprite: SpriteKind, + timer_frac: f32, + summon_distance: (f32, f32), + achieved_radius: i32, + angle: f32, + sparseness: f64, + anchor_pos: Vec3, + stack_sprites: bool, + del_timeout: Option<(f32, f32)>, +) -> i32 { + // Determines distance from summoner sprites should be created. Goes outward + // with time. + let summon_distance = timer_frac * (summon_distance.1 - summon_distance.0) + summon_distance.0; + let summon_distance = summon_distance.round() as i32; + + // Only summons sprites if summon distance is greater than achieved radius + for radius in achieved_radius..=summon_distance { + // 1 added to make range correct, too lazy to add 1 to both variables above + let radius = radius + 1; + // Creates a spiral iterator for the newly achieved radius + let spiral = Spiral2d::with_edge_radius(radius); + for point in spiral { + // If square is in the angle and is not sparse, generate sprite + if data + .ori + .look_vec() + .xy() + .angle_between(point.as_()) + .to_degrees() + <= (angle / 2.0) + && !thread_rng().gen_bool(sparseness) + { + // The coordinates of where the sprite is created + let sprite_pos = Vec3::new( + anchor_pos.x.floor() as i32 + point.x, + anchor_pos.y.floor() as i32 + point.y, + anchor_pos.z.floor() as i32, + ); + + // Check for collision in z up to 10 blocks up or down + let (obstacle_z, obstacle_z_result) = data + .terrain + .ray( + sprite_pos.map(|x| x as f32 + 0.5) + Vec3::unit_z() * 10.0, + sprite_pos.map(|x| x as f32 + 0.5) - Vec3::unit_z() * 10.0, + ) + .until(|b| { + // Until reaching a solid block that is not the created + // sprite + Block::is_solid(b) && b.get_sprite() != Some(sprite) + }) + .cast(); + + let z = match sprite { + // z height - 1 to delete sprite layer below caster + SpriteKind::Empty => sprite_pos.z + (10.5 - obstacle_z).ceil() as i32 - 1, + _ => { + sprite_pos.z + + if let (true, Ok(None)) = (stack_sprites, obstacle_z_result) { + 0 + } else { + (10.5 - obstacle_z).ceil() as i32 + } + }, + }; + + // Location sprite will be created + let sprite_pos = Vec3::new(sprite_pos.x, sprite_pos.y, z); + // Layers of sprites + let layers = match sprite { + SpriteKind::SeaUrchin => 2, + _ => 1, + }; + for i in 0..layers { + // Send server event to create sprite + output_events.emit_server(CreateSpriteEvent { + pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i), + sprite, + del_timeout, + }); + } + } + } + } + summon_distance +} diff --git a/common/src/states/static_aura.rs b/common/src/states/static_aura.rs new file mode 100644 index 0000000000..6e829dc86d --- /dev/null +++ b/common/src/states/static_aura.rs @@ -0,0 +1,169 @@ +use crate::{ + combat::GroupTarget, + comp::{ + aura::{AuraBuffConstructor, AuraTarget, Auras}, + character_state::OutputEvents, + CharacterState, StateUpdate, + }, + event::CreateAuraEntityEvent, + resources::Secs, + states::{ + behavior::{CharacterBehavior, JoinData}, + sprite_summon::create_sprites, + utils::*, + }, + terrain::SpriteKind, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Separated out to condense update portions of character state +#[derive(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 auras + pub auras: Vec, + /// How long aura lasts + pub aura_duration: Option, + /// Radius of aura + pub range: f32, + /// Information about sprites if the state should create sprites + pub sprite_info: Option, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} + +#[derive(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, + /// If creates sprites, what radius has been achieved so far + pub achieved_radius: Option, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { + let mut update = StateUpdate::from(data); + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::StaticAura(Data { + static_data: self.static_data.clone(), + timer: tick_attack_or_default(data, self.timer, None), + ..*self + }); + } else { + // Build up + update.character = CharacterState::StaticAura(Data { + static_data: self.static_data.clone(), + timer: Duration::default(), + stage_section: StageSection::Action, + achieved_radius: self.achieved_radius, + }); + } + }, + StageSection::Action => { + if self.timer < self.static_data.cast_duration { + // If creates sprites, create sprites + let achieved_radius = if let Some(sprite_info) = self.static_data.sprite_info { + let timer_frac = + self.timer.as_secs_f32() / self.static_data.cast_duration.as_secs_f32(); + + let achieved_radius = create_sprites( + data, + output_events, + sprite_info.sprite, + timer_frac, + sprite_info.summon_distance, + self.achieved_radius.unwrap_or(0), + 360.0, + sprite_info.sparseness, + data.pos.0, + false, + sprite_info.del_timeout, + ); + Some(achieved_radius) + } else { + None + }; + // Cast + update.character = CharacterState::StaticAura(Data { + static_data: self.static_data.clone(), + timer: tick_attack_or_default(data, self.timer, None), + achieved_radius, + ..*self + }); + } else { + // Creates aura + let targets = + AuraTarget::from((Some(self.static_data.targets), Some(data.uid))); + let mut auras = Vec::new(); + for aura_data in &self.static_data.auras { + let aura = aura_data.to_aura( + data.uid, + self.static_data.range, + self.static_data.aura_duration, + targets, + *data.time, + ); + auras.push(aura); + } + output_events.emit_server(CreateAuraEntityEvent { + auras: Auras::new(auras), + pos: *data.pos, + creator_uid: *data.uid, + }); + update.character = CharacterState::StaticAura(Data { + static_data: self.static_data.clone(), + timer: Duration::default(), + stage_section: StageSection::Recover, + achieved_radius: self.achieved_radius, + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + update.character = CharacterState::StaticAura(Data { + static_data: self.static_data.clone(), + timer: tick_attack_or_default(data, self.timer, None), + ..*self + }); + } else { + // Done + end_ability(data, &mut update); + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + end_ability(data, &mut update); + }, + } + + // At end of state logic so an interrupt isn't overwritten + handle_interrupts(data, &mut update, output_events); + + update + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SpriteInfo { + pub sprite: SpriteKind, + pub del_timeout: Option<(f32, f32)>, + pub summon_distance: (f32, f32), + pub sparseness: f64, +} diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index a2f89e0873..d75c710054 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -70,6 +70,7 @@ sprites! { Bomb = 0x02, FireBlock = 0x03, // FireBlock for Burning Buff HotSurface = 0x04, + Stones2 = 0x05, // Same as `Stones` but not collectible }, // Furniture. In the future, we might add an attribute to customise material // TODO: Remove sizes and variants, represent with attributes diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index dc5ab7f41c..ae7957a74e 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -167,7 +167,8 @@ impl<'a> System<'a> for Sys { | CharacterState::FinisherMelee(_) | CharacterState::DiveMelee(_) | CharacterState::RiposteMelee(_) - | CharacterState::RapidMelee(_) => { + | CharacterState::RapidMelee(_) + | CharacterState::StaticAura(_) => { if energy.needs_regen_rate_reset() { energy.reset_regen_rate(); } diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 14c4d56a26..3b72a241a1 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -12,9 +12,9 @@ use common::{ WaypointArea, }, event::{ - CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent, CreateShipEvent, - CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent, InitializeSpectatorEvent, - ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent, + CreateAuraEntityEvent, CreateItemDropEvent, CreateNpcEvent, CreateObjectEvent, + CreateShipEvent, CreateSpecialEntityEvent, EventBus, InitializeCharacterEvent, + InitializeSpectatorEvent, ShockwaveEvent, ShootEvent, UpdateCharacterDataEvent, }, generation::SpecialEntity, mounting::{Mounting, Volume, VolumeMounting, VolumePos}, @@ -489,3 +489,16 @@ pub fn handle_create_object( .maybe_with(stats) .build(); } + +pub fn handle_create_aura_entity(server: &mut Server, ev: CreateAuraEntityEvent) { + server + .state + .ecs_mut() + .create_entity_synced() + .with(ev.pos) + .with(comp::Vel(Vec3::zero())) + .with(comp::Ori::default()) + .with(ev.auras) + .with(comp::Alignment::Owned(ev.creator_uid)) + .build(); +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index b7c5dc60eb..25e3d56f36 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -13,9 +13,10 @@ use specs::{ use self::{ entity_creation::{ - handle_create_item_drop, handle_create_npc, handle_create_object, handle_create_ship, - handle_create_special_entity, handle_initialize_character, handle_initialize_spectator, - handle_loaded_character_data, handle_shockwave, handle_shoot, + handle_create_aura_entity, handle_create_item_drop, handle_create_npc, + handle_create_object, handle_create_ship, handle_create_special_entity, + handle_initialize_character, handle_initialize_spectator, handle_loaded_character_data, + handle_shockwave, handle_shoot, }, entity_manipulation::{handle_delete, handle_transform}, interaction::handle_tame_pet, @@ -149,6 +150,7 @@ impl Server { self.handle_serial_events(handle_create_special_entity); self.handle_serial_events(handle_create_item_drop); self.handle_serial_events(handle_create_object); + self.handle_serial_events(handle_create_aura_entity); self.handle_serial_events(handle_delete); self.handle_serial_events(handle_character_delete); diff --git a/voxygen/anim/src/character/shockwave.rs b/voxygen/anim/src/character/shockwave.rs index 57fb7057a9..9ac451a538 100644 --- a/voxygen/anim/src/character/shockwave.rs +++ b/voxygen/anim/src/character/shockwave.rs @@ -3,6 +3,7 @@ use super::{ hammer_start, twist_back, twist_forward, CharacterSkeleton, SkeletonAttr, }; use common::states::utils::StageSection; +use std::f32::consts::PI; pub struct Input { pub attack: bool, @@ -136,6 +137,28 @@ impl Animation for ShockwaveAnimation { next.control.position += Vec3::new(-16.0, 0.0, 0.0) * move2; next.chest.orientation.rotate_x(-0.8 * move2); }, + Some("common.abilities.hammer.rampart") => { + hammer_start(&mut next, s_a); + let (move1, move2, move3) = match stage_section { + Some(StageSection::Buildup) => (anim_time, 0.0, 0.0), + Some(StageSection::Action) => (1.0, anim_time, 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time), + _ => (0.0, 0.0, 0.0), + }; + let pullback = 1.0 - move3; + let move1 = move1 * pullback; + let move2 = move2 * pullback; + + next.control.orientation.rotate_x(move1 * 0.6); + next.control.orientation.rotate_y(move1 * -PI / 2.0); + next.hand_l.orientation.rotate_y(move1 * -PI); + next.hand_r.orientation.rotate_y(move1 * -PI); + next.control.position += Vec3::new(-5.0, 0.0, 30.0) * move1; + + next.control.position += Vec3::new(0.0, 0.0, -10.0) * move2; + next.torso.orientation.rotate_x(move2 * -0.6); + next.control.orientation.rotate_x(move2 * 0.6); + }, _ => {}, } diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 34a40f058d..714a79358b 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -330,6 +330,7 @@ image_ids! { hammer_helm_crusher: "voxygen.element.skills.hammer.helm_crusher", hammer_iron_tempest: "voxygen.element.skills.hammer.iron_tempest", hammer_upheaval: "voxygen.element.skills.hammer.upheaval", + hammer_rampart: "voxygen.element.skills.hammer.rampart", // Skilltree Icons health_plus_skill: "voxygen.element.skills.skilltree.health_plus", energy_plus_skill: "voxygen.element.skills.skilltree.energy_plus", diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index de89dd3f5a..47ddb53f64 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -653,6 +653,7 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id { "common.abilities.hammer.dual_iron_tempest" => imgs.hammer_iron_tempest, "common.abilities.hammer.upheaval" => imgs.hammer_upheaval, "common.abilities.hammer.dual_upheaval" => imgs.hammer_upheaval, + "common.abilities.hammer.rampart" => imgs.hammer_rampart, // Bow "common.abilities.bow.charged" => imgs.bow_m1, "common.abilities.bow.repeater" => imgs.bow_m2, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 1e39700673..dbda2c46bb 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1674,6 +1674,29 @@ impl FigureMgr { skeleton_attr, ) }, + CharacterState::StaticAura(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::Action => { + stage_time / s.static_data.cast_duration.as_secs_f32() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f32() + }, + _ => 0.0, + }; + + anim::character::ShockwaveAnimation::update_skeleton( + &target_base, + (ability_id, time, rel_vel.magnitude(), Some(s.stage_section)), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, CharacterState::LeapMelee(s) => { let stage_time = s.timer.as_secs_f32(); let stage_progress = match s.stage_section {