diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 0d2530b7ea..f7c4489465 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -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, diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 49d5a28251..e53d611716 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -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, } +#[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, +} + impl Component for Shockwave { type Storage = FlaggedStorage>; } + +impl std::ops::Deref for Shockwave { + type Target = Properties; + + fn deref(&self) -> &Properties { &self.properties } +} diff --git a/common/src/event.rs b/common/src/event.rs index e2bd4d1ea4..616fb5736a 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -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, speed: f32, }, - Shockwave {shockwave: comp::Shockwave}, + Shockwave { + properties: comp::shockwave::Properties, + pos: Pos, + ori: Ori, + }, LandOnGround { entity: EcsEntity, vel: Vec3, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 726d67321a..244733612b 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -29,6 +29,7 @@ sum_type! { Pos(comp::Pos), Vel(comp::Vel), Ori(comp::Ori), + Shockwave(comp::Shockwave), } } // Automatically derive From for EcsCompPhantom @@ -56,6 +57,7 @@ sum_type! { Pos(PhantomData), Vel(PhantomData), Ori(PhantomData), + Shockwave(PhantomData), } } 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::(entity, world), EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Shockwave(_) => sync::handle_remove::(entity, world), } } } diff --git a/common/src/state.rs b/common/src/state.rs index 78f34068fb..bc203820cb 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -126,6 +126,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); // Register components send from clients -> server ecs.register::(); diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index c16f594711..8f501785d3 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -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 { diff --git a/common/src/states/ground_shockwave.rs b/common/src/states/ground_shockwave.rs index c9fc147dce..124684a45a 100644 --- a/common/src/states/ground_shockwave.rs +++ b/common/src/states/ground_shockwave.rs @@ -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 { diff --git a/common/src/sys/combat.rs b/common/src/sys/combat.rs index 2de2ea4058..f4f4352ecc 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/combat.rs @@ -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(); diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index 832dc37d63..c5716b6211 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -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]); } diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 36a5ed75f6..15b3bb0c54 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -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; } diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs new file mode 100644 index 0000000000..ee78384701 --- /dev/null +++ b/common/src/sys/shockwave.rs @@ -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>, + Read<'a, EventBus>, + Read<'a, Time>, + Read<'a, DeltaTime>, + Read<'a, UidAllocator>, + ReadStorage<'a, Uid>, + ReadStorage<'a, Pos>, + ReadStorage<'a, Last>, + 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, + /// Normalizable direction + dir: Vec2, + /// 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, + 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, Vec2) { + 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) -> 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 + } +} diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index f120bd7e34..586d8400e7 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -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) { diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 6cdd018c0b..63ded0012f 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -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), diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 97c3be95ec..bb577f2f1b 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -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::().0; diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 5fb64ddd5f..ea208acd1b 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -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>, pub loadout: ReadExpect<'a, UpdateTracker>, pub character_state: ReadExpect<'a, UpdateTracker>, + pub shockwave: ReadExpect<'a, UpdateTracker>, } 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>, loadout: WriteExpect<'a, UpdateTracker>, character_state: WriteExpect<'a, UpdateTracker>, + shockwave: WriteExpect<'a, UpdateTracker>, } 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::(); world.register_tracker::(); world.register_tracker::(); + world.register_tracker::(); } /// Deleted entities grouped by region