2020-12-01 00:28:00 +00:00
use common ::{
2021-02-02 18:02:40 +00:00
combat ::AttackerInfo ,
2020-09-05 16:27:36 +00:00
comp ::{
2021-02-22 18:57:56 +00:00
Beam , BeamSegment , Body , Energy , Group , Health , HealthSource , Inventory , Last , Ori , Pos ,
2021-01-29 01:37:33 +00:00
Scale ,
2020-09-05 16:27:36 +00:00
} ,
event ::{ EventBus , ServerEvent } ,
2020-12-01 00:28:00 +00:00
resources ::{ DeltaTime , Time } ,
2020-12-13 17:11:55 +00:00
uid ::{ Uid , UidAllocator } ,
2020-11-04 02:01:12 +00:00
GroupTarget ,
2020-09-05 16:27:36 +00:00
} ;
2021-02-22 18:57:56 +00:00
use specs ::{
saveload ::MarkerAllocator , shred ::ResourceId , Entities , Join , Read , ReadStorage , System ,
SystemData , World , WriteStorage ,
} ;
2020-09-24 02:02:30 +00:00
use std ::time ::Duration ;
2020-09-05 16:27:36 +00:00
use vek ::* ;
2021-02-22 18:57:56 +00:00
#[ derive(SystemData) ]
2021-02-22 21:02:37 +00:00
pub struct ReadData < ' a > {
2021-02-22 18:57:56 +00:00
entities : Entities < ' a > ,
server_bus : Read < ' a , EventBus < ServerEvent > > ,
time : Read < ' a , Time > ,
dt : Read < ' a , DeltaTime > ,
uid_allocator : Read < ' a , UidAllocator > ,
uids : ReadStorage < ' a , Uid > ,
positions : ReadStorage < ' a , Pos > ,
last_positions : ReadStorage < ' a , Last < Pos > > ,
orientations : ReadStorage < ' a , Ori > ,
scales : ReadStorage < ' a , Scale > ,
bodies : ReadStorage < ' a , Body > ,
healths : ReadStorage < ' a , Health > ,
inventories : ReadStorage < ' a , Inventory > ,
groups : ReadStorage < ' a , Group > ,
energies : ReadStorage < ' a , Energy > ,
}
2020-09-24 02:02:30 +00:00
/// This system is responsible for handling beams that heal or do damage
2020-09-05 16:27:36 +00:00
pub struct Sys ;
impl < ' a > System < ' a > for Sys {
type SystemData = (
2021-02-22 21:02:37 +00:00
ReadData < ' a > ,
2020-09-24 02:02:30 +00:00
WriteStorage < ' a , BeamSegment > ,
2020-09-05 16:27:36 +00:00
WriteStorage < ' a , Beam > ,
) ;
2021-02-22 21:02:37 +00:00
fn run ( & mut self , ( read_data , mut beam_segments , mut beams ) : Self ::SystemData ) {
let mut server_emitter = read_data . server_bus . emitter ( ) ;
2021-02-22 18:57:56 +00:00
2021-02-22 21:02:37 +00:00
let time = read_data . time . 0 ;
let dt = read_data . dt . 0 ;
2020-09-05 16:27:36 +00:00
// Beams
2021-02-22 18:57:56 +00:00
for ( entity , pos , ori , beam_segment ) in (
2021-02-22 21:02:37 +00:00
& read_data . entities ,
& read_data . positions ,
& read_data . orientations ,
2021-02-22 18:57:56 +00:00
& beam_segments ,
)
. join ( )
2020-09-05 16:27:36 +00:00
{
2020-09-24 02:02:30 +00:00
let creation_time = match beam_segment . creation {
2020-09-05 16:27:36 +00:00
Some ( time ) = > time ,
// Skip newly created beam segments
None = > continue ,
} ;
2020-09-24 02:02:30 +00:00
let end_time = creation_time + beam_segment . duration . as_secs_f64 ( ) ;
2020-09-05 16:27:36 +00:00
// 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 ;
2020-09-24 02:02:30 +00:00
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 ) ;
2021-02-22 21:02:37 +00:00
let beam_owner = beam_segment
. owner
. and_then ( | uid | read_data . uid_allocator . retrieve_entity_internal ( uid . into ( ) ) ) ;
2020-09-05 16:27:36 +00:00
// Group to ignore collisions with
// Might make this more nuanced if beams are used for non damage effects
2021-02-22 21:02:37 +00:00
let group = beam_owner . and_then ( | e | read_data . groups . get ( e ) ) ;
2020-09-24 02:02:30 +00:00
let hit_entities = if let Some ( beam ) = beam_owner . and_then ( | e | beams . get_mut ( e ) ) {
& mut beam . hit_entities
} else {
continue ;
} ;
2020-09-05 16:27:36 +00:00
// Go through all other effectable entities
2021-02-22 18:57:56 +00:00
for ( target , uid_b , pos_b , health_b , body_b ) in (
2021-02-22 21:02:37 +00:00
& read_data . entities ,
& read_data . uids ,
& read_data . positions ,
& read_data . healths ,
& read_data . bodies ,
2020-09-05 16:27:36 +00:00
)
. join ( )
{
2020-09-24 02:02:30 +00:00
// Check to see if entity has already been hit recently
if hit_entities . iter ( ) . any ( | & uid | uid = = * uid_b ) {
continue ;
}
2020-09-05 16:27:36 +00:00
// Scales
2021-02-22 21:02:37 +00:00
let scale_b = read_data . scales . get ( target ) . map_or ( 1.0 , | s | s . 0 ) ;
let last_pos_b_maybe = read_data . last_positions . get ( target ) ;
2020-09-05 16:27:36 +00:00
let rad_b = body_b . radius ( ) * scale_b ;
let height_b = body_b . height ( ) * scale_b ;
// Check if it is a hit
2021-02-22 18:57:56 +00:00
let hit = entity ! = target
2020-10-31 22:34:08 +00:00
& & ! health_b . is_dead
2020-09-05 16:27:36 +00:00
// Collision shapes
2021-02-04 09:17:38 +00:00
& & ( sphere_wedge_cylinder_collision ( pos . 0 , frame_start_dist , frame_end_dist , * ori . look_dir ( ) , 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 . look_dir ( ) , beam_segment . angle , ( pos_maybe . 0 ) . 0 , rad_b , height_b ) } ) ) ;
2020-09-05 16:27:36 +00:00
if hit {
// See if entities are in the same group
let same_group = group
2021-02-22 21:02:37 +00:00
. map ( | group_a | Some ( group_a ) = = read_data . groups . get ( target ) )
2020-09-24 02:02:30 +00:00
. unwrap_or ( Some ( * uid_b ) = = beam_segment . owner ) ;
2020-09-05 16:27:36 +00:00
2020-11-02 00:26:01 +00:00
let target_group = if same_group {
GroupTarget ::InGroup
} else {
GroupTarget ::OutOfGroup
} ;
2020-09-06 21:01:56 +00:00
// If owner, shouldn't heal or damage
2020-09-24 02:02:30 +00:00
if Some ( * uid_b ) = = beam_segment . owner {
2020-09-06 21:01:56 +00:00
continue ;
}
2020-09-05 16:27:36 +00:00
2021-02-02 18:02:40 +00:00
let attacker_info =
beam_owner
. zip ( beam_segment . owner )
. map ( | ( entity , uid ) | AttackerInfo {
entity ,
uid ,
2021-02-22 21:02:37 +00:00
energy : read_data . energies . get ( entity ) ,
2021-02-02 18:02:40 +00:00
} ) ;
beam_segment . properties . attack . apply_attack (
2021-02-01 02:43:26 +00:00
target_group ,
2021-02-02 18:02:40 +00:00
attacker_info ,
2021-02-22 18:57:56 +00:00
target ,
2021-02-22 21:02:37 +00:00
read_data . inventories . get ( target ) ,
2021-02-04 09:17:38 +00:00
ori . look_dir ( ) ,
2021-02-01 02:43:26 +00:00
false ,
1.0 ,
2021-02-02 18:02:40 +00:00
| e | server_emitter . emit ( e ) ,
2021-02-01 02:43:26 +00:00
) ;
2021-02-02 18:02:40 +00:00
hit_entities . push ( * uid_b ) ;
2020-09-05 16:27:36 +00:00
}
}
}
2020-09-24 02:02:30 +00:00
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 ( ) ;
}
}
2020-09-05 16:27:36 +00:00
// Set start time on new beams
// This change doesn't need to be recorded as it is not sent to the client
2020-09-24 02:02:30 +00:00
beam_segments . set_event_emission ( false ) ;
2021-01-07 20:25:12 +00:00
( & mut beam_segments ) . join ( ) . for_each ( | mut beam_segment | {
2020-09-24 02:02:30 +00:00
if beam_segment . creation . is_none ( ) {
beam_segment . creation = Some ( time ) ;
2020-09-05 16:27:36 +00:00
}
} ) ;
2020-09-24 02:02:30 +00:00
beam_segments . set_event_emission ( true ) ;
2020-09-05 16:27:36 +00:00
}
}
/// Assumes upright cylinder
/// See page 12 of https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.396.7952&rep=rep1&type=pdf
2020-09-07 21:46:25 +00:00
#[ allow(clippy::too_many_arguments) ]
2020-09-05 16:27:36 +00:00
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
2020-09-05 23:45:36 +00:00
let center_pos_b = Vec3 ::new (
bottom_pos_b . x ,
bottom_pos_b . y ,
bottom_pos_b . z + length_b / 2.0 ,
) ;
2020-09-05 16:27:36 +00:00
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 ;
2020-09-05 23:45:36 +00:00
// Done so that if distance = 0, atan() can still be calculated https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6d2221bb9454debdfca8f9c52d1edb29
let tangent_value1 : f32 = rad_b / distance ;
let tangent_value2 : f32 = length_b / 2.0 / distance ;
let in_angle = pos2 . angle_between ( - ori2 ) < angle + ( tangent_value1 ) . atan ( ) . abs ( )
& & pos . angle_between ( - ori ) < angle + ( tangent_value2 ) . atan ( ) . abs ( ) ;
2020-09-05 16:27:36 +00:00
in_range & & in_angle
} else {
// Checks case 2: if sphere collides with top/bottom of cylinder, doesn't use
2020-09-05 23:45:36 +00:00
// 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
2020-09-05 16:27:36 +00:00
let sign = if pos . z > 0.0 { 1.0 } else { - 1.0 } ;
2020-09-05 23:45:36 +00:00
let height = sign * length_b / 2.0 ;
2020-09-05 16:27:36 +00:00
let ( in_range , in_angle ) : ( bool , bool ) ;
2020-09-05 23:45:36 +00:00
// Gets relatively how far along the line (between sphere and cylinder centers)
// the endcap of the cylinder is, is between 0 and 1 when sphere center is not
// in cylinder
2020-09-05 16:27:36 +00:00
let intersect_frac = ( length_b / 2.0 / pos . z ) . abs ( ) ;
2020-09-05 23:45:36 +00:00
// Gets the position of the cylinder edge closest to the sphere center
2020-09-06 19:39:23 +00:00
let edge_pos = if let Some ( vec ) = Vec3 ::new ( pos . x , pos . y , 0.0 ) . try_normalized ( ) {
vec * rad_b
} else {
// Returns an arbitrary location that is still guaranteed to be on the cylinder
// edge. This case should only happen when the sphere is directly above the
// cylinder, in which case all positions on edge are equally close.
Vec3 ::new ( rad_b , 0.0 , 0.0 )
} ;
2020-09-23 00:18:00 +00:00
// Gets position on opposite edge of same endcap
let opp_end_edge_pos = Vec3 ::new ( - edge_pos . x , - edge_pos . y , height ) ;
// Gets position on same edge of opposite endcap
let bot_end_edge_pos = Vec3 ::new ( edge_pos . x , edge_pos . y , - height ) ;
2020-09-05 23:45:36 +00:00
// Gets point on line between sphere and cylinder centers that the z value is
// equal to the endcap z location
let intersect_point = Vec2 ::new ( pos . x * intersect_frac , pos . y * intersect_frac ) ;
// Checks if line between sphere and cylinder center passes through cap of
// cylinder
2020-09-05 16:27:36 +00:00
if intersect_point . distance_squared ( Vec2 ::zero ( ) ) < = rad_b . powi ( 2 ) {
2020-09-06 19:39:23 +00:00
let distance_squared =
Vec3 ::new ( intersect_point . x , intersect_point . y , height ) . distance_squared ( pos ) ;
2020-09-05 16:27:36 +00:00
in_range = distance_squared < max_rad . powi ( 2 ) & & distance_squared > min_rad . powi ( 2 ) ;
2020-09-23 00:18:00 +00:00
// Angle between (line between centers of cylinder and sphere) and either (line
// between opposite edge of endcap and sphere center) or (line between close
// edge of endcap on bottom of cylinder and sphere center). Whichever angle is
// largest is used.
let angle2 = ( pos_b - pos )
. angle_between ( opp_end_edge_pos - pos )
. max ( ( pos_b - pos ) . angle_between ( bot_end_edge_pos - pos ) ) ;
in_angle = pos . angle_between ( - ori ) < angle + angle2 ;
2020-09-05 16:27:36 +00:00
} else {
2020-09-05 23:45:36 +00:00
// TODO: Handle collision for this case more accurately
// For this case, the nearest point will be the edge of the endcap
let endcap_edge_pos = Vec3 ::new ( edge_pos . x , edge_pos . y , height ) ;
2020-09-05 16:27:36 +00:00
let distance_squared = endcap_edge_pos . distance_squared ( pos ) ;
2020-09-05 23:45:36 +00:00
in_range = distance_squared > min_rad . powi ( 2 ) & & distance_squared < max_rad . powi ( 2 ) ;
// Gets side positions on same endcap
let side_end_edge_pos_1 = Vec3 ::new ( edge_pos . y , - edge_pos . x , height ) ;
let side_end_edge_pos_2 = Vec3 ::new ( - edge_pos . y , edge_pos . x , height ) ;
2020-09-23 00:18:00 +00:00
// Gets whichever angle is bigger, between sphere center and opposite edge,
// sphere center and bottom edge, or half of sphere center and both the side
// edges
let angle2 = ( pos_b - pos ) . angle_between ( opp_end_edge_pos - pos ) . max (
( pos_b - pos ) . angle_between ( bot_end_edge_pos - pos ) . max (
( side_end_edge_pos_1 - pos ) . angle_between ( side_end_edge_pos_2 - pos ) / 2.0 ,
) ,
) ;
2020-09-05 23:45:36 +00:00
// Will be somewhat inaccurate, tends towards hitting when it shouldn't
// Checks angle between orientation and line between sphere and cylinder centers
in_angle = pos . angle_between ( - ori ) < angle + angle2 ;
2020-09-05 16:27:36 +00:00
}
in_range & & in_angle
}
}