Addressed comments

This commit is contained in:
Sam 2020-09-19 11:55:31 -05:00
parent e39770d1d9
commit 45fef87f32
13 changed files with 108 additions and 141 deletions

View File

@ -1427,13 +1427,13 @@ impl Client {
ServerMsg::Outcomes(outcomes) => {
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
},
ServerMsg::Knockback(force) => {
ServerMsg::Knockback(impulse) => {
self.state
.ecs()
.read_resource::<EventBus<LocalEvent>>()
.emit_now(LocalEvent::ApplyForce {
.emit_now(LocalEvent::ApplyImpulse {
entity: self.entity,
force,
impulse,
});
},
}

View File

@ -121,6 +121,7 @@ pub enum CharacterAbility {
shockwave_angle: f32,
shockwave_speed: f32,
shockwave_duration: Duration,
requires_ground: bool,
},
}
@ -417,6 +418,7 @@ impl From<&CharacterAbility> for CharacterState {
shockwave_angle,
shockwave_speed,
shockwave_duration,
requires_ground,
} => CharacterState::GroundShockwave(ground_shockwave::Data {
exhausted: false,
buildup_duration: *buildup_duration,
@ -426,6 +428,7 @@ impl From<&CharacterAbility> for CharacterState {
shockwave_angle: *shockwave_angle,
shockwave_speed: *shockwave_speed,
shockwave_duration: *shockwave_duration,
requires_ground: *requires_ground,
}),
}
}

View File

@ -369,6 +369,7 @@ impl Tool {
shockwave_angle: 90.0,
shockwave_speed: 20.0,
shockwave_duration: Duration::from_millis(2000),
requires_ground: true,
},
]
} else {

View File

@ -11,8 +11,11 @@ use vek::*;
pub enum LocalEvent {
/// Applies upward force to entity's `Vel`
Jump(EcsEntity),
/// Applies the `force` to `entity`'s `Vel`
ApplyForce { entity: EcsEntity, force: Vec3<f32> },
/// Applies the `impulse` to `entity`'s `Vel`
ApplyImpulse {
entity: EcsEntity,
impulse: Vec3<f32>,
},
/// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction
WallLeap {
entity: EcsEntity,
@ -58,7 +61,7 @@ pub enum ServerEvent {
},
Knockback {
entity: EcsEntity,
force: Vec3<f32>,
impulse: Vec3<f32>,
},
LandOnGround {
entity: EcsEntity,

View File

@ -390,11 +390,9 @@ impl State {
vel.0.z = HUMANOID_JUMP_ACCEL;
}
},
LocalEvent::ApplyForce { entity, force } => {
// TODO: this sets the velocity directly to the value of `force`, consider
// renaming the event or changing the behavior
LocalEvent::ApplyImpulse { entity, impulse } => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0 = force;
vel.0 = impulse;
}
},
LocalEvent::WallLeap { entity, wall_dir } => {

View File

@ -25,6 +25,8 @@ pub struct Data {
pub shockwave_speed: f32,
/// How long the shockwave travels for
pub shockwave_duration: Duration,
/// Whether the shockwave requires the target to be on the ground
pub requires_ground: bool,
}
impl CharacterBehavior for Data {
@ -47,6 +49,7 @@ impl CharacterBehavior for Data {
shockwave_angle: self.shockwave_angle,
shockwave_speed: self.shockwave_speed,
shockwave_duration: self.shockwave_duration,
requires_ground: self.requires_ground,
});
} else if !self.exhausted {
// Attack
@ -56,7 +59,7 @@ impl CharacterBehavior for Data {
duration: self.shockwave_duration,
damage: self.damage,
knockback: self.knockback,
requires_ground: true,
requires_ground: self.requires_ground,
owner: Some(*data.uid),
};
update.server_events.push_front(ServerEvent::Shockwave {
@ -74,6 +77,7 @@ impl CharacterBehavior for Data {
shockwave_angle: self.shockwave_angle,
shockwave_speed: self.shockwave_speed,
shockwave_duration: self.shockwave_duration,
requires_ground: self.requires_ground,
});
} else if self.recover_duration != Duration::default() {
// Recovery
@ -89,6 +93,7 @@ impl CharacterBehavior for Data {
shockwave_angle: self.shockwave_angle,
shockwave_speed: self.shockwave_speed,
shockwave_duration: self.shockwave_duration,
requires_ground: self.requires_ground,
});
} else {
// Done

View File

@ -155,7 +155,7 @@ impl<'a> System<'a> for Sys {
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
server_emitter.emit(ServerEvent::Knockback {
entity: b,
force: attack.knockback
impulse: attack.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
});
}

View File

@ -117,9 +117,9 @@ impl<'a> System<'a> for Sys {
if let Some(entity) =
uid_allocator.retrieve_entity_internal(other.into())
{
local_emitter.emit(LocalEvent::ApplyForce {
local_emitter.emit(LocalEvent::ApplyImpulse {
entity,
force: knockback
impulse: knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5),
});
}

View File

@ -83,22 +83,17 @@ impl<'a> System<'a> for Sys {
// If shockwave is out of time emit destroy event but still continue since it
// may have traveled and produced effects a bit before reaching it's
// end point
if end_time < time {
if time > end_time {
server_emitter.emit(ServerEvent::Destroy {
entity,
cause: HealthSource::World,
});
}
// Determine area that was covered by the shockwave in the last tick
let frame_time = dt.min((end_time - time) as f32);
if frame_time <= 0.0 {
continue;
}
// Note: min() probably uneeded
// Determine area that was covered by the shockwave in the last tick
let time_since_creation = (time - creation_time) as f32;
let frame_start_dist = (shockwave.speed * (time_since_creation - frame_time)).max(0.0);
let frame_start_dist = (shockwave.speed * (time_since_creation - dt)).max(0.0);
let frame_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist);
let pos2 = Vec2::from(pos.0);
@ -159,6 +154,11 @@ impl<'a> System<'a> for Sys {
let pos_b_ground = Vec3::new(pos_b.0.x, pos_b.0.y, pos.0.z);
let max_angle = 15.0_f32.to_radians();
// See if entities are in the same group
let same_group = group
.map(|group_a| Some(group_a) == groups.get(b))
.unwrap_or(Some(*uid_b) == shockwave.owner);
// Check if it is a hit
let hit = entity != b
&& !stats_b.is_dead
@ -166,78 +166,54 @@ impl<'a> System<'a> for Sys {
&& {
// TODO: write code to collide rect with the arc strip so that we can do
// more complete collision detection for rapidly moving entities
arc_strip.collides_with_circle(Circle {
pos: pos_b2,
radius: rad_b,
}) || last_pos_b2_maybe.map_or(false, |pos| {
arc_strip.collides_with_circle(Circle { pos, radius: rad_b })
arc_strip.collides_with_circle(Disk::new(pos_b2, rad_b)) || last_pos_b2_maybe.map_or(false, |pos| {
arc_strip.collides_with_circle(Disk::new(pos, rad_b))
})
}
&& (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle
&& (!shockwave.requires_ground || physics_state_b.on_ground);
&& (!shockwave.requires_ground || physics_state_b.on_ground)
&& !same_group;
if hit {
// See if entities are in the same group
let same_group = group
.map(|group_a| Some(group_a) == groups.get(b))
.unwrap_or(Some(*uid_b) == shockwave.owner);
// Don't damage in the same group
if same_group {
continue;
}
// Weapon gives base damage
let source = DamageSource::Shockwave;
let mut damage = Damage {
healthchange: -(shockwave.damage as f32),
source,
source: DamageSource::Shockwave,
};
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
// TODO: investigate whether this calculation is proper for shockwaves
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
if let Some(loadout) = loadouts.get(b) {
damage.modify_damage(block, loadout);
}
if damage.healthchange < 0.0 {
if damage.healthchange != 0.0 {
let cause = if damage.healthchange < 0.0 {
HealthSource::Attack {
by: shockwave.owner.unwrap_or(*uid),
}
} else {
HealthSource::Healing {
by: Some(shockwave.owner.unwrap_or(*uid)),
}
};
server_emitter.emit(ServerEvent::Damage {
uid: *uid_b,
change: HealthChange {
amount: damage.healthchange as i32,
cause: HealthSource::Attack {
by: shockwave.owner.unwrap_or(*uid),
},
},
});
} else if damage.healthchange > 0.0 {
server_emitter.emit(ServerEvent::Damage {
uid: *uid_b,
change: HealthChange {
amount: damage.healthchange as i32,
cause: HealthSource::Healing {
by: Some(shockwave.owner.unwrap_or(*uid)),
},
cause,
},
});
}
if shockwave.knockback != 0.0 && damage.healthchange != 0.0 {
if shockwave.knockback < 0.0 {
server_emitter.emit(ServerEvent::Knockback {
entity: b,
force: shockwave.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85),
});
let impulse = if shockwave.knockback < 0.0 {
shockwave.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85)
} else {
server_emitter.emit(ServerEvent::Knockback {
entity: b,
force: shockwave.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
});
}
shockwave.knockback
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5)
};
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
}
}
}
@ -269,34 +245,28 @@ struct ArcStrip {
}
impl ArcStrip {
fn collides_with_circle(self, c: Circle) -> bool {
fn collides_with_circle(self, d: Disk<f32, f32>) -> bool {
// Quit if aabb's don't collide
if (self.origin.x - c.pos.x).abs() > self.end + c.radius
|| (self.origin.y - c.pos.y).abs() > self.end + c.radius
if (self.origin.x - d.center.x).abs() > self.end + d.radius
|| (self.origin.y - d.center.y).abs() > self.end + d.radius
{
return false;
}
let dist = self.origin.distance(c.pos);
let dist = self.origin.distance(d.center);
let half_angle = self.angle.to_radians() / 2.0;
if dist > self.end + c.radius || dist + c.radius < self.start {
if dist > self.end + d.radius || dist + d.radius < self.start {
// Completely inside or outside full ring
return false;
}
let inside_edge = Circle {
pos: self.origin,
radius: self.start,
};
let outside_edge = Circle {
pos: self.origin,
radius: self.end,
};
let inside_edge = Disk::new(self.origin, self.start);
let outside_edge = Disk::new(self.origin, self.end);
let inner_corner_in_circle = || {
let midpoint = self.dir.normalized() * self.start;
c.contains_point(midpoint.rotated_z(half_angle) + self.origin)
|| c.contains_point(midpoint.rotated_z(-half_angle) + self.origin)
d.contains_point(midpoint.rotated_z(half_angle) + self.origin)
|| d.contains_point(midpoint.rotated_z(-half_angle) + self.origin)
};
let arc_segment_in_circle = || {
let midpoint = self.dir.normalized();
@ -306,7 +276,7 @@ impl ArcStrip {
start: dir * self.start + self.origin,
end: dir * self.end + self.origin,
};
c.contains_point(side.projected_point(c.pos))
d.contains_point(side.projected_point(d.center))
};
segment_in_circle(half_angle) || segment_in_circle(-half_angle)
};
@ -316,7 +286,7 @@ impl ArcStrip {
// Check intersection with line segments
arc_segment_in_circle() || {
// Check angle of intersection points on outside edge of ring
let (p1, p2) = outside_edge.intersection_points(c, dist);
let (p1, p2) = intersection_points(outside_edge, d, dist);
self.dir.angle_between(p1 - self.origin) < half_angle
|| self.dir.angle_between(p2 - self.origin) < half_angle
}
@ -327,54 +297,43 @@ impl ArcStrip {
inner_corner_in_circle()
|| (
// Check that the circles aren't identical
!inside_edge.is_approx_eq(c) && {
let (p1, p2) = inside_edge.intersection_points(c, dist);
inside_edge != d && {
let (p1, p2) = intersection_points(inside_edge, d, dist);
self.dir.angle_between(p1 - self.origin) < half_angle
|| self.dir.angle_between(p2 - self.origin) < half_angle
}
)
} else if c.radius > dist {
} else if d.radius > dist {
// Circle center inside ring
// but center of ring is inside the circle so we can't calculate the angle
inner_corner_in_circle()
} else {
// Circle center inside ring
// Calculate extra angle to account for circle radius
let extra_angle = (c.radius / dist).asin();
self.dir.angle_between(c.pos - self.origin) < half_angle + extra_angle
let extra_angle = (d.radius / dist).asin();
self.dir.angle_between(d.center - self.origin) < half_angle + extra_angle
}
}
}
#[derive(Clone, Copy)]
struct Circle {
pos: Vec2<f32>,
radius: f32,
}
impl Circle {
// Assumes an intersection is occuring at 2 points
// Uses precalculated distance
// https://www.xarg.org/2016/07/calculate-the-intersection-points-of-two-circles/
fn intersection_points(self, other: Self, dist: f32) -> (Vec2<f32>, Vec2<f32>) {
let e = (other.pos - self.pos) / dist;
let x = (self.radius.powi(2) - other.radius.powi(2) + dist.powi(2)) / (2.0 * dist);
let y = (self.radius.powi(2) - x.powi(2)).sqrt();
let pxe = self.pos + x * e;
let eyx = e.yx();
let p1 = pxe + Vec2::new(-y, y) * eyx;
let p2 = pxe + Vec2::new(y, -y) * eyx;
(p1, p2)
}
fn contains_point(self, point: Vec2<f32>) -> bool {
point.distance_squared(self.pos) < self.radius.powi(2)
}
fn is_approx_eq(self, other: Self) -> bool {
(self.pos - other.pos).is_approx_zero() && self.radius - other.radius < 0.001
}
// Assumes an intersection is occuring at 2 points
// Uses precalculated distance
// https://www.xarg.org/2016/07/calculate-the-intersection-points-of-two-circles/
fn intersection_points(
disk1: Disk<f32, f32>,
disk2: Disk<f32, f32>,
dist: f32,
) -> (Vec2<f32>, Vec2<f32>) {
let e = (disk2.center - disk1.center) / dist;
let x = (disk1.radius.powi(2) - disk2.radius.powi(2) + dist.powi(2)) / (2.0 * dist);
let y = (disk1.radius.powi(2) - x.powi(2)).sqrt();
let pxe = disk1.center + x * e;
let eyx = e.yx();
let p1 = pxe + Vec2::new(-y, y) * eyx;
let p2 = pxe + Vec2::new(y, -y) * eyx;
(p1, p2)
}

View File

@ -2,8 +2,9 @@ use crate::{sys, Server, StateExt};
use common::{
character::CharacterId,
comp::{
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity, Item,
ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity,
Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel,
WaypointArea,
},
outcome::Outcome,
util::Dir,

View File

@ -32,16 +32,15 @@ pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
}
}
pub fn handle_knockback(server: &Server, entity: EcsEntity, force: Vec3<f32>) {
pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>) {
let state = &server.state;
let ecs = state.ecs();
let mut velocities = ecs.write_storage::<comp::Vel>();
let mut velocities = state.ecs().write_storage::<comp::Vel>();
if let Some(vel) = velocities.get_mut(entity) {
vel.0 = force;
vel.0 = impulse;
}
let mut clients = state.ecs().write_storage::<Client>();
if let Some(client) = clients.get_mut(entity) {
client.notify(ServerMsg::Knockback(force));
client.notify(ServerMsg::Knockback(impulse));
}
}

View File

@ -75,7 +75,9 @@ impl Server {
pos,
ori,
} => handle_shockwave(self, properties, pos, ori),
ServerEvent::Knockback { entity, force } => handle_knockback(&self, entity, force),
ServerEvent::Knockback { entity, impulse } => {
handle_knockback(&self, entity, impulse)
},
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),

View File

@ -447,28 +447,24 @@ impl ParticleMgr {
// 1 / 3 the size of terrain voxel
let scale = 1.0 / 3.0;
let freqency_millis = shockwave.properties.speed * scale;
let scaled_speed = shockwave.properties.speed * scale;
for heartbeat in 0..self
.scheduler
.heartbeats(Duration::from_millis(freqency_millis as u64))
.heartbeats(Duration::from_millis(scaled_speed as u64))
{
let sub_tick_interpolation = freqency_millis * 1000.0 * heartbeat as f32;
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
let distance =
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
for d in 0..((distance / scale) as i32) {
let arc_position = (theta - (radians / 2.0)) + (dtheta * d as f32 * scale);
let arc_position = theta - radians / 2.0 + dtheta * d as f32 * scale;
let position = pos.0
+ Vec3::new(
distance * arc_position.cos(),
distance * arc_position.sin(),
0.0,
);
let position =
pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
let position_snapped = (position / scale).round() * scale;
let position_snapped = ((position / scale).floor() + 0.5) * scale;
self.particles.push(Particle::new(
Duration::from_millis(250),