Added beam system. Added collision code for spherical wedge/cylinder detection.

This commit is contained in:
Sam 2020-09-05 11:27:36 -05:00
parent ddbe871b50
commit 46563e7008
15 changed files with 380 additions and 23 deletions

View File

@ -153,6 +153,7 @@ pub enum CharacterAbility {
energy_cost: u32, energy_cost: u32,
buildup_duration: Duration, buildup_duration: Duration,
recover_duration: Duration, recover_duration: Duration,
beam_duration: Duration,
base_hps: u32, base_hps: u32,
base_dps: u32, base_dps: u32,
tick_rate: f32, tick_rate: f32,
@ -520,6 +521,7 @@ impl From<&CharacterAbility> for CharacterState {
energy_cost: _, energy_cost: _,
buildup_duration, buildup_duration,
recover_duration, recover_duration,
beam_duration,
base_hps, base_hps,
base_dps, base_dps,
tick_rate, tick_rate,
@ -533,6 +535,7 @@ impl From<&CharacterAbility> for CharacterState {
buildup_duration: *buildup_duration, buildup_duration: *buildup_duration,
cooldown_duration: Duration::default(), cooldown_duration: Duration::default(),
recover_duration: *recover_duration, recover_duration: *recover_duration,
beam_duration: *beam_duration,
base_hps: *base_hps, base_hps: *base_hps,
base_dps: *base_dps, base_dps: *base_dps,
tick_rate: *tick_rate, tick_rate: *tick_rate,

36
common/src/comp/beam.rs Normal file
View File

@ -0,0 +1,36 @@
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 Properties {
pub angle: f32,
pub speed: f32,
pub damage: u32,
pub heal: u32,
pub lifesteal_eff: f32,
pub duration: Duration,
pub owner: Option<Uid>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Beam {
pub properties: Properties,
#[serde(skip)]
/// Time that the beam segment was created at
/// Used to calculate beam propagation
/// Deserialized from the network as `None`
pub creation: Option<f64>,
}
impl Component for Beam {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
impl std::ops::Deref for Beam {
type Target = Properties;
fn deref(&self) -> &Properties { &self.properties }
}

View File

@ -306,6 +306,7 @@ impl Tool {
energy_cost: 0, energy_cost: 0,
buildup_duration: Duration::from_millis(250), buildup_duration: Duration::from_millis(250),
recover_duration: Duration::from_millis(250), recover_duration: Duration::from_millis(250),
beam_duration: Duration::from_secs(2),
base_hps: (60.0 * self.base_power()) as u32, base_hps: (60.0 * self.base_power()) as u32,
base_dps: (40.0 * self.base_power()) as u32, base_dps: (40.0 * self.base_power()) as u32,
tick_rate: 2.0, tick_rate: 2.0,

View File

@ -1,6 +1,7 @@
mod ability; mod ability;
mod admin; mod admin;
pub mod agent; pub mod agent;
pub mod beam;
mod body; mod body;
mod character_state; mod character_state;
pub mod chat; pub mod chat;
@ -25,6 +26,7 @@ pub mod visual;
pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout}; pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout};
pub use admin::{Admin, AdminList}; pub use admin::{Admin, AdminList};
pub use agent::{Agent, Alignment}; pub use agent::{Agent, Alignment};
pub use beam::Beam;
pub use body::{ pub use body::{
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object, biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData, quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData,

View File

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

View File

@ -30,6 +30,7 @@ sum_type! {
Vel(comp::Vel), Vel(comp::Vel),
Ori(comp::Ori), Ori(comp::Ori),
Shockwave(comp::Shockwave), Shockwave(comp::Shockwave),
Beam(comp::Beam),
} }
} }
// Automatically derive From<T> for EcsCompPhantom // Automatically derive From<T> for EcsCompPhantom
@ -58,6 +59,7 @@ sum_type! {
Vel(PhantomData<comp::Vel>), Vel(PhantomData<comp::Vel>),
Ori(PhantomData<comp::Ori>), Ori(PhantomData<comp::Ori>),
Shockwave(PhantomData<comp::Shockwave>), Shockwave(PhantomData<comp::Shockwave>),
Beam(PhantomData<comp::Beam>),
} }
} }
impl sync::CompPacket for EcsCompPacket { impl sync::CompPacket for EcsCompPacket {
@ -86,6 +88,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Vel(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::Ori(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Shockwave(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),
} }
} }
@ -112,6 +115,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Vel(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::Ori(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Shockwave(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),
} }
} }
@ -142,6 +146,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world), EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world), EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
EcsCompPhantom::Beam(_) => sync::handle_remove::<comp::Ori>(entity, world),
} }
} }
} }

