mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/make-roll-great-again' into 'master'
Roll overhaul See merge request veloren/veloren!1484
This commit is contained in:
commit
44f676d90c
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user