This commit is contained in:
Sam 2024-03-27 19:45:27 -04:00
parent e1ce09e0a1
commit 7daa9a29eb
19 changed files with 515 additions and 103 deletions

View File

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

View File

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

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

Binary file not shown.

View File

@ -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.

View File

@ -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: [

View File

@ -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::AuraBuffConstructor>,
aura_duration: Option<Secs>,
range: f32,
sprite_info: Option<static_aura::SpriteInfo>,
#[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,

View File

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

View File

@ -433,6 +433,12 @@ pub struct RequestPluginsEvent {
pub plugins: Vec<PluginHash>,
}
pub struct CreateAuraEntityEvent {
pub auras: comp::Auras,
pub pos: Pos,
pub creator_uid: Uid,
}
pub struct EventBus<E> {
queue: Mutex<VecDeque<E>>,
}
@ -556,6 +562,7 @@ pub fn register_event_busses(ecs: &mut World) {
ecs.insert(EventBus::<ToggleSpriteLightEvent>::default());
ecs.insert(EventBus::<TransformEvent>::default());
ecs.insert(EventBus::<RequestPluginsEvent>::default());
ecs.insert(EventBus::<CreateAuraEntityEvent>::default());
}
/// Define ecs read data for event busses. And a way to convert them all to

View File

@ -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;

View File

@ -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<f32>,
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
}

View File

@ -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<AuraBuffConstructor>,
/// How long aura lasts
pub aura_duration: Option<Secs>,
/// Radius of aura
pub range: f32,
/// Information about sprites if the state should create sprites
pub sprite_info: Option<SpriteInfo>,
/// 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<i32>,
}
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,
}

View File

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

View File

@ -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();
}

View File

@ -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();
}

View File

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

View File

@ -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);
},
_ => {},
}

View File

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

View File

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

View File

@ -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 {