View File

@ -127,6 +127,7 @@ impl State {
ecs.register::<comp::Object>(); ecs.register::<comp::Object>();
ecs.register::<comp::Group>(); ecs.register::<comp::Group>();
ecs.register::<comp::Shockwave>(); ecs.register::<comp::Shockwave>();
ecs.register::<comp::Beam>();
// Register components send from clients -> server // Register components send from clients -> server
ecs.register::<comp::Controller>(); ecs.register::<comp::Controller>();

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
comp::{Attacking, CharacterState, EnergySource, StateUpdate}, comp::{beam, Attacking, CharacterState, EnergySource, Ori, Pos, StateUpdate},
event::ServerEvent,
states::utils::*, states::utils::*,
sys::character_behavior::*, sys::character_behavior::*,
}; };
@ -19,6 +20,8 @@ pub struct Data {
pub cooldown_duration: Duration, pub cooldown_duration: Duration,
/// How long the state has until exiting /// How long the state has until exiting
pub recover_duration: Duration, pub recover_duration: Duration,
/// How long each beam segment persists for
pub beam_duration: Duration,
/// Base healing per second /// Base healing per second
pub base_hps: u32, pub base_hps: u32,
/// Base damage per second /// Base damage per second
@ -54,6 +57,7 @@ impl CharacterBehavior for Data {
.unwrap_or_default(), .unwrap_or_default(),
cooldown_duration: self.cooldown_duration, cooldown_duration: self.cooldown_duration,
recover_duration: self.recover_duration, recover_duration: self.recover_duration,
beam_duration: self.beam_duration,
base_hps: self.base_hps, base_hps: self.base_hps,
base_dps: self.base_dps, base_dps: self.base_dps,
tick_rate: self.tick_rate, tick_rate: self.tick_rate,
@ -65,18 +69,22 @@ impl CharacterBehavior for Data {
} else if data.inputs.primary.is_pressed() && !self.exhausted { } else if data.inputs.primary.is_pressed() && !self.exhausted {
let damage = (self.base_dps as f32 / self.tick_rate) as u32; let damage = (self.base_dps as f32 / self.tick_rate) as u32;
let heal = (self.base_hps as f32 / self.tick_rate) as u32; let heal = (self.base_hps as f32 / self.tick_rate) as u32;
// Hit attempt let speed = self.range / self.beam_duration.as_secs_f32();
data.updater.insert(data.entity, Attacking { let properties = beam::Properties {
base_damage: damage, angle: self.max_angle.to_radians(),
base_heal: heal, speed,
range: self.range, damage,
max_angle: self.max_angle.to_radians(), heal,
applied: false,
hit_count: 0,
knockback: 0.0,
is_melee: false,
lifesteal_eff: self.lifesteal_eff, lifesteal_eff: self.lifesteal_eff,
look_dir: self.particle_ori, duration: self.beam_duration,
owner: Some(*data.uid),
};
let pos = Pos(data.pos.0 + Vec3::new(0.0, 0.0, 1.0));
// Create beam segment
update.server_events.push_front(ServerEvent::Beam {
properties,
pos,
ori: Ori(data.inputs.look_dir),
}); });
update.character = CharacterState::BasicBeam(Data { update.character = CharacterState::BasicBeam(Data {
@ -85,6 +93,7 @@ impl CharacterBehavior for Data {
buildup_duration: self.buildup_duration, buildup_duration: self.buildup_duration,
recover_duration: self.recover_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),
beam_duration: self.beam_duration,
base_hps: self.base_hps, base_hps: self.base_hps,
base_dps: self.base_dps, base_dps: self.base_dps,
tick_rate: self.tick_rate, tick_rate: self.tick_rate,
@ -105,6 +114,7 @@ impl CharacterBehavior for Data {
.checked_sub(Duration::from_secs_f32(data.dt.0)) .checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(), .unwrap_or_default(),
recover_duration: self.recover_duration, recover_duration: self.recover_duration,
beam_duration: self.beam_duration,
base_hps: self.base_hps, base_hps: self.base_hps,
base_dps: self.base_dps, base_dps: self.base_dps,
tick_rate: self.tick_rate, tick_rate: self.tick_rate,
@ -120,6 +130,7 @@ impl CharacterBehavior for Data {
buildup_duration: self.buildup_duration, buildup_duration: self.buildup_duration,
recover_duration: self.recover_duration, recover_duration: self.recover_duration,
cooldown_duration: self.cooldown_duration, cooldown_duration: self.cooldown_duration,
beam_duration: self.beam_duration,
base_hps: self.base_hps, base_hps: self.base_hps,
base_dps: self.base_dps, base_dps: self.base_dps,
tick_rate: self.tick_rate, tick_rate: self.tick_rate,
@ -148,6 +159,7 @@ impl CharacterBehavior for Data {
.recover_duration .recover_duration
.checked_sub(Duration::from_secs_f32(data.dt.0)) .checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(), .unwrap_or_default(),
beam_duration: self.beam_duration,
base_hps: self.base_hps, base_hps: self.base_hps,
base_dps: self.base_dps, base_dps: self.base_dps,
tick_rate: self.tick_rate, tick_rate: self.tick_rate,

252
common/src/sys/beam.rs Normal file
View File

@ -0,0 +1,252 @@
use crate::{
comp::{
group, Beam, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, Last,
Loadout, Ori, Pos, Scale, Stats,
},
event::{EventBus, ServerEvent},
state::{DeltaTime, Time},
sync::{Uid, UidAllocator},
};
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, 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>,
WriteStorage<'a, Beam>,
);
fn run(
&mut self,
(
entities,
server_bus,
time,
dt,
uid_allocator,
uids,
positions,
last_positions,
orientations,
scales,
bodies,
stats,
loadouts,
groups,
character_states,
mut beams,
): Self::SystemData,
) {
let mut server_emitter = server_bus.emitter();
let time = time.0;
let dt = dt.0;
// Beams
for (entity, uid, pos, ori, beam) in
(&entities, &uids, &positions, &orientations, &beams).join()
{
let creation_time = match beam.creation {
Some(time) => time,
// Skip newly created beam segments
None => continue,
};
let end_time = creation_time + beam.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
// end point
if end_time < time {
server_emitter.emit(ServerEvent::Destroy {
entity,
cause: HealthSource::World,
});
}
// Determine area that was covered by the beam 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 = (beam.speed * (time_since_creation - frame_time)).max(0.0);
let frame_end_dist = (beam.speed * time_since_creation).max(frame_start_dist);
// 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));
// 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,
) 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,
)
.join()
{
// Scales
let scale_b = scale_b_maybe.map_or(1.0, |s| s.0);
let rad_b = body_b.radius() * scale_b;
let height_b = body_b.height() * scale_b;
// Check if it is a hit
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)}));
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);
// Don't damage in the same group
if same_group {
continue;
}
// Weapon gives base damage
let source = DamageSource::Melee;
let mut damage = Damage {
healthchange: -(beam.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 beams
&& 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: beam.owner.unwrap_or(*uid),
},
},
});
}
}
}
}
// 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);
}
});
beams.set_event_emission(true);
}
}
/// Assumes upright cylinder
/// See page 12 of https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.396.7952&rep=rep1&type=pdf
fn sphere_wedge_cylinder_collision(
// Values for spherical wedge
real_pos: Vec3<f32>,
min_rad: f32, // Distance from beam origin to inner section of beam
max_rad: f32, //Distance from beam origin to outer section of beam
ori: Vec3<f32>,
angle: f32,
// Values for cylinder
bottom_pos_b: Vec3<f32>, // Position of bottom of cylinder
rad_b: f32,
length_b: f32,
) -> bool {
// Converts all coordinates so that the new origin is in the center of the
// cylinder
let center_pos_b = Vec3::new(bottom_pos_b.x, bottom_pos_b.y, bottom_pos_b.z + length_b / 2.0);
let pos = real_pos - center_pos_b;
let pos_b = Vec3::zero();
if pos.distance_squared(pos_b) > (max_rad + rad_b + length_b).powi(2) {
// Does quick check if entity is too far (I'm not sure if necessary, but
// probably makes detection more efficient)
false
} else if pos.z.abs() <= length_b / 2.0 {
// Checks case 1: center of sphere is on same z-height as cylinder
let pos2 = Vec2::<f32>::from(pos);
let ori2 = Vec2::from(ori);
let distance = pos2.distance(Vec2::zero());
let in_range = distance < max_rad && distance > min_rad;
let in_angle = pos2.angle_between(-ori2) < angle + (rad_b / distance).atan().abs()
&& pos.angle_between(-ori) < angle + (length_b / 2.0 / distance).atan().abs();
in_range && in_angle
} else {
// Checks case 2: if sphere collides with top/bottom of cylinder, doesn't use
// paper. Logic used here is it checks if line between centers passes through either cap, then if the cap is within range, then if withing angle of beam. If line
let sign = if pos.z > 0.0 { 1.0 } else { -1.0 };
let (in_range, in_angle): (bool, bool);
let intersect_frac = (length_b / 2.0 / pos.z).abs();
let edge_pos = Vec3::new(pos.x, pos.y, 0.0).normalized() * rad_b;
let intersect_point = Vec2::new(pos.x * intersect_frac, pos.y * intersect_frac);
if intersect_point.distance_squared(Vec2::zero()) <= rad_b.powi(2) {
// Checks if line between sphere and cylinder center passes through cap of cylinder
let distance_squared = Vec3::new(intersect_point.x, intersect_point.y, length_b / 2.0).distance_squared(pos);
in_range = distance_squared < max_rad.powi(2) && distance_squared > min_rad.powi(2);
let mod_pos = Vec3::new(pos.x, pos.y, pos.z - sign * length_b / 2.0); // Changes position so I can compare this with origin instead of original position with top of cylinder
let angle2 = (pos_b - mod_pos).angle_between(edge_pos - mod_pos); // Angle between (line between center of endcap and sphere center) and (line between edge of endcap and sphere center)
in_angle = mod_pos.angle_between(-ori) < angle + angle2;
} else {
let endcap_edge_pos = Vec3::new(edge_pos.x, edge_pos.y, sign * length_b / 2.0);
let distance_squared = endcap_edge_pos.distance_squared(pos);
in_range = distance_squared < max_rad.powi(2) && distance_squared > min_rad.powi(2);
let angle2 = (endcap_edge_pos - pos).angle_between(edge_pos - pos); // Angle between (line between center of sphere, and edge of cylinder in center height) and (line between center of sphere, and edge of endcap)
in_angle = (pos - edge_pos).angle_between(-ori) < angle + angle2;
}
in_range && in_angle
}
}

