diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 7fcf42ae05..9f98dae078 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -1,6 +1,6 @@ use crate::sync::Uid; use serde::{Deserialize, Serialize}; -use specs::{Component, FlaggedStorage}; +use specs::{Component, FlaggedStorage, VecStorage}; use specs_idvs::IdvStorage; use std::time::Duration; @@ -18,7 +18,7 @@ pub struct Properties { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Beam { +pub struct BeamSegment { pub properties: Properties, #[serde(skip)] /// Time that the beam segment was created at @@ -27,12 +27,23 @@ pub struct Beam { pub creation: Option, } -impl Component for Beam { +impl Component for BeamSegment { type Storage = FlaggedStorage>; } -impl std::ops::Deref for Beam { +impl std::ops::Deref for BeamSegment { type Target = Properties; fn deref(&self) -> &Properties { &self.properties } } + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Beam { + pub hit_entities: Vec, + pub tick_dur: Duration, + pub timer: Duration, +} + +impl Component for Beam { + type Storage = VecStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index a1e529e75e..e2d403d1d8 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -26,7 +26,7 @@ pub mod visual; pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout}; pub use admin::{Admin, AdminList}; pub use agent::{Agent, Alignment}; -pub use beam::Beam; +pub use beam::{Beam, BeamSegment}; pub use body::{ biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object, quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData, diff --git a/common/src/event.rs b/common/src/event.rs index f8b53d5afe..a8480143ca 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -64,7 +64,7 @@ pub enum ServerEvent { entity: EcsEntity, impulse: Vec3, }, - Beam { + BeamSegment { properties: comp::beam::Properties, pos: Pos, ori: Ori, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 6bff5a852a..ce9852720f 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -30,7 +30,7 @@ sum_type! { Vel(comp::Vel), Ori(comp::Ori), Shockwave(comp::Shockwave), - Beam(comp::Beam), + BeamSegment(comp::BeamSegment), } } // Automatically derive From for EcsCompPhantom @@ -59,7 +59,7 @@ sum_type! { Vel(PhantomData), Ori(PhantomData), Shockwave(PhantomData), - Beam(PhantomData), + BeamSegment(PhantomData), } } impl sync::CompPacket for EcsCompPacket { @@ -88,7 +88,7 @@ impl sync::CompPacket for EcsCompPacket { 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), - EcsCompPacket::Beam(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world), } } @@ -115,7 +115,7 @@ impl sync::CompPacket for EcsCompPacket { 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), - EcsCompPacket::Beam(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world), } } @@ -146,7 +146,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Beam(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::BeamSegment(_) => sync::handle_remove::(entity, world), } } } diff --git a/common/src/state.rs b/common/src/state.rs index 9859d62751..be586f6b55 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -127,7 +127,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); + ecs.register::(); // Register components send from clients -> server ecs.register::(); @@ -164,6 +164,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); // Register synced resources used by the ECS. ecs.insert(TimeOfDay(0.0)); diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index ad0ffafb1d..830babef21 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -2,6 +2,7 @@ use crate::{ comp::{beam, CharacterState, Ori, Pos, StateUpdate}, event::ServerEvent, states::utils::*, + sync::Uid, sys::character_behavior::*, }; use serde::{Deserialize, Serialize}; @@ -54,6 +55,12 @@ impl CharacterBehavior for Data { } if self.buildup_duration != Duration::default() { + // Creates beam + data.updater.insert(data.entity, beam::Beam { + hit_entities: Vec::::new(), + tick_dur: Duration::from_secs_f32(1.0 / self.tick_rate), + timer: Duration::default(), + }); // Build up update.character = CharacterState::BasicBeam(Data { exhausted: self.exhausted, @@ -93,7 +100,7 @@ impl CharacterBehavior for Data { }; let pos = Pos(data.pos.0 + Vec3::new(0.0, 0.0, 1.0)); // Create beam segment - update.server_events.push_front(ServerEvent::Beam { + update.server_events.push_front(ServerEvent::BeamSegment { properties, pos, ori: Ori(data.inputs.look_dir), @@ -104,7 +111,7 @@ impl CharacterBehavior for Data { particle_ori: Some(*data.inputs.look_dir), buildup_duration: self.buildup_duration, recover_duration: self.recover_duration, - cooldown_duration: Duration::from_secs_f32(1.0 / self.tick_rate), + cooldown_duration: Duration::from_secs_f32(1.0 / self.tick_rate / 10.0), beam_duration: self.beam_duration, base_hps: self.base_hps, base_dps: self.base_dps, @@ -178,6 +185,8 @@ impl CharacterBehavior for Data { } else { // Done update.character = CharacterState::Wielding; + // Make sure beam component is removed + data.updater.remove::(data.entity); } update diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 6bd66a38a7..c5e1304c38 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,6 +1,6 @@ use crate::{ comp::{ - group, Beam, Body, CharacterState, Damage, DamageSource, Energy, EnergySource, + group, Beam, BeamSegment, Body, CharacterState, Damage, DamageSource, Energy, EnergySource, HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, @@ -8,12 +8,12 @@ use crate::{ sync::{Uid, UidAllocator}, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; +use std::time::Duration; use vek::*; pub const BLOCK_ANGLE: f32 = 180.0; -/// This system is responsible for handling accepted inputs like moving or -/// attacking +/// This system is responsible for handling beams that heal or do damage pub struct Sys; impl<'a> System<'a> for Sys { #[allow(clippy::type_complexity)] @@ -34,6 +34,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, group::Group>, ReadStorage<'a, CharacterState>, WriteStorage<'a, Energy>, + WriteStorage<'a, BeamSegment>, WriteStorage<'a, Beam>, ); @@ -56,6 +57,7 @@ impl<'a> System<'a> for Sys { groups, character_states, mut energies, + mut beam_segments, mut beams, ): Self::SystemData, ) { @@ -65,16 +67,16 @@ impl<'a> System<'a> for Sys { let dt = dt.0; // Beams - for (entity, uid, pos, ori, beam) in - (&entities, &uids, &positions, &orientations, &beams).join() + for (entity, uid, pos, ori, beam_segment) in + (&entities, &uids, &positions, &orientations, &beam_segments).join() { - let creation_time = match beam.creation { + let creation_time = match beam_segment.creation { Some(time) => time, // Skip newly created beam segments None => continue, }; - let end_time = creation_time + beam.duration.as_secs_f64(); + let end_time = creation_time + beam_segment.duration.as_secs_f64(); // If beam segment is out of time emit destroy event but still continue since it // may have traveled and produced effects a bit before reaching it's @@ -94,15 +96,23 @@ impl<'a> System<'a> for Sys { // Note: min() probably uneeded let time_since_creation = (time - creation_time) as f32; - let frame_start_dist = (beam.speed * (time_since_creation - frame_time)).max(0.0); - let frame_end_dist = (beam.speed * time_since_creation).max(frame_start_dist); + let frame_start_dist = + (beam_segment.speed * (time_since_creation - frame_time)).max(0.0); + let frame_end_dist = (beam_segment.speed * time_since_creation).max(frame_start_dist); + + let beam_owner = beam_segment + .owner + .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())); // Group to ignore collisions with // Might make this more nuanced if beams are used for non damage effects - let group = beam - .owner - .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) - .and_then(|e| groups.get(e)); + let group = beam_owner.and_then(|e| groups.get(e)); + + let hit_entities = if let Some(beam) = beam_owner.and_then(|e| beams.get_mut(e)) { + &mut beam.hit_entities + } else { + continue; + }; // Go through all other effectable entities for ( @@ -129,6 +139,11 @@ impl<'a> System<'a> for Sys { ) .join() { + // Check to see if entity has already been hit recently + if hit_entities.iter().any(|&uid| uid == *uid_b) { + continue; + } + // Scales let scale_b = scale_b_maybe.map_or(1.0, |s| s.0); let rad_b = body_b.radius() * scale_b; @@ -138,26 +153,26 @@ impl<'a> System<'a> for Sys { let hit = entity != b && !stats_b.is_dead // Collision shapes - && (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam.angle, pos_b.0, rad_b, height_b) - || last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam.angle, (pos_maybe.0).0, rad_b, height_b)})); + && (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, pos_b.0, rad_b, height_b) + || last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, (pos_maybe.0).0, rad_b, height_b)})); 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) == beam.owner); + .unwrap_or(Some(*uid_b) == beam_segment.owner); // If owner, shouldn't heal or damage - if Some(*uid_b) == beam.owner { + if Some(*uid_b) == beam_segment.owner { continue; } // Don't heal if outside group // Don't damage in the same group let (mut is_heal, mut is_damage) = (false, false); - if !same_group && (beam.damage > 0) { + if !same_group && (beam_segment.damage > 0) { is_damage = true; } - if same_group && (beam.heal > 0) { + if same_group && (beam_segment.heal > 0) { is_heal = true; } if !is_heal && !is_damage { @@ -171,9 +186,9 @@ impl<'a> System<'a> for Sys { DamageSource::Energy }; let healthchange = if is_heal { - beam.heal as f32 + beam_segment.heal as f32 } else { - -(beam.damage as f32) + -(beam_segment.damage as f32) }; let mut damage = Damage { @@ -194,33 +209,40 @@ impl<'a> System<'a> for Sys { uid: *uid_b, change: HealthChange { amount: damage.healthchange as i32, - cause: HealthSource::Energy { owner: beam.owner }, + cause: HealthSource::Energy { + owner: beam_segment.owner, + }, }, }); server_emitter.emit(ServerEvent::Damage { - uid: beam.owner.unwrap_or(*uid), + uid: beam_segment.owner.unwrap_or(*uid), change: HealthChange { - amount: (-damage.healthchange * beam.lifesteal_eff) as i32, - cause: HealthSource::Healing { by: beam.owner }, + amount: (-damage.healthchange * beam_segment.lifesteal_eff) as i32, + cause: HealthSource::Healing { + by: beam_segment.owner, + }, }, }); - if let Some(energy_mut) = beam + if let Some(energy_mut) = beam_segment .owner .and_then(|o| uid_allocator.retrieve_entity_internal(o.into())) .and_then(|o| energies.get_mut(o)) { - energy_mut.change_by(beam.energy_regen as i32, EnergySource::HitEnemy); + energy_mut.change_by( + beam_segment.energy_regen as i32, + EnergySource::HitEnemy, + ); } } if is_heal { - if let Some(energy_mut) = beam + if let Some(energy_mut) = beam_segment .owner .and_then(|o| uid_allocator.retrieve_entity_internal(o.into())) .and_then(|o| energies.get_mut(o)) { if energy_mut .try_change_by( - -(beam.energy_drain as i32), // Stamina use + -(beam_segment.energy_drain as i32), // Stamina use EnergySource::Ability, ) .is_ok() @@ -229,25 +251,41 @@ impl<'a> System<'a> for Sys { uid: *uid_b, change: HealthChange { amount: damage.healthchange as i32, - cause: HealthSource::Healing { by: beam.owner }, + cause: HealthSource::Healing { + by: beam_segment.owner, + }, }, }); } } } + // Adds entities that were hit to the hit_entities list on the beam, sees if it + // needs to purge the hit_entities list + hit_entities.push(*uid_b); } } } + for beam in (&mut beams).join() { + beam.timer = beam + .timer + .checked_add(Duration::from_secs_f32(dt)) + .unwrap_or(beam.tick_dur); + if beam.timer >= beam.tick_dur { + beam.hit_entities.clear(); + beam.timer = beam.timer.checked_sub(beam.tick_dur).unwrap_or_default(); + } + } + // Set start time on new beams // This change doesn't need to be recorded as it is not sent to the client - beams.set_event_emission(false); - (&mut beams).join().for_each(|beam| { - if beam.creation.is_none() { - beam.creation = Some(time); + beam_segments.set_event_emission(false); + (&mut beam_segments).join().for_each(|beam_segment| { + if beam_segment.creation.is_none() { + beam_segment.creation = Some(time); } }); - beams.set_event_emission(true); + beam_segments.set_event_emission(true); } } diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index 72faec9d0e..9ac69116cb 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -1,6 +1,6 @@ use crate::{ comp::{ - Attacking, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, + Attacking, Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, @@ -64,6 +64,7 @@ pub struct JoinData<'a> { pub body: &'a Body, pub physics: &'a PhysicsState, pub attacking: Option<&'a Attacking>, + pub beam: Option<&'a Beam>, pub updater: &'a LazyUpdate, } @@ -89,6 +90,7 @@ pub type JoinTuple<'a> = ( &'a Body, &'a PhysicsState, Option<&'a Attacking>, + Option<&'a Beam>, ); fn incorporate_update(tuple: &mut JoinTuple, state_update: StateUpdate) { @@ -126,6 +128,7 @@ impl<'a> JoinData<'a> { body: j.10, physics: j.11, attacking: j.12, + beam: j.13, updater, dt, } @@ -158,6 +161,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Body>, ReadStorage<'a, PhysicsState>, ReadStorage<'a, Attacking>, + ReadStorage<'a, Beam>, ReadStorage<'a, Uid>, ReadStorage<'a, Mounting>, ); @@ -184,6 +188,7 @@ impl<'a> System<'a> for Sys { bodies, physics_states, attacking_storage, + beam_storage, uids, mountings, ): Self::SystemData, @@ -207,6 +212,7 @@ impl<'a> System<'a> for Sys { &bodies, &physics_states, attacking_storage.maybe(), + beam_storage.maybe(), ) .join() { diff --git a/common/src/sys/phys.rs b/common/src/sys/phys.rs index 461d17197a..7d45102ca1 100644 --- a/common/src/sys/phys.rs +++ b/common/src/sys/phys.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - Beam, Collider, Gravity, Group, Mass, Mounting, Ori, PhysicsState, Pos, Projectile, Scale, - Sticky, Vel, + BeamSegment, Collider, Gravity, Group, Mass, Mounting, Ori, PhysicsState, Pos, Projectile, + Scale, Sticky, Vel, }, event::{EventBus, ServerEvent}, metrics::SysMetrics, @@ -70,7 +70,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Mounting>, ReadStorage<'a, Group>, ReadStorage<'a, Projectile>, - ReadStorage<'a, Beam>, + ReadStorage<'a, BeamSegment>, ); #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index fc485a31e1..0f961e11b1 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -84,7 +84,7 @@ impl Server { pos, ori, } => handle_shockwave(self, properties, pos, ori), - ServerEvent::Beam { + ServerEvent::BeamSegment { properties, pos, ori, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 70974684db..e51de63761 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -173,7 +173,7 @@ impl StateExt for State { .create_entity_synced() .with(pos) .with(ori) - .with(comp::Beam { + .with(comp::BeamSegment { properties, creation: None, }) diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index b3181213fe..fcecccb288 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,9 +1,9 @@ use super::SysTimer; use common::{ comp::{ - Beam, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter, - Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky, - Vel, + BeamSegment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, + LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, + Stats, Sticky, Vel, }, msg::EcsCompPacket, span, @@ -59,7 +59,7 @@ pub struct TrackedComps<'a> { pub loadout: ReadStorage<'a, Loadout>, pub character_state: ReadStorage<'a, CharacterState>, pub shockwave: ReadStorage<'a, Shockwave>, - pub beam: ReadStorage<'a, Beam>, + pub beam_segment: ReadStorage<'a, BeamSegment>, } impl<'a> TrackedComps<'a> { pub fn create_entity_package( @@ -139,7 +139,10 @@ impl<'a> TrackedComps<'a> { .get(entity) .cloned() .map(|c| comps.push(c.into())); - self.beam.get(entity).cloned().map(|c| comps.push(c.into())); + self.beam_segment + .get(entity) + .cloned() + .map(|c| comps.push(c.into())); // Add untracked comps pos.map(|c| comps.push(c.into())); vel.map(|c| comps.push(c.into())); @@ -169,7 +172,7 @@ pub struct ReadTrackers<'a> { pub loadout: ReadExpect<'a, UpdateTracker>, pub character_state: ReadExpect<'a, UpdateTracker>, pub shockwave: ReadExpect<'a, UpdateTracker>, - pub beam: ReadExpect<'a, UpdateTracker>, + pub beam_segment: ReadExpect<'a, UpdateTracker>, } impl<'a> ReadTrackers<'a> { pub fn create_sync_packages( @@ -209,7 +212,7 @@ impl<'a> ReadTrackers<'a> { filter, ) .with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter) - .with_component(&comps.uid, &*self.beam, &comps.beam, filter); + .with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter); (entity_sync_package, comp_sync_package) } @@ -236,7 +239,7 @@ pub struct WriteTrackers<'a> { loadout: WriteExpect<'a, UpdateTracker>, character_state: WriteExpect<'a, UpdateTracker>, shockwave: WriteExpect<'a, UpdateTracker>, - beam: WriteExpect<'a, UpdateTracker>, + beam: WriteExpect<'a, UpdateTracker>, } fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { @@ -262,7 +265,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { .character_state .record_changes(&comps.character_state); trackers.shockwave.record_changes(&comps.shockwave); - trackers.beam.record_changes(&comps.beam); + trackers.beam.record_changes(&comps.beam_segment); // Debug how many updates are being sent /* macro_rules! log_counts { @@ -319,7 +322,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); - world.register_tracker::(); + world.register_tracker::(); } /// Deleted entities grouped by region