Beam tick rate now more responsive when aiming.

This commit is contained in:
Sam 2020-09-23 21:02:30 -05:00
parent 506ad1e80e
commit a8f31cbe6f
12 changed files with 133 additions and 65 deletions

View File

@ -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<f64>,
}
impl Component for Beam {
impl Component for BeamSegment {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
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<Uid>,
pub tick_dur: Duration,
pub timer: Duration,
}
impl Component for Beam {
type Storage = VecStorage<Self>;
}

View File

@ -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,

View File

@ -64,7 +64,7 @@ pub enum ServerEvent {
entity: EcsEntity,
impulse: Vec3<f32>,
},
Beam {
BeamSegment {
properties: comp::beam::Properties,
pos: Pos,
ori: Ori,

View File

@ -30,7 +30,7 @@ sum_type! {
Vel(comp::Vel),
Ori(comp::Ori),
Shockwave(comp::Shockwave),
Beam(comp::Beam),
BeamSegment(comp::BeamSegment),
}
}
// Automatically derive From<T> for EcsCompPhantom
@ -59,7 +59,7 @@ sum_type! {
Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>),
Shockwave(PhantomData<comp::Shockwave>),
Beam(PhantomData<comp::Beam>),
BeamSegment(PhantomData<comp::BeamSegment>),
}
}
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::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
EcsCompPhantom::Beam(_) => sync::handle_remove::<comp::Ori>(entity, world),
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::Ori>(entity, world),
}
}
}

View File

@ -127,7 +127,7 @@ impl State {
ecs.register::<comp::Object>();
ecs.register::<comp::Group>();
ecs.register::<comp::Shockwave>();
ecs.register::<comp::Beam>();
ecs.register::<comp::BeamSegment>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
@ -164,6 +164,7 @@ impl State {
ecs.register::<comp::Faction>();
ecs.register::<comp::group::Invite>();
ecs.register::<comp::group::PendingInvites>();
ecs.register::<comp::Beam>();
// Register synced resources used by the ECS.
ecs.insert(TimeOfDay(0.0));

View File

@ -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::<Uid>::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::<beam::Beam>(data.entity);
}
update

View File

@ -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);
}
}

View File

@ -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()
{

View File

@ -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

View File

@ -84,7 +84,7 @@ impl Server {
pos,
ori,
} => handle_shockwave(self, properties, pos, ori),
ServerEvent::Beam {
ServerEvent::BeamSegment {
properties,
pos,
ori,

View File

@ -173,7 +173,7 @@ impl StateExt for State {
.create_entity_synced()
.with(pos)
.with(ori)
.with(comp::Beam {
.with(comp::BeamSegment {
properties,
creation: None,
})

View File

@ -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<Loadout>>,
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
pub beam: ReadExpect<'a, UpdateTracker<Beam>>,
pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>,
}
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<Loadout>>,
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
beam: WriteExpect<'a, UpdateTracker<Beam>>,
beam: WriteExpect<'a, UpdateTracker<BeamSegment>>,
}
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::<Loadout>();
world.register_tracker::<CharacterState>();
world.register_tracker::<Shockwave>();
world.register_tracker::<Beam>();
world.register_tracker::<BeamSegment>();
}
/// Deleted entities grouped by region