Addressed comments. Roll now only provides i-frames to melee attacks. It also reduces height of hitbox.

This commit is contained in:
Sam 2020-11-05 19:22:26 -06:00
parent 66850a5878
commit 37ecb165ef
12 changed files with 79 additions and 126 deletions

View File

@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Buff system - Buff system
- Sneaking lets you be closer to enemies without being detected - Sneaking lets you be closer to enemies without being detected
- Flight - Flight
- Roll has i-frames - Roll dodges melee attacks, and reduces the height of your hitbox
### Changed ### Changed

View File

@ -119,9 +119,6 @@ pub enum CharacterAbility {
movement_duration: Duration, movement_duration: Duration,
recover_duration: Duration, recover_duration: Duration,
roll_strength: f32, roll_strength: f32,
buildup_iframes: bool,
movement_iframes: bool,
recover_iframes: bool,
}, },
ComboMelee { ComboMelee {
stage_data: Vec<combo_melee::Stage>, stage_data: Vec<combo_melee::Stage>,
@ -278,6 +275,16 @@ impl CharacterAbility {
_ => true, _ => 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)] #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
@ -302,7 +309,7 @@ impl From<Item> for ItemConfig {
ability2: ability_drain.next(), ability2: ability_drain.next(),
ability3: ability_drain.next(), ability3: ability_drain.next(),
block_ability: None, block_ability: None,
dodge_ability: Some(get_roll()), dodge_ability: Some(CharacterAbility::default_roll()),
}; };
} }
@ -310,19 +317,6 @@ impl From<Item> 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)] #[derive(Arraygen, Clone, PartialEq, Default, Debug, Serialize, Deserialize)]
#[gen_array(pub fn get_armor: &Option<Item>)] #[gen_array(pub fn get_armor: &Option<Item>)]
pub struct Loadout { pub struct Loadout {
@ -488,18 +482,12 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
movement_duration, movement_duration,
recover_duration, recover_duration,
roll_strength, roll_strength,
buildup_iframes,
movement_iframes,
recover_iframes,
} => CharacterState::Roll(roll::Data { } => CharacterState::Roll(roll::Data {
static_data: roll::StaticData { static_data: roll::StaticData {
buildup_duration: *buildup_duration, buildup_duration: *buildup_duration,
movement_duration: *movement_duration, movement_duration: *movement_duration,
recover_duration: *recover_duration, recover_duration: *recover_duration,
roll_strength: *roll_strength, roll_strength: *roll_strength,
buildup_iframes: *buildup_iframes,
movement_iframes: *movement_iframes,
recover_iframes: *recover_iframes,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,

View File

@ -145,20 +145,6 @@ impl CharacterState {
// Check if state is the same without looking at the inner data // Check if state is the same without looking at the inner data
std::mem::discriminant(self) == std::mem::discriminant(other) 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 { impl Default for CharacterState {

View File

@ -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 { 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), Collider::Point => (0.0, 0.0),
} }
} }

View File

@ -15,14 +15,8 @@ pub struct StaticData {
pub movement_duration: Duration, pub movement_duration: Duration,
/// How long it takes to recover from roll /// How long it takes to recover from roll
pub recover_duration: Duration, pub recover_duration: Duration,
/// How strong the roll is /// Affects the speed and distance of the roll
pub roll_strength: f32, 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)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, Health, group, Beam, BeamSegment, Body, Energy, EnergyChange, EnergySource, Health, HealthChange,
HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, HealthSource, Last, Loadout, Ori, Pos, Scale,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
state::{DeltaTime, Time}, state::{DeltaTime, Time},
@ -34,7 +34,6 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Energy>, ReadStorage<'a, Energy>,
WriteStorage<'a, BeamSegment>, WriteStorage<'a, BeamSegment>,
WriteStorage<'a, Beam>, WriteStorage<'a, Beam>,
ReadStorage<'a, CharacterState>,
); );
fn run( fn run(
@ -57,7 +56,6 @@ impl<'a> System<'a> for Sys {
energies, energies,
mut beam_segments, mut beam_segments,
mut beams, mut beams,
char_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
@ -114,16 +112,7 @@ impl<'a> System<'a> for Sys {
}; };
// Go through all other effectable entities // Go through all other effectable entities
for ( for (b, uid_b, pos_b, last_pos_b_maybe, scale_b_maybe, health_b, body_b) in (
b,
uid_b,
pos_b,
last_pos_b_maybe,
scale_b_maybe,
health_b,
body_b,
char_state_b_maybe,
) in (
&entities, &entities,
&uids, &uids,
&positions, &positions,
@ -132,7 +121,6 @@ impl<'a> System<'a> for Sys {
scales.maybe(), scales.maybe(),
&healths, &healths,
&bodies, &bodies,
char_states.maybe(),
) )
.join() .join()
{ {
@ -146,9 +134,6 @@ impl<'a> System<'a> for Sys {
let rad_b = body_b.radius() * scale_b; let rad_b = body_b.radius() * scale_b;
let height_b = body_b.height() * 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 // Check if it is a hit
let hit = entity != b let hit = entity != b
&& !health_b.is_dead && !health_b.is_dead
@ -175,9 +160,7 @@ impl<'a> System<'a> for Sys {
for (target, damage) in beam_segment.damages.iter() { for (target, damage) in beam_segment.damages.iter() {
if let Some(target) = target { if let Some(target) = target {
if *target != target_group if *target != target_group {
|| (!matches!(target, GroupTarget::InGroup) && is_invincible)
{
continue; continue;
} }
} }

View File

@ -94,8 +94,8 @@ impl<'a> System<'a> for Sys {
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0); let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
let rad_b = body_b.radius() * scale_b; let rad_b = body_b.radius() * scale_b;
// Check if entity is invincible // Check if entity is dodging
let is_invincible = char_state_b_maybe.map_or(false, |c_s| c_s.is_invincible()); let is_dodge = char_state_b_maybe.map_or(false, |c_s| c_s.is_dodge());
// Check if it is a hit // Check if it is a hit
if entity != b if entity != b
@ -119,7 +119,7 @@ impl<'a> System<'a> for Sys {
for (target, damage) in attack.damages.iter() { for (target, damage) in attack.damages.iter() {
if let Some(target) = target { if let Some(target) = target {
if *target != target_group if *target != target_group
|| (!matches!(target, GroupTarget::InGroup) && is_invincible) || (!matches!(target, GroupTarget::InGroup) && is_dodge)
{ {
continue; continue;
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
BeamSegment, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, PreviousVelDtCache, BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos,
Projectile, Scale, Shockwave, Sticky, Vel, PreviousVelDtCache, Projectile, Scale, Shockwave, Sticky, Vel,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
metrics::{PhysicsMetrics, SysMetrics}, metrics::{PhysicsMetrics, SysMetrics},
@ -71,6 +71,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Projectile>, ReadStorage<'a, Projectile>,
ReadStorage<'a, BeamSegment>, ReadStorage<'a, BeamSegment>,
ReadStorage<'a, Shockwave>, ReadStorage<'a, Shockwave>,
ReadStorage<'a, CharacterState>,
); );
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
@ -99,6 +100,7 @@ impl<'a> System<'a> for Sys {
projectiles, projectiles,
beams, beams,
shockwaves, shockwaves,
char_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
let start_time = std::time::Instant::now(); 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 // 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 // should interact into the collider component or into a separate component
projectiles.maybe(), projectiles.maybe(),
char_states.maybe(),
) )
.par_join() .par_join()
.filter(|(_, _, _, _, _, _, _, _, sticky, physics, _)| { .filter(|(_, _, _, _, _, _, _, _, sticky, physics, _, _)| {
sticky.is_none() || (physics.on_wall.is_none() && !physics.on_ground) 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)) .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, .fold(
|mut metrics,(entity, pos, vel, vel_dt, scale, mass, collider, physics, projectile)| { 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 scale = scale.map(|s| s.0).unwrap_or(1.0);
let radius = collider.map(|c| c.get_radius()).unwrap_or(0.5); 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); let mass = mass.map(|m| m.0).unwrap_or(scale);
// Resets touch_entities in physics // Resets touch_entities in physics
@ -215,6 +238,7 @@ impl<'a> System<'a> for Sys {
_, _,
_, _,
_, _,
char_state_other_maybe,
) in ( ) in (
&entities, &entities,
&uids, &uids,
@ -227,6 +251,7 @@ impl<'a> System<'a> for Sys {
!&mountings, !&mountings,
!&beams, !&beams,
!&shockwaves, !&shockwaves,
char_states.maybe(),
) )
.join() .join()
{ {
@ -240,13 +265,22 @@ impl<'a> System<'a> for Sys {
let collision_dist = scale * radius + scale_other * radius_other; let collision_dist = scale * radius + scale_other * radius_other;
// Sanity check: skip colliding entities that are too far from each 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; 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 let z_limits_other = collider_other
.map(|c| c.get_z_limits()) .map(|c| c.get_z_limits(modifier_other))
.unwrap_or((-0.5, 0.5)); .unwrap_or((-0.5 * modifier_other, 0.5 * modifier_other));
let mass_other = mass_other.map(|m| m.0).unwrap_or(scale_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 //This check after the pos check, as we currently don't have that many
// massless entites [citation needed] // massless entites [citation needed]
@ -257,7 +291,8 @@ impl<'a> System<'a> for Sys {
metrics.entity_entity_collision_checks += 1; metrics.entity_entity_collision_checks += 1;
const MIN_COLLISION_DIST: f32 = 0.3; 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) .max(1.0)
.ceil() as usize; .ceil() as usize;
let step_delta = 1.0 / increments as f32; let step_delta = 1.0 / increments as f32;
@ -300,11 +335,11 @@ impl<'a> System<'a> for Sys {
metrics metrics
}, },
) )
.reduce(PhysicsMetrics::default, |old, new| { .reduce(PhysicsMetrics::default, |old, new| PhysicsMetrics {
PhysicsMetrics { entity_entity_collision_checks: old.entity_entity_collision_checks
entity_entity_collision_checks: old.entity_entity_collision_checks + new.entity_entity_collision_checks, + new.entity_entity_collision_checks,
entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, 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_collision_checks = metrics.entity_entity_collision_checks;
physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions;

View File

@ -1,8 +1,8 @@
use crate::{ use crate::{
comp::{ comp::{
buff::{BuffChange, BuffSource}, buff::{BuffChange, BuffSource},
projectile, CharacterState, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, projectile, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState,
PhysicsState, Pos, Projectile, Vel, Pos, Projectile, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
metrics::SysMetrics, metrics::SysMetrics,
@ -35,7 +35,6 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Projectile>, WriteStorage<'a, Projectile>,
ReadStorage<'a, Loadout>, ReadStorage<'a, Loadout>,
ReadStorage<'a, Group>, ReadStorage<'a, Group>,
ReadStorage<'a, CharacterState>,
); );
fn run( fn run(
@ -54,7 +53,6 @@ impl<'a> System<'a> for Sys {
mut projectiles, mut projectiles,
loadouts, loadouts,
groups, groups,
char_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
@ -104,18 +102,6 @@ impl<'a> System<'a> for Sys {
continue; 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(..) { for effect in projectile.hit_entity.drain(..) {
match effect { match effect {
projectile::Effect::Damage(target, damage) => { projectile::Effect::Damage(target, damage) => {

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
comp::{ comp::{
group, Body, CharacterState, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, group, Body, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, Shockwave,
Scale, Shockwave, ShockwaveHitEntities, ShockwaveHitEntities,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
state::{DeltaTime, Time}, state::{DeltaTime, Time},
@ -36,7 +36,6 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, PhysicsState>, ReadStorage<'a, PhysicsState>,
WriteStorage<'a, Shockwave>, WriteStorage<'a, Shockwave>,
WriteStorage<'a, ShockwaveHitEntities>, WriteStorage<'a, ShockwaveHitEntities>,
ReadStorage<'a, CharacterState>,
); );
fn run( fn run(
@ -60,7 +59,6 @@ impl<'a> System<'a> for Sys {
physics_states, physics_states,
mut shockwaves, mut shockwaves,
mut shockwave_hit_lists, mut shockwave_hit_lists,
char_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_bus.emitter(); let mut server_emitter = server_bus.emitter();
@ -133,7 +131,6 @@ impl<'a> System<'a> for Sys {
health_b, health_b,
body_b, body_b,
physics_state_b, physics_state_b,
char_state_b_maybe,
) in ( ) in (
&entities, &entities,
&uids, &uids,
@ -144,7 +141,6 @@ impl<'a> System<'a> for Sys {
&healths, &healths,
&bodies, &bodies,
&physics_states, &physics_states,
char_states.maybe(),
) )
.join() .join()
{ {
@ -180,9 +176,6 @@ impl<'a> System<'a> for Sys {
GroupTarget::OutOfGroup 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 // Check if it is a hit
let hit = entity != b let hit = entity != b
&& !health_b.is_dead && !health_b.is_dead
@ -200,9 +193,7 @@ impl<'a> System<'a> for Sys {
if hit { if hit {
for (target, damage) in shockwave.damages.iter() { for (target, damage) in shockwave.damages.iter() {
if let Some(target) = target { if let Some(target) = target {
if *target != target_group if *target != target_group {
|| (!matches!(target, GroupTarget::InGroup) && is_invincible)
{
continue; continue;
} }
} }

View File

@ -647,14 +647,7 @@ pub fn handle_explosion(
.get(entity_b) .get(entity_b)
.map_or(false, |h| !h.is_dead); .map_or(false, |h| !h.is_dead);
let is_invincible = ecs if is_alive {
.read_storage::<comp::CharacterState>()
.get(entity_b)
.map_or(false, |c_s| c_s.is_invincible());
if is_alive
&& (matches!(target, Some(GroupTarget::InGroup)) || !is_invincible)
{
effect.modify_strength(strength); effect.modify_strength(strength);
server.state().apply_effect(entity_b, effect, owner); server.state().apply_effect(entity_b, effect, owner);
// Apply energy change // Apply energy change

View File

@ -158,9 +158,6 @@ fn maps_roll() {
movement_duration: Duration::default(), movement_duration: Duration::default(),
recover_duration: Duration::default(), recover_duration: Duration::default(),
roll_strength: 0.0, roll_strength: 0.0,
buildup_iframes: false,
movement_iframes: false,
recover_iframes: false,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: states::utils::StageSection::Buildup, stage_section: states::utils::StageSection::Buildup,