mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added beam system. Added collision code for spherical wedge/cylinder detection.
This commit is contained in:
parent
ddbe871b50
commit
46563e7008
@ -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
36
common/src/comp/beam.rs
Normal 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 }
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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>,
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>();
|
||||||
|
@ -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
252
common/src/sys/beam.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user