View File

@ -1,4 +1,5 @@
pub mod agent; pub mod agent;
mod beam;
pub mod character_behavior; pub mod character_behavior;
pub mod combat; pub mod combat;
pub mod controller; pub mod controller;
@ -15,6 +16,7 @@ use specs::DispatcherBuilder;
pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys"; pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys";
pub const COMBAT_SYS: &str = "combat_sys"; pub const COMBAT_SYS: &str = "combat_sys";
pub const AGENT_SYS: &str = "agent_sys"; pub const AGENT_SYS: &str = "agent_sys";
pub const BEAM_SYS: &str = "beam_sys";
pub const CONTROLLER_SYS: &str = "controller_sys"; pub const CONTROLLER_SYS: &str = "controller_sys";
pub const MOUNT_SYS: &str = "mount_sys"; pub const MOUNT_SYS: &str = "mount_sys";
pub const PHYS_SYS: &str = "phys_sys"; pub const PHYS_SYS: &str = "phys_sys";
@ -33,5 +35,6 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(phys::Sys, PHYS_SYS, &[CONTROLLER_SYS, MOUNT_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(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]);
dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]); dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]);
dispatch_builder.add(beam::Sys, BEAM_SYS, &[PHYS_SYS]);
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]); dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]);
} }

View File

