diff --git a/CHANGELOG.md b/CHANGELOG.md index 13faa58783..d58d790b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Buff system - Sneaking lets you be closer to enemies without being detected - Flight -- Roll has i-frames +- Roll dodges melee attacks, and reduces the height of your hitbox ### Changed diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 623fce073d..47e6213182 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -119,9 +119,6 @@ pub enum CharacterAbility { movement_duration: Duration, recover_duration: Duration, roll_strength: f32, - buildup_iframes: bool, - movement_iframes: bool, - recover_iframes: bool, }, ComboMelee { stage_data: Vec, @@ -278,6 +275,16 @@ impl CharacterAbility { _ => true, } } + + fn default_roll() -> CharacterAbility { + CharacterAbility::Roll { + energy_cost: 100, + buildup_duration: Duration::from_millis(100), + movement_duration: Duration::from_millis(250), + recover_duration: Duration::from_millis(150), + roll_strength: 2.5, + } + } } #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] @@ -302,7 +309,7 @@ impl From for ItemConfig { ability2: ability_drain.next(), ability3: ability_drain.next(), block_ability: None, - dodge_ability: Some(get_roll()), + dodge_ability: Some(CharacterAbility::default_roll()), }; } @@ -310,19 +317,6 @@ impl From for ItemConfig { } } -fn get_roll() -> CharacterAbility { - CharacterAbility::Roll { - energy_cost: 100, - buildup_duration: Duration::from_millis(100), - movement_duration: Duration::from_millis(250), - recover_duration: Duration::from_millis(150), - roll_strength: 2.5, - buildup_iframes: true, - movement_iframes: true, - recover_iframes: false, - } -} - #[derive(Arraygen, Clone, PartialEq, Default, Debug, Serialize, Deserialize)] #[gen_array(pub fn get_armor: &Option)] pub struct Loadout { @@ -488,18 +482,12 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { movement_duration, recover_duration, roll_strength, - buildup_iframes, - movement_iframes, - recover_iframes, } => CharacterState::Roll(roll::Data { static_data: roll::StaticData { buildup_duration: *buildup_duration, movement_duration: *movement_duration, recover_duration: *recover_duration, roll_strength: *roll_strength, - buildup_iframes: *buildup_iframes, - movement_iframes: *movement_iframes, - recover_iframes: *recover_iframes, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index e90ad1d404..f1b7e91502 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -145,20 +145,6 @@ impl CharacterState { // Check if state is the same without looking at the inner data std::mem::discriminant(self) == std::mem::discriminant(other) } - - pub fn is_invincible(&self) -> bool { - if let CharacterState::Roll(data) = self { - use utils::StageSection; - match data.stage_section { - StageSection::Buildup => data.static_data.buildup_iframes, - StageSection::Movement => data.static_data.movement_iframes, - StageSection::Recover => data.static_data.recover_iframes, - _ => false, - } - } else { - false - } - } } impl Default for CharacterState { diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index c59c038333..902d391737 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -73,9 +73,9 @@ impl Collider { } } - pub fn get_z_limits(&self) -> (f32, f32) { + pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { match self { - Collider::Box { z_min, z_max, .. } => (*z_min, *z_max), + Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::Point => (0.0, 0.0), } } diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index 587d314147..45decbb376 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -15,14 +15,8 @@ pub struct StaticData { pub movement_duration: Duration, /// How long it takes to recover from roll pub recover_duration: Duration, - /// How strong the roll is + /// Affects the speed and distance of the roll pub roll_strength: f32, - /// Whether you are immune to damage in buildup - pub buildup_iframes: bool, - /// Whether you are immune to damage in movement - pub movement_iframes: bool, - /// Whether you are immune to damage in recover - pub recover_iframes: bool, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index d5cd98eb98..0642e0e9f7 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, Health, - HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, + group, Beam, BeamSegment, Body, Energy, EnergyChange, EnergySource, Health, HealthChange, + HealthSource, Last, Loadout, Ori, Pos, Scale, }, event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, @@ -34,7 +34,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Energy>, WriteStorage<'a, BeamSegment>, WriteStorage<'a, Beam>, - ReadStorage<'a, CharacterState>, ); fn run( @@ -57,7 +56,6 @@ impl<'a> System<'a> for Sys { energies, mut beam_segments, mut beams, - char_states, ): Self::SystemData, ) { let mut server_emitter = server_bus.emitter(); @@ -114,16 +112,7 @@ impl<'a> System<'a> for Sys { }; // Go through all other effectable entities - for ( - b, - uid_b, - pos_b, - last_pos_b_maybe, - scale_b_maybe, - health_b, - body_b, - char_state_b_maybe, - ) in ( + for (b, uid_b, pos_b, last_pos_b_maybe, scale_b_maybe, health_b, body_b) in ( &entities, &uids, &positions, @@ -132,7 +121,6 @@ impl<'a> System<'a> for Sys { scales.maybe(), &healths, &bodies, - char_states.maybe(), ) .join() { @@ -146,9 +134,6 @@ impl<'a> System<'a> for Sys { let rad_b = body_b.radius() * scale_b; let height_b = body_b.height() * scale_b; - // Check if entity is immune to damage - let is_invincible = char_state_b_maybe.map_or(false, |c_s| c_s.is_invincible()); - // Check if it is a hit let hit = entity != b && !health_b.is_dead @@ -175,9 +160,7 @@ impl<'a> System<'a> for Sys { for (target, damage) in beam_segment.damages.iter() { if let Some(target) = target { - if *target != target_group - || (!matches!(target, GroupTarget::InGroup) && is_invincible) - { + if *target != target_group { continue; } } diff --git a/common/src/sys/melee.rs b/common/src/sys/melee.rs index 0377765c63..f284cb390d 100644 --- a/common/src/sys/melee.rs +++ b/common/src/sys/melee.rs @@ -94,8 +94,8 @@ impl<'a> System<'a> for Sys { let scale_b = scale_b_maybe.map_or(1.0, |s| s.0); let rad_b = body_b.radius() * scale_b; - // Check if entity is invincible - let is_invincible = char_state_b_maybe.map_or(false, |c_s| c_s.is_invincible()); + // Check if entity is dodging + let is_dodge = char_state_b_maybe.map_or(false, |c_s| c_s.is_dodge()); // Check if it is a hit if entity != b @@ -119,7 +119,7 @@ impl<'a> System<'a> for Sys { for (target, damage) in attack.damages.iter() { if let Some(target) = target { if *target != target_group - || (!matches!(target, GroupTarget::InGroup) && is_invincible) + || (!matches!(target, GroupTarget::InGroup) && is_dodge) { continue; } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index b9e75feb0d..f8d8bd5f3c 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - BeamSegment, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, PreviousVelDtCache, - Projectile, Scale, Shockwave, Sticky, Vel, + BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, + PreviousVelDtCache, Projectile, Scale, Shockwave, Sticky, Vel, }, event::{EventBus, ServerEvent}, metrics::{PhysicsMetrics, SysMetrics}, @@ -71,6 +71,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Projectile>, ReadStorage<'a, BeamSegment>, ReadStorage<'a, Shockwave>, + ReadStorage<'a, CharacterState>, ); #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 @@ -99,6 +100,7 @@ impl<'a> System<'a> for Sys { projectiles, beams, shockwaves, + char_states, ): Self::SystemData, ) { let start_time = std::time::Instant::now(); @@ -183,17 +185,38 @@ impl<'a> System<'a> for Sys { // TODO: if we need to avoid collisions for other things consider moving whether it // should interact into the collider component or into a separate component projectiles.maybe(), + char_states.maybe(), ) .par_join() - .filter(|(_, _, _, _, _, _, _, _, sticky, physics, _)| { + .filter(|(_, _, _, _, _, _, _, _, sticky, physics, _, _)| { sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground) }) - .map(|(e, p, v, vd, s, m, c, _, _, ph, pr)| (e, p, v, vd, s, m, c, ph, pr)) - .fold(PhysicsMetrics::default, - |mut metrics,(entity, pos, vel, vel_dt, scale, mass, collider, physics, projectile)| { + .map(|(e, p, v, vd, s, m, c, _, _, ph, pr, c_s)| (e, p, v, vd, s, m, c, ph, pr, c_s)) + .fold( + PhysicsMetrics::default, + |mut metrics, + ( + entity, + pos, + vel, + vel_dt, + scale, + mass, + collider, + physics, + projectile, + char_state_maybe, + )| { let scale = scale.map(|s| s.0).unwrap_or(1.0); let radius = collider.map(|c| c.get_radius()).unwrap_or(0.5); - let z_limits = collider.map(|c| c.get_z_limits()).unwrap_or((-0.5, 0.5)); + let modifier = if char_state_maybe.map_or(false, |c_s| c_s.is_dodge()) { + 0.5 + } else { + 1.0 + }; + let z_limits = collider + .map(|c| c.get_z_limits(modifier)) + .unwrap_or((-0.5 * modifier, 0.5 * modifier)); let mass = mass.map(|m| m.0).unwrap_or(scale); // Resets touch_entities in physics @@ -215,6 +238,7 @@ impl<'a> System<'a> for Sys { _, _, _, + char_state_other_maybe, ) in ( &entities, &uids, @@ -227,6 +251,7 @@ impl<'a> System<'a> for Sys { !&mountings, !&beams, !&shockwaves, + char_states.maybe(), ) .join() { @@ -240,13 +265,22 @@ impl<'a> System<'a> for Sys { let collision_dist = scale * radius + scale_other * radius_other; // Sanity check: skip colliding entities that are too far from each other - if (pos.0 - pos_other.0).xy().magnitude() > (vel_dt.0 - vel_dt_other.0).xy().magnitude() + collision_dist { + if (pos.0 - pos_other.0).xy().magnitude() + > (vel_dt.0 - vel_dt_other.0).xy().magnitude() + collision_dist + { continue; } + let modifier_other = + if char_state_other_maybe.map_or(false, |c_s| c_s.is_dodge()) { + 0.5 + } else { + 1.0 + }; + let z_limits_other = collider_other - .map(|c| c.get_z_limits()) - .unwrap_or((-0.5, 0.5)); + .map(|c| c.get_z_limits(modifier_other)) + .unwrap_or((-0.5 * modifier_other, 0.5 * modifier_other)); let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_other); //This check after the pos check, as we currently don't have that many // massless entites [citation needed] @@ -257,7 +291,8 @@ impl<'a> System<'a> for Sys { metrics.entity_entity_collision_checks += 1; const MIN_COLLISION_DIST: f32 = 0.3; - let increments = ((vel_dt.0 - vel_dt_other.0).magnitude() / MIN_COLLISION_DIST) + let increments = ((vel_dt.0 - vel_dt_other.0).magnitude() + / MIN_COLLISION_DIST) .max(1.0) .ceil() as usize; let step_delta = 1.0 / increments as f32; @@ -300,11 +335,11 @@ impl<'a> System<'a> for Sys { metrics }, ) - .reduce(PhysicsMetrics::default, |old, new| { - PhysicsMetrics { - entity_entity_collision_checks: old.entity_entity_collision_checks + new.entity_entity_collision_checks, - entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, - } + .reduce(PhysicsMetrics::default, |old, new| PhysicsMetrics { + entity_entity_collision_checks: old.entity_entity_collision_checks + + new.entity_entity_collision_checks, + entity_entity_collisions: old.entity_entity_collisions + + new.entity_entity_collisions, }); physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 023a7ce998..0e4a8b2a07 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,8 +1,8 @@ use crate::{ comp::{ buff::{BuffChange, BuffSource}, - projectile, CharacterState, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, - PhysicsState, Pos, Projectile, Vel, + projectile, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, + Pos, Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, @@ -35,7 +35,6 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Projectile>, ReadStorage<'a, Loadout>, ReadStorage<'a, Group>, - ReadStorage<'a, CharacterState>, ); fn run( @@ -54,7 +53,6 @@ impl<'a> System<'a> for Sys { mut projectiles, loadouts, groups, - char_states, ): Self::SystemData, ) { let start_time = std::time::Instant::now(); @@ -104,18 +102,6 @@ impl<'a> System<'a> for Sys { continue; } - // Checks if entity is immune to damage - // TODO: When projectiles are reduced down to a collection of (target, effect)s, - // move this check there so that future projectiles intended for allies cannot - // be dodged by those allies - let entity_invincible = uid_allocator - .retrieve_entity_internal(other.into()) - .and_then(|e| char_states.get(e)) - .map_or(false, |c_s| c_s.is_invincible()); - if entity_invincible { - continue; - } - for effect in projectile.hit_entity.drain(..) { match effect { projectile::Effect::Damage(target, damage) => { diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs index 5d02caa340..de13dfc17a 100644 --- a/common/src/sys/shockwave.rs +++ b/common/src/sys/shockwave.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Body, CharacterState, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, - Scale, Shockwave, ShockwaveHitEntities, + group, Body, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, + ShockwaveHitEntities, }, event::{EventBus, LocalEvent, ServerEvent}, state::{DeltaTime, Time}, @@ -36,7 +36,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, PhysicsState>, WriteStorage<'a, Shockwave>, WriteStorage<'a, ShockwaveHitEntities>, - ReadStorage<'a, CharacterState>, ); fn run( @@ -60,7 +59,6 @@ impl<'a> System<'a> for Sys { physics_states, mut shockwaves, mut shockwave_hit_lists, - char_states, ): Self::SystemData, ) { let mut server_emitter = server_bus.emitter(); @@ -133,7 +131,6 @@ impl<'a> System<'a> for Sys { health_b, body_b, physics_state_b, - char_state_b_maybe, ) in ( &entities, &uids, @@ -144,7 +141,6 @@ impl<'a> System<'a> for Sys { &healths, &bodies, &physics_states, - char_states.maybe(), ) .join() { @@ -180,9 +176,6 @@ impl<'a> System<'a> for Sys { GroupTarget::OutOfGroup }; - // Check if entity is immune to damage - let is_invincible = char_state_b_maybe.map_or(false, |c_s| c_s.is_invincible()); - // Check if it is a hit let hit = entity != b && !health_b.is_dead @@ -200,9 +193,7 @@ impl<'a> System<'a> for Sys { if hit { for (target, damage) in shockwave.damages.iter() { if let Some(target) = target { - if *target != target_group - || (!matches!(target, GroupTarget::InGroup) && is_invincible) - { + if *target != target_group { continue; } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 05c42172fd..91717fd532 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -647,14 +647,7 @@ pub fn handle_explosion( .get(entity_b) .map_or(false, |h| !h.is_dead); - let is_invincible = ecs - .read_storage::() - .get(entity_b) - .map_or(false, |c_s| c_s.is_invincible()); - - if is_alive - && (matches!(target, Some(GroupTarget::InGroup)) || !is_invincible) - { + if is_alive { effect.modify_strength(strength); server.state().apply_effect(entity_b, effect, owner); // Apply energy change diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs index c054cd9e2a..0001b867ff 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs @@ -158,9 +158,6 @@ fn maps_roll() { movement_duration: Duration::default(), recover_duration: Duration::default(), roll_strength: 0.0, - buildup_iframes: false, - movement_iframes: false, - recover_iframes: false, }, timer: Duration::default(), stage_section: states::utils::StageSection::Buildup,