mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add shockwave system to handle shockwaves colliding with other entities
This commit is contained in:
parent
717142d5ea
commit
829d8a20d1
@ -116,6 +116,16 @@ impl Tool {
|
||||
|
||||
match &self.kind {
|
||||
Sword(_) => vec![
|
||||
GroundShockwave {
|
||||
energy_cost: 0,
|
||||
buildup_duration: Duration::from_millis(1000),
|
||||
recover_duration: Duration::from_millis(2000),
|
||||
damage: 300,
|
||||
knockback: -30.0,
|
||||
shockwave_angle: 15.0,
|
||||
shockwave_speed: 10.0,
|
||||
shockwave_duration: Duration::from_millis(3000),
|
||||
},
|
||||
TripleStrike {
|
||||
base_damage: (60.0 * self.base_power()) as u32,
|
||||
needs_timing: false,
|
||||
|
@ -1,25 +1,36 @@
|
||||
use crate::{
|
||||
comp::phys::{Ori, Pos},
|
||||
sync::Uid,
|
||||
};
|
||||
use crate::sync::Uid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Shockwave {
|
||||
pub shockwave_origin: Pos,
|
||||
pub shockwave_direction: Ori,
|
||||
pub shockwave_angle: f32,
|
||||
pub shockwave_speed: f32,
|
||||
pub shockwave_duration: Duration,
|
||||
pub struct Properties {
|
||||
pub angle: f32,
|
||||
pub speed: f32,
|
||||
pub damage: u32,
|
||||
pub knockback: f32,
|
||||
pub requires_ground: bool,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Shockwave {
|
||||
pub properties: Properties,
|
||||
#[serde(skip)]
|
||||
/// Time that the shockwave was created at
|
||||
/// Used to calculate shockwave propagation
|
||||
/// Deserialized from the network as `None`
|
||||
pub creation: Option<f64>,
|
||||
}
|
||||
|
||||
impl Component for Shockwave {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Shockwave {
|
||||
type Target = Properties;
|
||||
|
||||
fn deref(&self) -> &Properties { &self.properties }
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::{character::CharacterId, comp, sync::Uid, util::Dir};
|
||||
use comp::item::{Item, Reagent};
|
||||
use comp::{
|
||||
item::{Item, Reagent},
|
||||
Ori, Pos,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use specs::Entity as EcsEntity;
|
||||
use std::{collections::VecDeque, ops::DerefMut};
|
||||
@ -48,7 +51,11 @@ pub enum ServerEvent {
|
||||
gravity: Option<comp::Gravity>,
|
||||
speed: f32,
|
||||
},
|
||||
Shockwave {shockwave: comp::Shockwave},
|
||||
Shockwave {
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: Pos,
|
||||
ori: Ori,
|
||||
},
|
||||
LandOnGround {
|
||||
entity: EcsEntity,
|
||||
vel: Vec3<f32>,
|
||||
|
@ -29,6 +29,7 @@ sum_type! {
|
||||
Pos(comp::Pos),
|
||||
Vel(comp::Vel),
|
||||
Ori(comp::Ori),
|
||||
Shockwave(comp::Shockwave),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for EcsCompPhantom
|
||||
@ -56,6 +57,7 @@ sum_type! {
|
||||
Pos(PhantomData<comp::Pos>),
|
||||
Vel(PhantomData<comp::Vel>),
|
||||
Ori(PhantomData<comp::Ori>),
|
||||
Shockwave(PhantomData<comp::Shockwave>),
|
||||
}
|
||||
}
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
@ -83,6 +85,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +111,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +141,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Pos(_) => sync::handle_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ impl State {
|
||||
ecs.register::<comp::CharacterState>();
|
||||
ecs.register::<comp::Object>();
|
||||
ecs.register::<comp::Group>();
|
||||
ecs.register::<comp::Shockwave>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
|
@ -155,7 +155,8 @@ impl CharacterBehavior for Data {
|
||||
projectile,
|
||||
light: self.projectile_light,
|
||||
gravity: self.projectile_gravity,
|
||||
speed: self.initial_projectile_speed + charge_amount * (self.max_projectile_speed - self.initial_projectile_speed),
|
||||
speed: self.initial_projectile_speed
|
||||
+ charge_amount * (self.max_projectile_speed - self.initial_projectile_speed),
|
||||
});
|
||||
|
||||
update.character = CharacterState::ChargedRanged(Data {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{Attacking, CharacterState, Shockwave, StateUpdate},
|
||||
comp::{shockwave, Attacking, CharacterState, StateUpdate},
|
||||
event::ServerEvent,
|
||||
states::utils::*,
|
||||
sys::character_behavior::*,
|
||||
@ -51,19 +51,19 @@ impl CharacterBehavior for Data {
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Attack
|
||||
let shockwave = Shockwave {
|
||||
shockwave_origin: *data.pos,
|
||||
shockwave_direction: *data.ori,
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
let properties = shockwave::Properties {
|
||||
angle: self.shockwave_angle,
|
||||
speed: self.shockwave_speed,
|
||||
duration: self.shockwave_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
requires_ground: true,
|
||||
owner: Some(*data.uid),
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
shockwave,
|
||||
properties,
|
||||
pos: *data.pos,
|
||||
ori: *data.ori,
|
||||
});
|
||||
|
||||
update.character = CharacterState::GroundShockwave(Data {
|
||||
|
@ -33,8 +33,8 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
WriteStorage<'a, Attacking>,
|
||||
WriteStorage<'a, CharacterState>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
@ -52,8 +52,8 @@ impl<'a> System<'a> for Sys {
|
||||
stats,
|
||||
loadouts,
|
||||
groups,
|
||||
mut attacking_storage,
|
||||
character_states,
|
||||
mut attacking_storage,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
@ -5,6 +5,7 @@ pub mod controller;
|
||||
mod mount;
|
||||
pub mod phys;
|
||||
mod projectile;
|
||||
mod shockwave;
|
||||
mod stats;
|
||||
|
||||
// External
|
||||
@ -18,6 +19,7 @@ pub const CONTROLLER_SYS: &str = "controller_sys";
|
||||
pub const MOUNT_SYS: &str = "mount_sys";
|
||||
pub const PHYS_SYS: &str = "phys_sys";
|
||||
pub const PROJECTILE_SYS: &str = "projectile_sys";
|
||||
pub const SHOCKWAVE_SYS: &str = "shockwave_sys";
|
||||
pub const STATS_SYS: &str = "stats_sys";
|
||||
|
||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
@ -30,5 +32,6 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||
dispatch_builder.add(stats::Sys, STATS_SYS, &[]);
|
||||
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_SYS, STATS_SYS]);
|
||||
dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]);
|
||||
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ impl<'a> System<'a> for Sys {
|
||||
mass_other,
|
||||
collider_other,
|
||||
_,
|
||||
group,
|
||||
group_b,
|
||||
) in (
|
||||
&entities,
|
||||
&uids,
|
||||
@ -186,7 +186,7 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
if entity == entity_other || (ignore_group.is_some() && ignore_group == group) {
|
||||
if entity == entity_other || (ignore_group.is_some() && ignore_group == group_b) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
357
common/src/sys/shockwave.rs
Normal file
357
common/src/sys/shockwave.rs
Normal file
@ -0,0 +1,357 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
group, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, Last,
|
||||
Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, Stats,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::{DeltaTime, Time},
|
||||
sync::{Uid, UidAllocator},
|
||||
util::Dir,
|
||||
};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
pub const BLOCK_ANGLE: f32 = 180.0;
|
||||
|
||||
/// This system is responsible for handling accepted inputs like moving or
|
||||
/// attacking
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
#[allow(clippy::type_complexity)]
|
||||
type SystemData = (
|
||||
Entities<'a>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
Read<'a, EventBus<LocalEvent>>,
|
||||
Read<'a, Time>,
|
||||
Read<'a, DeltaTime>,
|
||||
Read<'a, UidAllocator>,
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, Pos>,
|
||||
ReadStorage<'a, Last<Pos>>,
|
||||
ReadStorage<'a, Ori>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Stats>,
|
||||
ReadStorage<'a, Loadout>,
|
||||
ReadStorage<'a, group::Group>,
|
||||
ReadStorage<'a, CharacterState>,
|
||||
ReadStorage<'a, PhysicsState>,
|
||||
WriteStorage<'a, Shockwave>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
entities,
|
||||
server_bus,
|
||||
local_bus,
|
||||
time,
|
||||
dt,
|
||||
uid_allocator,
|
||||
uids,
|
||||
positions,
|
||||
last_positions,
|
||||
orientations,
|
||||
scales,
|
||||
bodies,
|
||||
stats,
|
||||
loadouts,
|
||||
groups,
|
||||
character_states,
|
||||
physics_states,
|
||||
mut shockwaves,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
let mut local_emitter = local_bus.emitter();
|
||||
|
||||
let time = time.0;
|
||||
let dt = dt.0;
|
||||
|
||||
// Shockwaves
|
||||
for (entity, uid, pos, ori, shockwave) in
|
||||
(&entities, &uids, &positions, &orientations, &shockwaves).join()
|
||||
{
|
||||
let creation_time = match shockwave.creation {
|
||||
Some(time) => time,
|
||||
// Skip newly created shockwaves
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let end_time = creation_time + shockwave.duration.as_secs_f64();
|
||||
|
||||
// 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 {
|
||||
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
|
||||
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_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist);
|
||||
let pos2 = Vec2::from(pos.0);
|
||||
|
||||
// From one frame to the next a shockwave travels over a strip of an arc
|
||||
// This is used for collision detection
|
||||
let arc_strip = ArcStrip {
|
||||
origin: pos2,
|
||||
// TODO: make sure this is not Vec2::new(0.0, 0.0)
|
||||
dir: ori.0.xy(),
|
||||
angle: shockwave.angle,
|
||||
start: frame_start_dist,
|
||||
end: frame_end_dist,
|
||||
};
|
||||
|
||||
// Group to ignore collisions with
|
||||
// Might make this more nuanced if shockwaves are used for non damage effects
|
||||
let group = shockwave
|
||||
.owner
|
||||
.and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into()))
|
||||
.and_then(|e| groups.get(e));
|
||||
|
||||
// Go through all other effectable entities
|
||||
for (
|
||||
b,
|
||||
uid_b,
|
||||
pos_b,
|
||||
last_pos_b_maybe,
|
||||
ori_b,
|
||||
scale_b_maybe,
|
||||
character_b,
|
||||
stats_b,
|
||||
body_b,
|
||||
physics_state_b,
|
||||
) in (
|
||||
&entities,
|
||||
&uids,
|
||||
&positions,
|
||||
// TODO: make sure that these are maintained on the client and remove `.maybe()`
|
||||
last_positions.maybe(),
|
||||
&orientations,
|
||||
scales.maybe(),
|
||||
character_states.maybe(),
|
||||
&stats,
|
||||
&bodies,
|
||||
&physics_states,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
// 2D versions
|
||||
let pos_b2 = pos_b.0.xy();
|
||||
let last_pos_b2_maybe = last_pos_b_maybe.map(|p| (p.0).0.xy());
|
||||
|
||||
// Scales
|
||||
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
|
||||
let rad_b = body_b.radius() * scale_b;
|
||||
|
||||
// Check if it is a hit
|
||||
let hit = entity != b
|
||||
&& !stats_b.is_dead
|
||||
// Collision shapes
|
||||
&& {
|
||||
// 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 })
|
||||
})
|
||||
}
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground);
|
||||
|
||||
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::Melee;
|
||||
|
||||
let mut damage = Damage {
|
||||
healthchange: -(shockwave.damage as f32),
|
||||
source,
|
||||
};
|
||||
|
||||
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 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Attack {
|
||||
by: shockwave.owner.unwrap_or(*uid),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if shockwave.knockback != 0.0 {
|
||||
local_emitter.emit(LocalEvent::ApplyForce {
|
||||
entity: b,
|
||||
force: shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set start time on new shockwaves
|
||||
// This change doesn't need to be recorded as it is not sent to the client
|
||||
shockwaves.set_event_emission(false);
|
||||
(&mut shockwaves).join().for_each(|shockwave| {
|
||||
if shockwave.creation.is_none() {
|
||||
shockwave.creation = Some(time);
|
||||
}
|
||||
});
|
||||
shockwaves.set_event_emission(true);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ArcStrip {
|
||||
origin: Vec2<f32>,
|
||||
/// Normalizable direction
|
||||
dir: Vec2<f32>,
|
||||
/// Angle in degrees
|
||||
angle: f32,
|
||||
/// Start radius
|
||||
start: f32,
|
||||
/// End radius
|
||||
end: f32,
|
||||
}
|
||||
|
||||
impl ArcStrip {
|
||||
fn collides_with_circle(self, c: Circle) -> 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
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let dist = self.origin.distance(c.pos);
|
||||
let half_angle = self.angle.to_radians() / 2.0;
|
||||
|
||||
if dist > self.end + c.radius || dist + c.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 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)
|
||||
};
|
||||
let arc_segment_in_circle = || {
|
||||
let midpoint = self.dir.normalized();
|
||||
let segment_in_circle = |angle| {
|
||||
let dir = midpoint.rotated_z(angle);
|
||||
let side = LineSegment2 {
|
||||
start: dir * self.start + self.origin,
|
||||
end: dir * self.end + self.origin,
|
||||
};
|
||||
c.contains_point(side.projected_point(c.pos))
|
||||
};
|
||||
segment_in_circle(half_angle) || segment_in_circle(-half_angle)
|
||||
};
|
||||
|
||||
if dist > self.end {
|
||||
// Circle center is outside ring
|
||||
// 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);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
} else if dist < self.start {
|
||||
// Circle center is inside ring
|
||||
// Check angle of intersection points on inside edge of ring
|
||||
// Check if circle contains one of the inner points of the arc
|
||||
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);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
)
|
||||
} else if c.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ use crate::{sys, Server, StateExt};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
comp::{
|
||||
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, Agent, Alignment, Body, Gravity, Item,
|
||||
ItemDrop, LightEmitter, Loadout, Pos, Projectile, Scale, Shockwave, 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,
|
||||
@ -124,10 +124,14 @@ pub fn handle_shoot(
|
||||
builder.build();
|
||||
}
|
||||
|
||||
pub fn handle_shockwave(server: &mut Server, shockwave: Shockwave) {
|
||||
pub fn handle_shockwave(
|
||||
server: &mut Server,
|
||||
properties: shockwave::Properties,
|
||||
pos: Pos,
|
||||
ori: Ori,
|
||||
) {
|
||||
let state = server.state_mut();
|
||||
let builder = state.create_shockwave(shockwave);
|
||||
builder.build();
|
||||
state.create_shockwave(properties, pos, ori).build();
|
||||
}
|
||||
|
||||
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
|
@ -5,7 +5,7 @@ use common::{
|
||||
};
|
||||
use entity_creation::{
|
||||
handle_create_npc, handle_create_waypoint, handle_initialize_character,
|
||||
handle_loaded_character_data, handle_shoot,
|
||||
handle_loaded_character_data, handle_shockwave, handle_shoot,
|
||||
};
|
||||
use entity_manipulation::{
|
||||
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
|
||||
@ -70,7 +70,11 @@ impl Server {
|
||||
gravity,
|
||||
speed,
|
||||
} => handle_shoot(self, entity, dir, body, light, projectile, gravity, speed),
|
||||
ServerEvent::Shockwave {shockwave} => handle_shockwave(self, shockwave),
|
||||
ServerEvent::Shockwave {
|
||||
properties,
|
||||
pos,
|
||||
ori,
|
||||
} => handle_shockwave(self, properties, pos, ori),
|
||||
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),
|
||||
|
@ -39,6 +39,13 @@ pub trait StateExt {
|
||||
body: comp::Body,
|
||||
projectile: comp::Projectile,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a shockwave entity
|
||||
fn create_shockwave(
|
||||
&mut self,
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Insert common/default components for a new character joining the server
|
||||
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
|
||||
/// Update the components associated with the entity's current character.
|
||||
@ -134,6 +141,22 @@ impl StateExt for State {
|
||||
.with(comp::Sticky)
|
||||
}
|
||||
|
||||
fn create_shockwave(
|
||||
&mut self,
|
||||
properties: comp::shockwave::Properties,
|
||||
pos: comp::Pos,
|
||||
ori: comp::Ori,
|
||||
) -> EcsEntityBuilder {
|
||||
self.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(ori)
|
||||
.with(comp::Shockwave {
|
||||
properties,
|
||||
creation: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) {
|
||||
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
||||
|
||||
|
@ -2,7 +2,8 @@ use super::SysTimer;
|
||||
use common::{
|
||||
comp::{
|
||||
Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter,
|
||||
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel,
|
||||
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky,
|
||||
Vel,
|
||||
},
|
||||
msg::EcsCompPacket,
|
||||
span,
|
||||
@ -57,6 +58,7 @@ pub struct TrackedComps<'a> {
|
||||
pub gravity: ReadStorage<'a, Gravity>,
|
||||
pub loadout: ReadStorage<'a, Loadout>,
|
||||
pub character_state: ReadStorage<'a, CharacterState>,
|
||||
pub shockwave: ReadStorage<'a, Shockwave>,
|
||||
}
|
||||
impl<'a> TrackedComps<'a> {
|
||||
pub fn create_entity_package(
|
||||
@ -132,6 +134,11 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.shockwave
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
// Add untracked comps
|
||||
// Add untracked comps
|
||||
pos.map(|c| comps.push(c.into()));
|
||||
vel.map(|c| comps.push(c.into()));
|
||||
@ -160,6 +167,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>,
|
||||
pub loadout: ReadExpect<'a, UpdateTracker<Loadout>>,
|
||||
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
|
||||
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
|
||||
}
|
||||
impl<'a> ReadTrackers<'a> {
|
||||
pub fn create_sync_packages(
|
||||
@ -197,7 +205,8 @@ impl<'a> ReadTrackers<'a> {
|
||||
&*self.character_state,
|
||||
&comps.character_state,
|
||||
filter,
|
||||
);
|
||||
)
|
||||
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter);
|
||||
|
||||
(entity_sync_package, comp_sync_package)
|
||||
}
|
||||
@ -223,6 +232,7 @@ pub struct WriteTrackers<'a> {
|
||||
gravity: WriteExpect<'a, UpdateTracker<Gravity>>,
|
||||
loadout: WriteExpect<'a, UpdateTracker<Loadout>>,
|
||||
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
|
||||
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
|
||||
}
|
||||
|
||||
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
@ -247,6 +257,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers
|
||||
.character_state
|
||||
.record_changes(&comps.character_state);
|
||||
trackers.shockwave.record_changes(&comps.shockwave);
|
||||
// Debug how many updates are being sent
|
||||
/*
|
||||
macro_rules! log_counts {
|
||||
@ -278,6 +289,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(gravity, "Gravitys");
|
||||
log_counts!(loadout, "Loadouts");
|
||||
log_counts!(character_state, "Character States");
|
||||
log_counts!(shockwave, "Shockwaves");
|
||||
*/
|
||||
}
|
||||
|
||||
@ -300,6 +312,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<Gravity>();
|
||||
world.register_tracker::<Loadout>();
|
||||
world.register_tracker::<CharacterState>();
|
||||
world.register_tracker::<Shockwave>();
|
||||
}
|
||||
|
||||
/// Deleted entities grouped by region
|
||||
|
Loading…
Reference in New Issue
Block a user