Merge branch 'sam/make-roll-great-again' into 'master'

Roll overhaul

See merge request veloren/veloren!1484
This commit is contained in:
Samuel Keiffer 2020-11-06 03:25:18 +00:00
commit 44f676d90c
10 changed files with 127 additions and 69 deletions

View File

@ -28,6 +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 dodges melee attacks, and reduces the height of your hitbox
### Changed

View File

@ -113,7 +113,13 @@ pub enum CharacterAbility {
is_interruptible: bool,
},
BasicBlock,
Roll,
Roll {
energy_cost: u32,
buildup_duration: Duration,
movement_duration: Duration,
recover_duration: Duration,
roll_strength: f32,
},
ComboMelee {
stage_data: Vec<combo_melee::Stage>,
initial_energy_gain: u32,
@ -214,13 +220,12 @@ impl CharacterAbility {
/// applicable.
pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
match self {
CharacterAbility::Roll => {
CharacterAbility::Roll { energy_cost, .. } => {
data.physics.on_ground
&& data.body.is_humanoid()
&& data.vel.0.xy().magnitude_squared() > 0.5
&& update
.energy
.try_change_by(-220, EnergySource::Ability)
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok()
},
CharacterAbility::DashMelee { energy_cost, .. } => update
@ -270,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)]
@ -293,8 +308,8 @@ impl From<Item> for ItemConfig {
ability1: ability_drain.next(),
ability2: ability_drain.next(),
ability3: ability_drain.next(),
block_ability: Some(CharacterAbility::BasicBlock),
dodge_ability: Some(CharacterAbility::Roll),
block_ability: None,
dodge_ability: Some(CharacterAbility::default_roll()),
};
}
@ -461,11 +476,18 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
exhausted: false,
}),
CharacterAbility::BasicBlock => CharacterState::BasicBlock,
CharacterAbility::Roll => CharacterState::Roll(roll::Data {
CharacterAbility::Roll {
energy_cost: _,
buildup_duration,
movement_duration,
recover_duration,
roll_strength,
} => CharacterState::Roll(roll::Data {
static_data: roll::StaticData {
buildup_duration: Duration::from_millis(100),
movement_duration: Duration::from_millis(300),
recover_duration: Duration::from_millis(100),
buildup_duration: *buildup_duration,
movement_duration: *movement_duration,
recover_duration: *recover_duration,
roll_strength: *roll_strength,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,

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 {
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),
}
}

View File

@ -144,18 +144,8 @@ impl LoadoutBuilder {
_ => {},
};
let active_item = if let Some(ItemKind::Tool(tool)) = main_tool.as_ref().map(|i| i.kind()) {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
main_tool.map(|item| ItemConfig {
item,
ability1: ability_drain.next(),
ability2: ability_drain.next(),
ability3: ability_drain.next(),
block_ability: None,
dodge_ability: Some(CharacterAbility::Roll),
})
let active_item = if let Some(ItemKind::Tool(_)) = main_tool.as_ref().map(|i| i.kind()) {
main_tool.map(ItemConfig::from)
} else {
Some(ItemConfig {
// We need the empty item so npcs can attack

View File

@ -2,13 +2,9 @@ use crate::{
comp::{CharacterState, StateUpdate},
states::utils::*,
sys::character_behavior::{CharacterBehavior, JoinData},
util::Dir,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use vek::Vec3;
const ROLL_SPEED: f32 = 25.0;
/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -19,6 +15,8 @@ pub struct StaticData {
pub movement_duration: Duration,
/// How long it takes to recover from roll
pub recover_duration: Duration,
/// Affects the speed and distance of the roll
pub roll_strength: f32,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -40,19 +38,12 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Update velocity
update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z)
+ (update.vel.0 * Vec3::new(1.0, 1.0, 0.0)
+ 0.25 * data.inputs.move_dir.try_normalized().unwrap_or_default())
.try_normalized()
.unwrap_or_default()
* ROLL_SPEED;
// Smooth orientation
update.ori.0 = Dir::slerp_to_vec3(update.ori.0, update.vel.0.xy().into(), 9.0 * data.dt.0);
handle_orientation(data, &mut update, 1.0);
match self.stage_section {
StageSection::Buildup => {
handle_move(data, &mut update, 1.0);
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::Roll(Data {
@ -72,6 +63,16 @@ impl CharacterBehavior for Data {
}
},
StageSection::Movement => {
// Update velocity
handle_forced_movement(
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.roll_strength,
},
0.0,
);
if self.timer < self.static_data.movement_duration {
// Movement
update.character = CharacterState::Roll(Data {
@ -115,6 +116,8 @@ impl CharacterBehavior for Data {
// If it somehow ends up in an incorrect stage section
if self.was_wielded {
update.character = CharacterState::Wielding;
} else if self.was_sneak {
update.character = CharacterState::Sneak;
} else {
update.character = CharacterState::Idle;
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{buff, group, Attacking, Body, Health, Loadout, Ori, Pos, Scale},
comp::{buff, group, Attacking, Body, CharacterState, Health, Loadout, Ori, Pos, Scale},
event::{EventBus, LocalEvent, ServerEvent},
metrics::SysMetrics,
span,
@ -31,6 +31,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Loadout>,
ReadStorage<'a, group::Group>,
WriteStorage<'a, Attacking>,
ReadStorage<'a, CharacterState>,
);
fn run(
@ -49,6 +50,7 @@ impl<'a> System<'a> for Sys {
loadouts,
groups,
mut attacking_storage,
char_states,
): Self::SystemData,
) {
let start_time = std::time::Instant::now();
@ -72,8 +74,15 @@ impl<'a> System<'a> for Sys {
attack.applied = true;
// Go through all other entities
for (b, pos_b, scale_b_maybe, health_b, body_b) in
(&entities, &positions, scales.maybe(), &healths, &bodies).join()
for (b, pos_b, scale_b_maybe, health_b, body_b, char_state_b_maybe) in (
&entities,
&positions,
scales.maybe(),
&healths,
&bodies,
char_states.maybe(),
)
.join()
{
// 2D versions
let pos2 = Vec2::from(pos.0);
@ -85,6 +94,9 @@ 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 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
&& !health_b.is_dead
@ -106,7 +118,9 @@ impl<'a> System<'a> for Sys {
for (target, damage) in attack.damages.iter() {
if let Some(target) = target {
if *target != target_group {
if *target != target_group
|| (!matches!(target, GroupTarget::InGroup) && is_dodge)
{
continue;
}
}

View File

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

View File

@ -176,17 +176,8 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
.or_insert(comp::Loadout::default());
let item = comp::Item::new_from_asset_expect("common.items.debug.possess");
if let item::ItemKind::Tool(tool) = item.kind() {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
let debug_item = comp::ItemConfig {
item,
ability1: ability_drain.next(),
ability2: ability_drain.next(),
ability3: ability_drain.next(),
block_ability: None,
dodge_ability: None,
};
if let item::ItemKind::Tool(_) = item.kind() {
let debug_item = comp::ItemConfig::from(item);
std::mem::swap(&mut loadout.active_item, &mut loadout.second_item);
loadout.active_item = Some(debug_item);
}

View File

@ -31,7 +31,8 @@ impl Animation for RollAnimation {
*rate = 1.0;
let mut next = (*skeleton).clone();
let spin = anim_time as f32;
let spin = anim_time as f32 * 1.1;
let ori: Vec2<f32> = Vec2::from(orientation);
let last_ori = Vec2::from(last_ori);
let tilt = if ::vek::Vec2::new(ori, last_ori)

View File

@ -157,6 +157,7 @@ fn maps_roll() {
buildup_duration: Duration::default(),
movement_duration: Duration::default(),
recover_duration: Duration::default(),
roll_strength: 0.0,
},
timer: Duration::default(),
stage_section: states::utils::StageSection::Buildup,