@ -2,7 +2,7 @@ use crate::{sys, Server, StateExt};
use common::{ use common::{
character::CharacterId, character::CharacterId,
comp::{ comp::{
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity, self, beam, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity,
Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel, Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel,
WaypointArea, WaypointArea,
}, },
@ -136,6 +136,11 @@ pub fn handle_shockwave(
state.create_shockwave(properties, pos, ori).build(); state.create_shockwave(properties, pos, ori).build();
} }
pub fn handle_beam(server: &mut Server, properties: beam::Properties, pos: Pos, ori: Ori) {
let state = server.state_mut();
state.create_beam(properties, pos, ori).build();
}
pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) { pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
server server
.state .state

View File

@ -4,7 +4,7 @@ use common::{
span, span,
}; };
use entity_creation::{ use entity_creation::{
handle_create_npc, handle_create_waypoint, handle_initialize_character, handle_beam, handle_create_npc, handle_create_waypoint, handle_initialize_character,
handle_loaded_character_data, handle_shockwave, handle_shoot, handle_loaded_character_data, handle_shockwave, handle_shoot,
}; };
use entity_manipulation::{ use entity_manipulation::{
@ -84,6 +84,11 @@ impl Server {
pos, pos,
ori, ori,
} => handle_shockwave(self, properties, pos, ori), } => handle_shockwave(self, properties, pos, ori),
ServerEvent::Beam {
properties,
pos,
ori,
} => handle_beam(self, properties, pos, ori),
ServerEvent::Knockback { entity, impulse } => { ServerEvent::Knockback { entity, impulse } => {
handle_knockback(&self, entity, impulse) handle_knockback(&self, entity, impulse)
}, },

View File

@ -45,6 +45,13 @@ pub trait StateExt {
pos: comp::Pos, pos: comp::Pos,
ori: comp::Ori, ori: comp::Ori,
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
/// Build a beam entity
fn create_beam(
&mut self,
properties: comp::beam::Properties,
pos: comp::Pos,
ori: comp::Ori,
) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server /// Insert common/default components for a new character joining the server
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId); fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
/// Update the components associated with the entity's current character. /// Update the components associated with the entity's current character.
@ -156,6 +163,22 @@ impl StateExt for State {
}) })
} }
fn create_beam(
&mut self,
properties: comp::beam::Properties,
pos: comp::Pos,
ori: comp::Ori,
) -> EcsEntityBuilder {
self.ecs_mut()
.create_entity_synced()
.with(pos)
.with(ori)
.with(comp::Beam {
properties,
creation: None,
})
}
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) { fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) {
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0; let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;

View File

@ -1,7 +1,7 @@
use super::SysTimer; use super::SysTimer;
use common::{ use common::{
comp::{ comp::{
Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter, Beam, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter,
Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, Stats, Sticky,
Vel, Vel,
}, },
@ -59,6 +59,7 @@ pub struct TrackedComps<'a> {
pub loadout: ReadStorage<'a, Loadout>, pub loadout: ReadStorage<'a, Loadout>,
pub character_state: ReadStorage<'a, CharacterState>, pub character_state: ReadStorage<'a, CharacterState>,
pub shockwave: ReadStorage<'a, Shockwave>, pub shockwave: ReadStorage<'a, Shockwave>,
pub beam: ReadStorage<'a, Beam>,
} }
impl<'a> TrackedComps<'a> { impl<'a> TrackedComps<'a> {
pub fn create_entity_package( pub fn create_entity_package(
@ -138,7 +139,7 @@ impl<'a> TrackedComps<'a> {
.get(entity) .get(entity)
.cloned() .cloned()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
// Add untracked comps self.beam.get(entity).cloned().map(|c| comps.push(c.into()));
// Add untracked comps // Add untracked comps
pos.map(|c| comps.push(c.into())); pos.map(|c| comps.push(c.into()));
vel.map(|c| comps.push(c.into())); vel.map(|c| comps.push(c.into()));
@ -168,6 +169,7 @@ pub struct ReadTrackers<'a> {
pub loadout: ReadExpect<'a, UpdateTracker<Loadout>>, pub loadout: ReadExpect<'a, UpdateTracker<Loadout>>,
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>, pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>, pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
pub beam: ReadExpect<'a, UpdateTracker<Beam>>,
} }
impl<'a> ReadTrackers<'a> { impl<'a> ReadTrackers<'a> {
pub fn create_sync_packages( pub fn create_sync_packages(
@ -206,7 +208,8 @@ impl<'a> ReadTrackers<'a> {
&comps.character_state, &comps.character_state,
filter, filter,
) )
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter); .with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter)
.with_component(&comps.uid, &*self.beam, &comps.beam, filter);
(entity_sync_package, comp_sync_package) (entity_sync_package, comp_sync_package)
} }
@ -233,6 +236,7 @@ pub struct WriteTrackers<'a> {
loadout: WriteExpect<'a, UpdateTracker<Loadout>>, loadout: WriteExpect<'a, UpdateTracker<Loadout>>,
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>, character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>, shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
beam: WriteExpect<'a, UpdateTracker<Beam>>,
} }
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
@ -258,6 +262,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
.character_state .character_state
.record_changes(&comps.character_state); .record_changes(&comps.character_state);
trackers.shockwave.record_changes(&comps.shockwave); trackers.shockwave.record_changes(&comps.shockwave);
trackers.beam.record_changes(&comps.beam);
// Debug how many updates are being sent // Debug how many updates are being sent
/* /*
macro_rules! log_counts { macro_rules! log_counts {
@ -290,6 +295,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
log_counts!(loadout, "Loadouts"); log_counts!(loadout, "Loadouts");
log_counts!(character_state, "Character States"); log_counts!(character_state, "Character States");
log_counts!(shockwave, "Shockwaves"); log_counts!(shockwave, "Shockwaves");
log_counts!(beam, "Beams");
*/ */
} }
@ -313,6 +319,7 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<Loadout>(); world.register_tracker::<Loadout>();
world.register_tracker::<CharacterState>(); world.register_tracker::<CharacterState>();
world.register_tracker::<Shockwave>(); world.register_tracker::<Shockwave>();
world.register_tracker::<Beam>();
} }
/// Deleted entities grouped by region /// Deleted entities grouped by region

View File

@ -347,15 +347,12 @@ impl ParticleMgr {
if let CharacterState::BasicBeam(b) = character_state { if let CharacterState::BasicBeam(b) = character_state {
let particle_ori = b.particle_ori.unwrap_or(*ori.vec()); let particle_ori = b.particle_ori.unwrap_or(*ori.vec());
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) { for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) {
let buildup = b.buildup_duration.as_millis() as i32; if b.buildup_duration == Duration::default() {
for t in 0..((buildup / 50) + 1) {
let frac = ((t * 50) as f32) / 250.0; // Default value of buildup duration hardcoded for now, as it currently decreases over time
let dur = (2000.0 * (1.0 - frac)).max(0.0) as u64;
self.particles.push(Particle::new_beam( self.particles.push(Particle::new_beam(
Duration::from_millis(dur), b.beam_duration,
time, time,
ParticleMode::HealingBeam, ParticleMode::HealingBeam,
pos.0 + particle_ori * b.range * frac, pos.0,
pos.0 + particle_ori * b.range, pos.0 + particle_ori * b.range,
)); ));
} }